有关Delphi控件开发讨论, 自认为精通Delphi控件开发及VCL架构的高手请人内发表心得!?(300分)

  • 主题发起人 主题发起人 lorderic
  • 开始时间 开始时间
2. ComponentState属性的用途
除了csDesigning,还有 csLoading 表示组件正在从流中装载属性,有时可用在属性写方法中。
csDestroying 表示组件正在被释放,在组件重绘方法中判断可以避免出现无法绘制Canvas的
错误。
csReading和csWriting 表示组件正在读或写属性。
其它的好象不常用。
 
3.如果有一个属性为指向另一个类的实例(如TDataSource的Dataset属性), 要注意的事项
如果这个类实例也是TComponent派生类,该属性变更时会调用虚拟方法
procedure Notification(AComponent: TComponent; Operation: TOperation); virtual;
override该方法可处理连接变更事件。
 
6.TWinControl中如何封装Windows窗体(或者说, Windows的消息通知(message call), 经过了什么过程, 成为Delphi的Method Call) ? Windows的消息处理机制?
或者另一个问法, 在一个Control中, 要处理消息(message), 除了定义消息函数 (procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;) 外, 尚有那些方式?

除了用message关键字处理特定消息,还有其它方法:
1)TControl中已定义了MouseDown、MouseMove这样的虚拟保护方法,直接override就行,不过只适用于少数消息。
2)直接override TWinControl控件消息处理主过程 WndProc,适合批量处理消息。
3)override在TObject中定义的虚拟方法 DefaultHandler,这是最后被调用的了。

 
7.设计覆合式控件的注意事项
在published中声明一个TComponent属性,只能是链接到另一个组件,而不能由组件自己创建
一个实例拿来发布,如果你要在一个组件中包含另一个组件,一般来说,你需要在组件中声明
有用的属性并用代码连接到子组件中。

很多组件在设计期可创建新的组件到窗体上,这些组件有些使用带多个参数的构造器,一般用
RegisterNoIcon注册,象TField就是一个组件,但并不能放到窗体上。以前见过一个类似于QQ
面板控件的控件,也是用组件编辑器来创建用RegisterNoIcon注册的子控件的。
 
5.TControl与TWinControl的差异

书上都把TGraphicControl和TWinControl描述为TControl的两大分支,其实从代码层来看,
TGraphicControl并没有实现太多东西,人们描述的很多TGraphicControl的功能其实都是
TControl本身的功能,而TGraphicControl和TWinControl的差异大部分也是TControl与
TWinControl的差异。

TControl和TWinControl的联系是相当密切的,从Controls.pas单元中可看得非常清楚。
TWinControl中引入了很多新的属性和方法,而在控件绘制等方面,二者有很多方法都是紧
密相连的。

 
1.VCL控件开发的几个主要基类TComponent、TControl、TWinControl、
TGraphicControl、TCustomControl的主要功能及各类用途!?

TComponent:TComponent类直接由TPersistent类派生。TComponent地独特特征是它地属性能够
在设计浅见通过Object Inspector来控制,能够拥有其他组件。

TControl:TControl类定义了许多属性、方法和事件,一般用于可视控件。TControl本身没有什么
用处,你绝对不能创建它地派生类。大多数DELPHI的控件都是由TControl的派生类TWinControl
和TGraphicControl派生而来的。

TWinControl:标准窗口控件由TWinControl类派生。标准控件是你在大多数windows应用程序中
看到的用户界面。由于DELPHI封装了标准控件的行为,而不是通过WINDOWS API函数来实现它们,
你可以直接使用各种控件提供的属性。TWinControl对象有三个特征:它们有窗口句柄,能够接受输入
焦点,能够成为其他控件的父。

TGraphicControl:与TWinControl不同,TGraphicControl控件没有窗口句柄,因而不能接受输入
焦点。他们也不能是其他控件的父。

TCustomControl:一些TWinControl派生类的名称都是以TCustom开头,如TCustomComboBox、
TCustomEdit等等。自定义控件具有其他TWinControl派生类同样的功能,除了特殊的可视和交互
特征。这些以TCustom开头的派生类可以作为基类派生和创建你自己的自定义的控件。如果你是
组件编写者,你可以通过这些基类给自定义控件提供绘制自己的功能。

 
to duanliandl:
>TControl:TControl类定义了许多属性、方法和事件,一般用于可视控件。TControl本身没有什么
用处,[red]你绝对不能创建它地派生类[/red]。

