怎样编写自己的 VCL 控件
用过 Delphi 的朋友们 , 大概对 Delphi 的最喜欢 Delphi 的不是他的强类型的 pascal 语法 , 而是强大的 VCL 控件,本人就是一位 VCL 控件的爱好者。
VCL 控件的开源,给我们带来了享之不尽的好处。不像以前的 ole 控件以及 ActiveX ,你完全可以重写 Delphhi 标准控件,而且网上这方面的资源很多。
关于如何编写 VCL 控件 , 和多 Delphi 的书籍里都有介绍 , 在此简单的说明一下。
在 Delphi 中, TComponent 是所有 VCL 构件的基础。编写非可视构件以便从 TComponent 派生。 Delphi 提供了若干专门用于制作控件(可视构件)的基本构件类型,都是从 TControl 和 TWinControl 派生而来。其派生关系如下:
TControl --- TGraphicControl --- TCustomLabel
TWinControl -- TCustomControl --- TCustomGrid
--- TButtonControl -- TCustomGroupBox
--- TScrollingWinControl -- TCustomPanel
--- TCustomComboBox
--- TCustomEdit
--- TCustomListBox
TControl 的子类型用于非窗口式控件, TWinControl 的子类型 则用于窗口式控件。除非特殊需要,一般不直接从 TControl 和 TWinControl 派生新控件,而是从其子类型派生。这样可以充分利 用原有的属性、事件和方法,减少很多工作量。
编写控件,首先要选择适当的 VCL 对象类型作为父对象,以派生新的对象。
下面给出一个具体的例子 , 来实现一个增强型的 Edit 输入框,是他可以根据输入的数据类型对输入字符进行有效性判断,增加类似于 TLabel 一样的左对齐、中对齐、右对齐功能。
通过 Delphi 的构件向导从 TEdit 继承生成自己的控件:
TEditDataType = (dtpString, dtpInteger, dtpFloat); // 输入的数据类型
TMyEdit=class(TEdit)
private
FAlignment: TAlignment;
FDataType: TEditDataType;
Fprecision: Integer;
procedure SetAlignment(const Value: TAlignment); ?? // 用来设置对齐方式方法
procedure SetPrecision(const Value: Integer); ????????????
procedure SetDataType(const Value: TEditDataType);
function GetAsFloat: double;
function GetAsInteger: Integer;
procedure SetAsFloat(const Value: double);
procedure SetAsInteger(const Value: Integer);
function GetAsText: String;
procedure SetAsText(const Value: String);
protected
procedure CMExit(var Message: TCMExit); message CM_EXIT; // 重写消息 , 下一篇将详细介绍消息
procedure KeyPress(var Key: Char); override; // 重写键盘按键事件,用来控制键盘输入
public
constructor Create(AOwner: TComponent); override;
procedure CreateParams(var Params: TCreateParams); override;// 重写,实现左右对其
property Alignment: TAlignment read FAlignment write SetAlignment default
taLeftJustify; // 对齐方式
property DataType: TEditDataType read FDataType write SetDataType default
dtpString; // 设置输入的数据类型
published
property Precision: Integer read Fprecision write SetPrecision; // 小数点有效位数 property AsFloat :double read GetAsFloat write SetAsFloat; // ? 增加类似于 C++ 一样 的类型转换函数,不用每一次自己调用函数转换
property AsInteger:Integer read GetAsInteger write SetAsInteger;
property AsString :String read GetAsText write SetAsText;
end;
// 下面我们来一个一个实现我们想要实现的功能。
第一个实现左对其必须重写 CreateParams 函数 , 具体实现如下:
procedure TMyEdit.CreateParams(var Params: TCreateParams);
const
Alignments: array[Boolean, TAlignment] of DWORD =
((SS_LEFT, SS_RIGHT, SS_CENTER), (SS_RIGHT, SS_LEFT, SS_CENTER));
begin
inherited CreateParams(Params);
CreateSubClass(Params, 'EDIT');
with Params do
begin
Style := Style or SS_NOTIFY or
Alignments[UseRightToLeftAlignment, FAlignment];
end;
end;
// 设置对齐方式
procedure TMyEdit.SetAlignment(const Value: TAlignment);
begin
if FAlignment <> Value then
begin
FAlignment := Value;
RecreateWnd; // 重画控件
end;
end;
根据所设定的数据类型控制输入的字符串 , 重写 KeyPress 事件
procedure TlsCustomEdit.KeyPress(var Key: Char);
begin
case FDataType of
dtpInteger: // 如果是整数
begin
if not (Key in ['0'..'9', '-', #8, #13, #35, #39]) then
Key := #0;
if (Key = '-') and ((Pos('-', Text) > 0) or (SelStart <> 0)) then
Key := #0;
end;
dtpFloat: // 如果是符点数
begin
if not (Key in ['0'..'9', '.', '-', #8, #13, #35, #36, #37, #39]) then
Key := #0;
if (Key = '.') and (Pos('.', Text) > 0) then
Key := #0;
if (Key = '-') and ((Pos('-', Text) > 0) or (SelStart <> 0)) then
Key := #0;
end
end;
inherited KeyPress(Key); ? // 调用父类的 KeyPress 方法
end;
号了 , 现在这个控件就差不多了 .
关于简单控件的编写 , 一般只需要从 Delphi 为你准备好的基本构件类型继承下来 , 然后重写父类的一些方法 , 添加自己的一些方法 , 以及属性就可以了 . 你还可以学 Delphi 一样写出一些抽象构件类型来 , 比如说上面的 TMyEdit, 你可以先写一个基本的 TCustomMyEdit 然后 , 从他继承 , 引伸出更多控件出来 . ? 基本构件类型的编写 , 你得高瞻远瞩 , 让你的基本类型足够抽象 . 在这里 , 你所学的 OOP 的思想就可以在此大展身手了 . 习惯上 , 基本构件类型 , 不需要 published.
在制作构件时,有要接触到很多类和很多的属性和方法,在这里,列出了一些比较重要而且比较常用的类和它的属性和方法。
TComponent
ComponentState 属性,标志构件正处于的状态,如编辑,调入,读取等。可以跟据状态来决定当前构件要做的事情;
Notification 方法:当一个构件被删除,加入时,会使用这一个方法通知所有的构件(除了被删除或加入的那一个构件)。这一个方法有两个参数: AComponent , Operation 。进入的参数通知构件现在对哪一个构件进行操作,进行什么样的操作。使用时,在新的构件里覆盖掉 Notification 方法,在 Notification 方法里写上接收到通知的代码就可以了;在做一些容器控件 , 控件关联 , 如 DB 构件时很有用 . 否则 , 你删除时会有一个地址错误 .
Loaded 方法:当一个构件载入完毕后,该方法被调用。与 Notification 方法一样,使用时,在新的构件里覆盖掉 Loaded 方法,然后在 Loaded 方法里写上载入完毕的代码就可以了;
Updating 方法:当一个构件正在被改变时,被调用;
Updated 方法:当一个构件改变完毕时,被调用。
TGraphicControl
Canvas 属性:画布,图形构件必须有的属性;
Left, Top, Width, Height 属性:在父构件里显示出来的大小;
ControlState 属性:控制状态,如鼠标按下,是否接受调色板更改信息,是否接受焦点等;
ControlStyle 属性:控制类型,如是否透明,是否有 3D Frame 等等;
Paint 方法:该方法在接受到 WM_PAINT 消息后就被调用,画出自己;
VCL 的初步学习一般还算容易,和任何计算机语言一样,当你需要更深入的时候,进展就比较慢了,所需要的知识是越来越多。就 VCL 的编写一些难点列举一二:一个是系统消息几 Windows API 。你需要写一个比较复杂的控件,你需要对系统消息比较熟悉。另一个是属性编辑器以及组件编辑器的编写。别看 Delphi 中 VCL 有源码,但不是全部的,就属性编辑器来说,想找到一些很有参考价值的,很难。本人将在接下来的篇章中慢馒和读者探讨。