1、如何创建另一个控件
  我需要我的控件在设计时创建后生成一个新的TPanel控件,该控件在设计状态下可以单独使用(就像TPageControl选择New Page时会多出一个TTabSheet一样)
2、如何响应父控件属性的改变
  如何在父控件属性改变时做出相应的改变(如使Caption属性与父控件的同步)
请指教
 

解决方案 »

  1.   

    1.在原控件的基础上publised一个Panels属性,并在SetPanels过程中动态生成Panel。
    2.VCL中定义了一些Parent消息,你可以在控件中处理这些消息。
      

  2.   

    我需要的是在设计状态下控件创建时自动生成Panel,且作为单独的控件使用(即在对象管理器中可以看见并设置属性)
      

  3.   

    我看了看tpagecontrol,也没看到那个右键菜单是咋加的~~
      

  4.   

    delphi5开发人员指南  21.3节
      

  5.   

    右键菜必须用到组件编辑器(ComponentEditor),有些组件编辑器Delphi已经定义了,但如果你要的组件编辑器没有定义,那么必须自己做一个,然后注册就可以了。
      

  6.   

    DELPHI构件制作方法          Delphi作为RAD工具,以其快速编译和友好的可视化界面受到广泛欢迎。Delphi提供了很多现成构件,而且随着版本更新不断增加新构件。另外还可以买到第三方开发的特色构件,或从因特网下载免费构件。这些构件足以支持一般应用系统开发。但应用开发人员仍有必要自己制作构件。采用构件形式可以把对象严密封装,并加上一层直观外壳,有利于软件调试和代码重用。开发群体以构件为功能单位分工协作,比较容易实现工程化管理,从软件规划设计到测试修改都可以减少意外差错,大大提高工作效率。成熟的构件还可以作为商品软件出售,带来附加效益,且有利于软件开发的社会化分工协作。Delphi的构件使用和构件制作采用同样的工作环境和相似的编程方法,只要弄清基本原理,制作构件无需学习多少新东西。  基本概念 制作构件的基本过程可以概括为:  1.编写构件单元(unit)。其中包含构件声明和构件实现代码。  2.按照与普通Delphi单元同样的方法编译和调试构件单元。  3.创建构件注册单元。其中用uses语句连接构件单元,并用Register过程完成构件的注册。  4.编写构件联机帮助信息,并编译成标准Windows帮助文件。         全部工作完成后,生成构件单元二进制文件(.DCU)、构件注册源文件(.PAS)和帮助信息文件(.HLP)及附加的关键词文件(.KWF)。用户拿到这些文件后,就可以安装使用了。在Delphi环境下调用菜单命令,启动安装过程(安装过程中需指定注册文件名),可以把构件注册到Delphi的VCL库中,并在构件工具条上生成一个新按钮。借助HelpInst安装工具可以把关键词文件并入Dephi帮助索引系统,用F1键实现联机帮助。          这样制作出的.DCU文件与一般Delphi单元没有根本区别,即使不安装到VCL库中也可以由其他单元直接调用。最大的区别在于:构件单元中某些属性和事件声明为published,从而在程序设计期对用户是可见的,用户可以通过对象编辑窗口(ObjectInspector)访问这些属性和事件。这是可视化程序设计的关键所在。  对象的继承与修改            制作构件第一件事就是选择适当的Delphi对象类型作为父对象,以派生新的对象。子对象可以继承父对象的全部非private部件,但不能摆脱不需要的部件。因此,所选父对象应尽可能多地包含子对象所需的属性、事件和方法,但不应包含子对象不需要的东西。              TComponent是所有Delphi构件的基点,但若直接从TComponent派生新构件,很多东西就需要自己从头做起。一般只有非可视构件才直接从TComponent派生。Delphi提供了若干专门用于制作控件(可视构件)的对象类型,都是从TControl和TWinControl派生而来。其派生关系如下:  TControl---TGraphicControl---TCustomLabel  
    TWinControl--TCustomControl---TCustomGrid  
    ---TButtonControl--TCustomGroupBox  
    ---TScrollingWinControl--TCustomPanel  
    ---TCustomComboBox  
    ---TCustomEdit  
    ---TCustomListBox  TControl的子类型用于非窗口式控件,TWinControl的子类型则用于窗口式控件。除非特殊需要,一般不直接从TControl和TWinControl派生新控件,而是从其子类型派生。这样可以充分利用原有的属性、事件和方法,减少很多工作量。  在这些构件类型中,非通用的属性、事件和方法都声明为protected。这样可以禁止构件用户访问,又能被子类型继承和修改。在新构件中,可以简单地把继承来的属性和事件重新声明为published,使构件用户能在设计期通过对象编辑窗口访问,也可以进而修改属性的默认值和读写方式,或是重载(override)事件处理子过程和其他构件方法,以修改其中的程序代码。重声明可以放宽访问权限,但不能 相反,例如,不可能把published属性重声明为private或protected。  为了增加新功能,常常需要定义全新的属性、事件和方法。定义时,一般总是把对用户开放的属性和事件声明为published,把方法声明为public或protected。  构件属性  在构件中,属性和方法往往可以相互替代。对构件用户来说,属性比方法更直观简便。因此,只要可能,应尽量以属性取代方法。属性类型包括简单类型(numeric,character,string)、枚举类型、集合类型、对象类型(例如font)和数组类型(例如TStrings 类型中的Strings)。其定义方法如下:  type  
    private  
    FLayers:Integer;{内部存储用的变量}  
    functionGetLayers:Integer;{用来读属性值的方法}  
    procedureSetLayers(ALayers:Integer);{用来写属性值的方法}  
    published  
    propertyLayers:  
    IntegerreadGetLayerswriteSetLayersdefault1;  
    end;  每个属性都需要相应的private变量用于内部存储。按照约定,变量名以F打头,后跟属性名(此处为Layers),读写方法名称分别为Get加属性名和Set加属性名。写方法总是带一个与属性类型相同的参数,用以传送属性值。此参数可以传值,也可以传递变量。如果不定义写方法(省略write部分),此属性便成为只读属性。读写方法应该在private部分声明,以使其对构件用户和构件的派生对象保持隐蔽。读写方法除了取值和赋值之外,还可以附加其他操作代码,使属性读写产生附加效应。这正是属性可以取代方法的原因。如果不需要附加效应,可以不定义读写方法,采用直接访问格式来声明属性: propertyLayers:  
    IntegerreadFLayerswriteFLayersdefault1;  default命令符用来指定属性的默认值,同时需要在构件的构造函数中为属性设置初值。default命令的作用是在窗体文件存盘时提供参照:若属性当前值与default命令指定的值不同,则把当前值保存在文件中,否则便无需保存。如果省略default命令,属性当前值总是保存在窗体文件中。  事件与事件处理过程  创建构件时,事件也被当做属性来处理,区别仅在于事件必须定义为过程类型,使其成为一个隐蔽指针,指向某个潜在的过程。当构件用户为事件指定处理子程序后,事件便成为指向该子程序的指针。事件的定义方式如下:  type  
    private  
    FOnClick:TNotifyEvent;{声明事件变量以保存过程指针}  
    published  
    propertyOnClick:  
    TNotifyEventreadFOnClickwriteFOnClick;  
    end;  
    此例正是Delphi标准控件中Click事件的定义方式。可以看出,除了OnClick被定义为过程类型外,其定义格式与一般属性的直接访问格式几乎完全相同。Delphi预定义了所有标准事件的过程类型及标准事件所引发的虚方法。其中,Click事件将引发如下虚方法: procedureTControl.Click;  
    begin  
    ifAssigned(OnClick)thenOnClick(Self);  
    {以下是默认处理部分}  
    end;  其中,Assigned函数检验OnClick是否已分配了事件处理过程。如果返回值为True,则调用用户指定的事件处理过程。通过重载此虚方法,可以修改Click事件的处理方式。在重载的方法中,一般应先调用用户处理程序,然后再安排后续处理。在本例中,首行代码应当是inheritedClick。  需要注意的是,构件用户不一定会给事件指定处理程序,因此事件不能定义为函数类型,否则可能会指向返回值类型不定的空函数。如果需要事件处理过程返回某个值,可以借助var参数。调用用户程序之前应确保此参数包含有效返回值,以免用户未指定事件处理过程时出错。  如果Delphi标准事件不能满足需要,也可以自己定义事件。其核心思想是选择适当的Windows消息来引发构件中的事件过程。篇幅所限,不拟详述,请读者参阅有关资料。  方法处理要点 
    方法处理在创建构件时和使用构件时没有多大区别,但有些问题仍需要注意。首先要注意的是,构件通常是在事件处理过程中调用,而构件作者又无法预测用户将在什么环境下如何调用构件。因此,构件中的方法应尽量避免占用系统资源,避免使Windows停止对用户操作的反应。  创建构件时应随时意识到,此构件不仅可以直接调用,而且可用来创建别的构件。即使是对用户隐蔽的方法也应具有完整的功能和清晰的接口。除了属性读写方法之外,内部方法一般应声明为protected虚方法,以便被派生对象继承和重载。属性读写方法则应采用private声明严密保护。派生对象如果需要读写父对象的属性值,应该访问属性本身,没有必要直接访问其读写方法。 构件测试  制作构件的核心工作是编写构件单元,包括根据构件功能要求设定对用户开放的属性、事件和方法,设定用以实现这些部件的变量、过程和函数等等。除了属性和事件有 特殊格式之外,构件单元的设计方式与一般Delphi单元没有什么不同,只是单元中不能包含窗体。  在编写构件单元的过程中,可以借助一个测试窗体直接对其测试。以可视化方法在窗体上安排构件,本质上不过是自动生成调用构件的代码。即使构件未并入VCL库,无法使用可视化操作,也可以手工编写这些调用代码。这样测试,可以免去反复修改而导致的反复安装。  测试时,需先建立一个窗体单元,然后进行以下操作:  
    1.把被测构件单元名称加入窗体单元的uses语句中,并在public部分声明被测构件的对象实例。  
    2.在窗体单元的FormCreate子程序中调用被测构件的Create方法,以构造构件实例,其Owner参数设置为Self,即窗体本身。然后给Parent属性赋值,并适当设置其他属性值。Parent是容纳构件的父对象,如果是窗体本身,应设置为Self。  
    3.运行包含测试窗体的工程,找出构件程序中的错误。  注册构件  注册构件用的程序代码可以放在构件单元中,但在Delphi下注册构件时要求提供包含注册代码的源程序文件(.PAS文件),因此,比较好的方式是把构件核心代码编译成.DCU文件或.DLL动态链接库,在注册源文件中只放注册代码和外围程序。下面是注册代码实例:  
    type  
    TMyPanelΚclass(TCustomPanel)  
    TMyLabelΚclass(TCustomLabel)  
    procedureRegister;  
    implementation  
    procedureRegister;  
    begin  
      

  7.   

    怎样编写自己的控件(基础篇)   作为一个Delphi程序员要进一步提高编程水平就必须掌握控件的编写方法,本篇文章是为初学编写控件的人所写,通过一个具体的简单的例子来介绍编写控件的一些基本的方法和模式。这个例子控件叫TLeiLabel,是在TLabel的基础上增加两个实用的功能:一是使文字具有立体形状,二是使文字具有超链接属性。 一、使文字具有立体形状 首先定义一个类型:T3DEffect = (Normal, Raised, Lowered, Shadowed);  四个值分别表示正常、凸起、凹进、阴影。 再定义一个属性Style3D如下: property Style3D: T3DEffect read FStyle3D write SetStyle3D default Normal; 这是定义属性的标准格式,包括属性名、属性类型、读和写声明及一个预定义值。它必须放在published中以便使属性出现在Object Inspector中。 读声明表示在用这个属性的值时是直接去读变量FStyle3D的值,写声明表示在给这个属性赋值时调用这个写方法SetStyle3D()去改变这个变量的值。 Default值并不表示其默认值,而只是表示在保存DFM文件时,若属性的值与之相等则不保存,不相等则保存,属性的默认值要在Create()中设置。 FStyle3D是在private中定义的一个变量:FStyle3D:T3DEffect; SetStyle3D()方法如下,这也是写方法的一般格式:      procedure TLeiLabel.SetStyle3D(Value: T3DEffect); 
        begin 
          if FStyle3D <> value then 
          begin 
            FStyle3D := value; 
            invalidate;   //表示控件将重画 
          end; 
        end; 
         
    另外对于带阴影的文字还要定义阴影的偏移量ShadeXOffSet、ShadeYOffSet: property ShadowXOffSet: integer read FXOffSet write SetFXOffSet default 5; property ShadowYOffSet: integer read FYOffSet write SetFYOffSet default -5; 写方法SetFXOffSet()、SetFYOffSet()和上面SetStyle3D()类似,略。 要重画控件一般要重载Paint方法,此处只是重画文字,只须重载DoDrawText方法(TLabel的DoDrawText的功能就是绘制文字)。 DoDrawText的声明放在Protected中:  procedure DoDrawText(var Rect: TRect; Flags: Longint); override; 此处DoDrawText()根据四种类型(正常、凸起、凹进、阴影)分别画出不同的文字:      procedure TLeiLabel.DoDrawText(var Rect: TRect; Flags: Longint); 
        var 
          Text: string; 
          TmpRect  : TRect; 
        begin 
          Text := GetLabelText; 
          if (Flags and DT_CALCRECT <> 0) and ((Text = '') or ShowAccelChar and 
            (Text[1] = '&') and (Text[2] = #0)) then Text := Text + ' '; 
          if not ShowAccelChar then Flags := Flags or DT_NOPREFIX; 
          Flags := DrawTextBiDiModeFlags(Flags); 
          Canvas.Font := Font;       Case FStyle3D of 
            Raised: 
              begin 
                TmpRect := Rect; 
                OffsetRect( TmpRect, 1, 1 ); 
                Canvas.Font.Color := clGray; 
                DrawText(Canvas.Handle, PChar(Text), Length(Text), TmpRect, Flags);             TmpRect := Rect; 
                OffsetRect( TmpRect, -1, -1 ); 
                Canvas.Font.Color := clWhite; 
                DrawText(Canvas.Handle, PChar(Text), Length(Text), TmpRect, Flags); 
              end ; 
            Lowered: 
              begin 
                TmpRect := Rect; 
                OffsetRect( TmpRect, 1, 1 ); 
                Canvas.Font.Color := clWhite; 
                DrawText(Canvas.Handle, PChar(Text), Length(Text), TmpRect, Flags);             TmpRect := Rect; 
                OffsetRect( TmpRect, -1, -1 ); 
                Canvas.Font.Color := clGray; 
                DrawText(Canvas.Handle, PChar(Text), Length(Text), TmpRect, Flags); 
              end ; 
            Shadowed: 
              begin 
                TmpRect := Rect; 
                OffsetRect( TmpRect, FXOffSet, FYOffSet ); 
                Canvas.Font.Color := clGray; 
                DrawText(Canvas.Handle, PChar(Text), Length(Text), TmpRect, Flags); 
              end; 
          end;       if not Enabled then 
            Canvas.Font.Color := clGrayText 
          else 
            Canvas.Font.Color := Font.Color;       DrawText(Canvas.Handle, PChar(Text), Length(Text), Rect, Flags); 
        end; 
       
    二、使文字具有超链接属性 定义一个属性URL表示要链接的网址或Email地址。 Property URL : String read FURL write SetURL; 写方法SetURL如下:     procedure TLeiLabel.SetURL(Value: String); 
        Begin 
          if FURL <> Value then  FURL := Value ; 
          if FURL <> '' then  Cursor := crHandPoint ; 
        end; 当点击此Label时要打开浏览器或收发邮件工具,这便要重载Click方法。 Procedure Click; Override;      procedure TLeiLabel.Click; 
        var s: string; 
        Begin 
          Inherited Click; 
          if FURL = '' then exit; 
          if LowerCase(Copy(FURL,1,7)) = 'http://' then 
            s := FURL 
          else if Pos('@',FURL) <> 0 Then 
            s := 'mailto:' + FURL 
          else 
            s := 'http://' + FURL; 
          ShellExecute(Application.Handle, 'open', PChar(s), NIL, NIL, SW_SHOWNORMAL); 
        end; 
        
    一般的超链接当鼠标移上时文字的颜色发生变化。为此加上属性HoverColor,表示鼠标移入时文字的颜色。 Property HoverColor : TColor Read FHoverColor Write SetHoverColor default clRed; 还要定义两个接收Windows消息CM_MOUSEENTER和CM_MOUSELEAVE(鼠标移进和移出)的过程: Procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER; Procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;      Procedure TLeiLabel.CMMouseEnter (Var Message: TMessage); 
        begin 
          If Enabled and Visible and getParentForm(Self).Active then 
          begin 
            FFontColor := Font.Color;   //将文字的颜色保存起来 
            Font.Color := FHoverColor;  //改变文字的颜色 
          End; 
        end; 
       
        Procedure TLeiLabel.CMMouseLeave (Var Message: TMessage); 
        begin 
          Font.Color:=FFontColor;    //恢复文字原来颜色 
        end; 
       
    为设置属性的默认值,还要重载构造Create()(经常还要重载析构Destroy())。 constructor Create(AOwner: TComponent); override; // destructor Destroy; override; 重载构造时必须首先调用祖先类的构造,若要重载析构,则必须要在最后调用祖先类的析构。      constructor TLeiLabel.Create(AOwner: TComponent); 
        begin 
          inherited Create(AOwner);  //必须首先调用祖先类的构造 
          FStyle3D := Normal; 
          FXOffSet := 5; 
          FYOffSet := -5; 
          FHoverColor := clRed; 
        end; 
        
    最后,还必须为这个控件加上图标。可以用Delphi中的Image Editor来创建一个24×24的Bitmap位图,保存为一个DCR文件,文件名必须与控件的Pas文件名相同(此处为LeiLabel.pas和LeiLabel.dcr),位图名必须与控件名相同且全部大写(此处为TLEILABEL)。这样,一个完整的实用的控件就做好了。
      

  8.   

    unit MyPanel;interfaceuses
      Windows, Messages, SysUtils, Classes, Controls, ExtCtrls, DesignEditors, DesignIntf;type  TMyPanel = class(TPanel)
      private
        { Private declarations }
        FOnMouseWheel: TMouseWheelEvent;
      protected
        { Protected declarations }    procedure CMParentColorChanged(var Msg: TMessage); message CM_PARENTCOLORCHANGED;
      public
        { Public declarations }
        procedure MouseWheelHandler(var Message: TMessage); override;
      published
        { Published declarations }
        property OnMouseWheel:TMouseWheelEvent read FOnMouseWheel write FOnMouseWheel;
      end;  TMyPanelEditor = class(TComponentEditor)
        procedure ExecuteVerb(Index: Integer); override;
        function GetVerb(Index: Integer): string; override;
        function GetVerbCount: Integer; override;
      end;procedure Register;implementationprocedure Register;
    begin
      RegisterComponents('GybCtrl', [TMyPanel]);
      RegisterComponentEditor(TMyPanel, TMyPanelEditor);
    end;{ TMyPanel }procedure TMyPanel.CMParentColorChanged(var Msg: TMessage);
    begin
      Caption := TMyPanel(Parent).Caption + '的颜色值: $' +
        IntToHex(TMyPanel(Parent).Color, 8);
    end;procedure TMyPanel.MouseWheelHandler(var Message: TMessage);
    var
      Handled: Boolean;
    begin
      Handled := False;
      if Assigned(FOnMouseWheel) then
        with Message do
          FonMouseWheel(Self, TShiftState(Byte(WParam)), WParamHi,
            Point(LParamLo, LParamHi), Handled);
      if not Handled then
        inherited;
    end;{ TMyPanelEditor }procedure TMyPanelEditor.ExecuteVerb(Index: Integer);
    begin
      with TMyPanel.Create(Component.Owner) do
      begin
        SetBounds(TMyPanel(Component).Left + 20, TMyPanel(Component).Top + 20,
                  TMyPanel(Component).Width,     TMyPanel(Component).Height);
        Parent := TMyPanel(Component).Parent;            
      end;
    end;function TMyPanelEditor.GetVerb(Index: Integer): string;
    begin
      Result := '新建一Panel';
    end;function TMyPanelEditor.GetVerbCount: Integer;
    begin
      Result := 1;
    end;end.
    如何在父控件属性改变时做出相应的改变:
    可以响应的有
      CM_PARENTFONTCHANGED      = CM_BASE + 8;
      CM_PARENTCOLORCHANGED     = CM_BASE + 9;
      CM_PARENTCTL3DCHANGED     = CM_BASE + 17;
      CM_PARENTSHOWHINTCHANGED  = CM_BASE + 35;
      CM_PARENTBIDIMODECHANGED  = CM_BASE + 61;但没有一个类似于CM_PARENTTEXTCHANGED的内部消息
      

  9.   

    对新生成的控件不要指定Owner(为空),只需要指定parent,但是必须在析构函数中显式的销毁该对象。
      

  10.   

    编写自己的可视化控件
    2001-03-09· 宋梦?·yesky  可视化控件(Visual Component)实际上就是一个类(class),要编写一个类,可以直接在*.pas文件中编写。但是要编写控件,则必须使用包(package)。从File菜单中选择New,新建一个Package,这就是存放和安装控件用的包。然后单击Package窗口中的Add按钮,添加一个元件(Unit)。   在弹出的对话框最上方选择New Component。因为一个控件的所有属性、方法、事件不可能都由自己编,所以就需要选择祖先类(或者叫做"父类"或"基类"),然后再在其上面添加自己的属性、方法、事件。在Ancestor type后的下拉框中选择所需的祖先类。由于编写可视化控件必须要画图,所以选择TGraphicControl作为祖先类。再在Class Name框中输入新控件(类)的名称,一般以"T"开头。Palette Page是用来选择新控件在Delphi的窗口中的控件页面名称,例如"Standard",这个可以自己取。在Unit File Name中添好新控件文件的路径及文件名,单击OK按钮。新的控件便加入了。现在可以为该控件编写代码了。   下面以编写一个可以自定义图片的滚动条为例,说明编写可视化控件的方法。   按照上面的方法,选择TGraphicControl为祖先类,新控件的名称是TPigHorizontalScroller(小猪水平滚动条)。选择好文件路径和文件名后,单击OK按钮,开始编写代码。   每一个控件,都会被创建(Create)和删除(Destroy),所以必须首先编写这两个过程。对于控件中的每一个过程,都必须在前面先定义,然后再在后面编写。定义的过程或属性有三种:一、在private后定义的是属于控件内部使用的,使用该控件的人无法看到;二、在protected后定义的一般是看不到的,只在别人使用该控件作为祖先类编写其它控件时才可见;三、在public后定义的只允许别人在程序中调用;四、在published后定义的可以在属性窗口(Object Inspector)中看到。由于创建和删除过程除了在编程过程中建立控件时自动执行外,还可能在程序运行过程中动态创建控件时被调用,所以把它定义在public后⑴。(该序号表示次步骤在所附源程序中的代码的位置,下同)现在也许还不知到应该在这两个过程中编写什么,如何去编。我们在下面将会讲到。  我们首先为这个控件添加一些属性。我们定义一个Max属性用于设置或读取滚动条的最大值。因为在程序中一般不直接使用属性,所以要定义一个变量,和该属性对应起来,一边修改或读取其值。因为它只在控件内部使用,所以我们把它定义在private后⑵。(一般与属性相关联的变量都以"F"开头,例如FMax)定义好变量后,再定义属性。这个属性需要再Object Inspector窗口中可见,所以把它定义再published后⑶。定义的语法是:   property <属性名>:<类型> read <读取该属性时对应的变量> write <写入该属性时对应的变量或过程>
       其它的变量和属性也类似的定义(例如Min最小值、Value当前值等)。下面我们定义几个属性和变量,用于设置滚动条的图片(因为图片变量比较特殊,所以单独讲一下)。我们把LeftButtonUpPicture(向左按钮图片)、LeftButtonDownPicture(向左按钮按下图片)等定义为TBitmap类型(一定要定义相对应的变量)。   大家一定注意到了,在所附的源程序中,定义这几个属性时,read后所指定的读取属性时对应的变量是F…,而write后指定的写入该属性时对应的不是一个变量,而是一个Set…之类的东西,这是一个自定义的过程。作为该功能的过程的定义为:
       procedure <过程名>(Value: <被设置的属性的值的类型>)   因为执行写入该类属性的时候需要做其它的事情,所以不能光用一个变量来处理,应该用一个过程来处理。这中过程一般定义在protected后。在该类过程中,使用一个在⑷处这样一个语句来给TBitmap类型的变量来赋值,这是由于该类型的变量不能直接赋值而采用的。  定义完这些TBitmap类型的变量的属性后,上面讲的create过程和destroy过程中就需要编写代码了。因为TBitmap也是一个类,所以在create过程中必须创建⑸,在destroy过程中必须释放掉(free)⑹。这里⑺所指的inherited语句是用于指明该过程是从祖先类类中继承来的。(这个一定不能掉)。   因为我们编写的是可视化控件,所以必须在控件上画图。我们这个控件的祖先类TGraphicControl中封装有一个Canvas(画布)对象,我们可以直接使用它来画图。如果你对画布的使用还不太熟悉,最好去找一本书来看一看。   下面要做的工作就是画图了。如何在控件上画图呢?祖先类TGraphicControl中有一个Paint事件,当控件需要重画时便会自动触发。我们现在要做的就是要为这个事件编写一段程序。首先在protected后定义一个Canvas对象。由于它是祖先类中已有的,所以不需要加任何说明⑻。我们将使用这个对象来画图。接着,就要定义一个Paint过程,编写绘制控件的代码。先在public后定义Paint过程。由于它是由祖先类触发的,而不是由用户调用的,所以后面必须加上override,否则,该控件将会由于Paint过程永远不会被调用而不成为可视化控件⑼。下面我们就来编写Paint过程的代码⑽。   该文章所附的源程序的Paint过程中的T_Height等变量是用来保存滚动条中按钮、滑块等的大小的,这部分程序和普通的Application中的程序差别不大,大部分都是对画布进行操作,相信大家一看就明白。值得注意的是下面对FAutoSize变量的判断⑾,FAutoSize是和该控件的属性AutoSize相关联的布尔型变量,是用来设置这个控件的大小是否随图片的大小而变化的。注意,在控件的代码中,一般都不直接调用属性,而是使用与其相对应的的变量。  程序编到这里,就算是终于给自己的新控件做了一个外型了,不过它还不能滚动。现在我们来编写鼠标事件,让我们能够操纵它。鼠标事件的过程的定义和Paint过程很相似,只是后面要加上参数说明⑿,鼠标事件分为MouseDown、MouseMove和MouseUp三个,在定义后面都要加上override。接下来在后面编写它的代码。注意:这里的鼠标事件是Mouse…,而不是通常的OnMouse…。可是在⒀处的定义是干什么用的呢?这里的事件定义,都是给用户使用的,也就是说,当使用该控件时,会在Object Inspector中的Event页面中显示出来。   这些鼠标事件的代码也非常简单,判断鼠标的坐标,在画布上画出相应的图片等,并同时触发相应的事件。值得注意的是,在调用自定义事件时,都要先用⒁处的这样一个语句来判断用户是否已经为该事件编写代码。这一点非常重要,否则会调用出错。   大家注意到了,刚才所调用的事件都是自定义的,定义的方法也很简单,和定义属性差不多,只是类型时TNotifyEvent罢了。    TNotifyEvent是默认事件,其定义为:   TNotifyEvent = procedure(Sender: TObject)   如果你要定义另外形式的事件,就必须这样:先在type后编写   <事件类型名称> = procedure(<参数>:<类型>) 例如:  TCustomEvent = procedure(a: Integer; b:String); 然后在public后定义:    <事件名称>:<事件类型名称> 例如:   AnEvent: TCustomEvent;   看完这些,这整个程序你应该理解了吧。如果编译或运行出错,注意检查以下几点:     1、create和destroy过程中是否有inherited语句;    2、TBitmap类型的变量create和free了没有;    3、过程前有没有控件名,例如:TPigHorizontalScroller.MoseMove   判断鼠标是否进入或离开控件的方法: 定义如下的过程:   procedure MouseEnter(var Msg: TMessage); message CM_MOUSEENTER;  procedure MouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;   再在下面编写代码就行了。这个方法用于编写三态按钮很有用,有兴趣的话,(将来)可以到我的主页下载Pig VCL Package(小猪可视化控件包),参考其中的TPigButton控件。(现在还没有,但是将来有了,欢迎大家下载)下载源码 
      

  11.   

    这么简单的问题竟然纠缠到现在,难道没有人这样用过吗,
    一般来说控件内容纳其它控件时,通常将子控件的Owner设置为父控件(构造是指定),Parent当然也是父控件(容器),IDE是根据Owner判断拦截其消息的,只要将子控件的Owner设置为空,就可以在设计时进行操作,如同容器外的其它独立控件一样,前面我已经说过了:“对新生成的控件不要指定Owner(为空),只需要指定parent,但是必须在析构函数中显式的销毁该对象”
      

  12.   

    所谓“不要指定Owner”是否是要将Create(Self)改为Create(nil)?如果是这样,还是不行
      

  13.   

    想应付空间的属性改变:
    你可以看看CM_TEXTCHANGED,CM_FONTCHANGED等类似的消息并相应它们。设计时菜单:
    你可以看看TComponentEditor的帮助文件。主要是定义一系列的动词,然后在回调函数终处理用户的点击。
      

  14.   

    解决了!!!!!
    问题1的解决:重载Loaded过程,在其中输入
    procedure TMacXForm.Loaded;
      var TheWnd: HWND;
    begin
     inherited Loaded;
      if not (csDesigning in ComponentState) and (FForm <> nil) then
       begin
        if FForm <> nil then
         begin
          TheWnd := FForm.Handle;
          FWndProcInstance := MakeObjectInstance(FormWndProc);
          FDefProc := SetWindowLong(TheWnd,GWL_WNDPROC,LongInt(FWndProcInstance));
         end;
      end;
    end;
    其中的定义:
    FForm: TCustomForm;     //就是TCustomForm(Parent)
    FWndProcInstance: Pointer;
    FDefProc: LongInt;
    然后在procedure TMacXForm.FormWndProc(var Message: TMessage);中就会接收到Parent的消息
      

  15.   

    问题2的解决:
    创建控件时并非是像 Lewolf(李狼) 说的那样将Owner设为nil,而是设为IDesignerHook对象的GetRoot,即
    ClientPanel := TMacXFormBody.Create(FForm.Designer.GetRoot);