不要听书上说的,TControl的用处非常大,你一看TGraphicControl的源码就知。
TGraphicControl不过是处理了WM_PAINT消息,用Canvas来绘制罢了,如果你创建的控件不需要用
TCanvas,而使用自己的方法绘制,完全可以从TControl中直接派生!
我在 CnPack 界面控件中就这样做的,上面已放有代码了。
 
[red]同志们,寻幽探密要靠自己啊![/red]
 
to duanliandl:
TCustomControl并不完全是你说的那个意思,它只是在TWinControl的基础上通过覆盖 PaintWindow
方法,引入了一个 Canvas 来允许控件直接绘制表面罢了(通过在 WM_PAINT 处理过程中为 ControlState
增加了 csCustomPaint 以允许自绘制)。就象 TGraphicControl 对 TControl 的扩充一样。
 
8.对Windows基本窗体(Eidt, Button, ListBox....)的了解及说明?

绝大部分对Windows标准窗口控件的封装代码在StdCtrls.pas单元中,一般它们通过覆盖
TWinControl的几个虚拟方法来工作:

1)窗体创建参数方法 CreateParams,如单选框的代码:
procedure TCustomCheckBox.CreateParams(var Params: TCreateParams);
const
Alignments: array[Boolean, TLeftRight] of DWORD =
((BS_LEFTTEXT, 0), (0, BS_LEFTTEXT));
begin
inherited CreateParams(Params);
CreateSubClass(Params, 'BUTTON');
with Params do
begin
Style := Style or BS_3STATE or
Alignments[UseRightToLeftAlignment, FAlignment];
WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
end;
end;
事实上我们在写普通窗体的代码时也经常覆盖该方法来修改窗体属性,实现一些Delphi不
支持的功能。

2)创建窗口方法 CreateWnd,在TWinControl.CreateHandle中调用,控件在此对新创建
的窗口进行初始化,如单选框的代码:
procedure TCustomCheckBox.CreateWnd;
begin
inherited CreateWnd;
SendMessage(Handle, BM_SETCHECK, Integer(FState), 0);
end;

3)处理 CN_COMMAND 消息,该消息由 WM_COMMAND 转换而来,为Windows发给窗口的用户
消息,如按钮的代码:
procedure TButton.CNCommand(var Message: TWMCommand);
begin
if Message.NotifyCode = BN_CLICKED then Click;
end;
 
>假设你设置了property A: Integer read FA write SetA default 10;
>如果没有为 A 赋初值 10,你会在DFM文本中看到保存了属性 A 的值为 0,如果你在设计期刚好
>把 A 置为 10,则DFM中不保存。

那么,你想设置属性为10,最后运行的时候发现是0

〉有时我们把一个Boolean属性设为 default True,而在 Create 中将其初始化为 False,这样可
〉保证组件装载后该属性始终为 False。因为如果设计期为 True,属性不会保存,Create时自动被
〉置为 False,如果设计期为 False,则正好:)

如果你想运行的时候始终为某一个值,应该通过stored false,而不是这点小把戏
 
Pipi.兄教训得好,多谢!

第一点我是想举个反例,不过少写了几句话:)
第二点确实有卖弄之嫌,报歉。
不过我也是想通过它来说明我们不应被已有的框架所局限,应该善于独立思考和联想。
在追求事物本质的基础上,去做出更多的创新,即使错了也可获得大量的经验。

当然,大师级的程序员都已到了化腐朽为神奇的地步,而不是卖弄一些小技巧、小聪明。
唉!什么时候我也能达到反璞归真的境界呢:)

承教了!
 
4.property宣告的具体心得及注意事项、Keyword Default及Stored的意义

default声明只能适用于数值、颜色、布尔等简单类型,如果要处理高级一些的属性,则需要用
Stored 跟一个函数方法(返回Boolean)名,在该方法中进行判断。

事实上,default、Stored的设计应该是为了减少DFM的存储内容,加快属性装载速度,而把一些不
怎么变化的属性在Create中赋初值,从这点上考虑会更容易理解。
 
to Pipi.兄:
前面关于 default 的帖子是我的犯低级错误(已删掉),补充一点使用stored 和布尔函数
搭配使用的方法,希望将功补过:)

例子:
function IsFontStored: Boolean;
...
property Font: TFont read GetFont write SetFont stored IsFontStored;
...

function TMyControl.IsFontStored: Boolean;
begin
Result := (Font.Name <> '宋体') or (Font.Size <> 9) or ...
end;

在 stored 关键字后接一个无参数、返回Boolean值的方法名,当设计期要保存窗体数据流时
会调用该方法来决定是否保存,可用来进行复杂的判断。

更进一步,通过对Classes单元中TFilter、TReader、TWriter和TComponent以及TypInfo单元
的分析,最终可发现,每一个发布属性都对应着一个这样的结构:
PPropInfo = ^TPropInfo;
TPropInfo = packed record
PropType: PPTypeInfo;
GetProc: Pointer;
SetProc: Pointer;
StoredProc: Pointer;
Index: Integer;
Default: Longint;
NameIndex: SmallInt;
Name: ShortString;
end;
里面的GetProc、SetProc对应着属性的读、写方法,而StoredProc则是决定是否存储的方法。
Default就保存着简单类型属性的默认值。

从TWriter.WriteProperty的源码中可看出组件属性写入流中的大部分过程,不过有一点疑问:
代码中在保存简单类型属性时,只将数值与PropInfo.Default进行了判断而没有通过StoredProc
来决定,如子过程中的IsDefaultValue:
procedure WriteOrdProp;
var
Value: Longint;

function IsDefaultValue: Boolean;
begin
if AncestorValid then
Result := Value = GetOrdProp(Ancestor, PropInfo) else
Result := Value = PPropInfo(PropInfo)^.Default;
end;

begin
Value := GetOrdProp(Instance, PropInfo);
if not IsDefaultValue then
begin
WritePropPath;
case PropType^.Kind of
tkInteger:
WriteIntProp(PPropInfo(PropInfo)^.PropType^, Value);
tkChar:
WriteChar(Chr(Value));
tkSet:
WriteSet(Value);
tkEnumeration:
WriteIdent(GetEnumName(PropType, Value));
end;
end;
end;
而TWriter在保存TPersistent属性时,调用了WriteProperties方法,该方法调用了
IsStoredProp这个汇编过程来访问PropInfo.StoredProc过程以决定是否保存。

[red]问题:[/red]为什么我们对简单类型属性使用 stored IsXXXStored 这样的代码时,TWriter
也能够正确地判断是否要保存呢??

 
to yygw
你再看呼叫WriteProperty的WriteProperties, 就可以明白了
 
多谢,看来我还得再多花些时间来分析代码才行:)
有哪位师兄把System、Classes、SysUtils、Graphics、Controls等这几个核心单元全部看完了吗??
 
to lorderic:
看了你答的几个问题,确实对VCL底层有相当深入的研究,小弟佩服:)
看来我要再练两年才出来混了。
 
上面的代码我懒的看,关于vcl也不是一篇文章就可以讲明白的,建议去看有关的书籍。
或到网上下载此类资料。
 
我想将一个自己做的类封装成ActiveX控件,但这个控件是从TGraphicControl中继承的,显然不行,我在控件中用到了Canvas的许多属性,从TWinControl类继承的话,TWinControl类没有Canvas,我该怎么办,请各位高手指教。
 
to zq67:
把它改由TCustomControl中繼承即可!
TCustomControl是繼承自TWinControl, 多加了Canvas
繼承自TWinControl後, 唯一要考慮的是, 要不要讓Control有GetFocus的能力;
預設TWinControl的TabStop是 False,而且點擊WinControl也無法使control GetFocus,
如果有需要GetFocus的話, 要由自己寫代碼處理
 

Similar threads

D
回复
0
查看
2K
DelphiTeacher的专栏
D
D
回复
0
查看
1K
DelphiTeacher的专栏
D
D
回复
0
查看
2K
DelphiTeacher的专栏
D
D
回复
0
查看
1K
DelphiTeacher的专栏
D
D
回复
0
查看
1K
DelphiTeacher的专栏
D
后退
顶部