Options 的 Linker 页次中将『Generate console application』选项打开,你就可以像以前使用 WinCrt 单元一样地写程序了!
9.3 自制组件时该从哪个类别继承?
VCL 中有一些『自订』类别,而且有许多控件是直接由这些『自订』类别继承下来的。例如 TMemo 直接继承自 TCustomMemo类别。这些自订类别写好了所有该控件所拥有的功能,只是没有将属性公开出来而己。大部分情形下,你应该从那些自订类别继承而不是控件类别。
如果你要从头撰写自己的组件,那么从 TCustomControl 类别继承是个不错的主意。撰写出来的组件会具有 Window Handle 且可以接受输入焦点。
另外根据你的需要也可以从这些类别继承:
TGraphicControl:视觉组件,但是没有window handle,也不能接受输入焦点。
TComponent:不可视组件,你没办法在执行时期看到它。
TWinControl:将已存在的窗口组件包装起来,如Windows标准控件或VBX组件。
-------------------------------------------------------------------------------
第十部分 进阶程序设计技巧
10.1 Delphi 有与 C++ 一样的 I/O Stream 类别吗?
答案可以说有也可以说没有。Delphi允许你建立自己的『文字文件驱动程序』,它可以让你使用Delphi 标准的 I/O 函式库来处理非标准的 I/O,如处理 UNIX 格式的文字文件或处理 Socket 所取得的资料。虽然没有像 C++ 的 I/O Stream 类别那么强大但应该也足够一般用途使用了。
建立『文字文件驱动程序』的方法在『Object Pascal Language Guide』中有明述。此 外你也可以参考 VCL 的 Printer 单元。
Delphi有 TStream 类别,不过是设计用来将对象写入资料流的,不像 C++ 的 I/O Stream 类别那么具有弹性。
10.2 如何取得列举型态变量的文字表示?
使用 TypInfo单元中的 GetEnumName 函式:
type
TMyType = (Value1, Value2);
var
TypeValue: TMyType;
begin
Writeln (GetEnumName(TypeInfo(TMyType), Ord(TypeValue));
end;
TypInfo单元中还有许多与型别信息有关的函式。
『Secrets of Delphi 2.0』这本书有许多关于TypInfo单元的信息,值得参考。
--------------------------------------------------------------------------------
第十一部分 组件虚拟方法
11.1 如何得知组件的window handle是何时建立的?
控件的 window handle 是在 CreateWnd 方法中建立的。如果你想要在建立 window handle 后接着做某些动作那么你应该改写 CreateWnd 方法:
procedure TMyClass.CreateWnd;
begin
// 现在还没取得 window handle
inherited CreateWnd;
// 呼叫 inherited 以取得 window handle
// 在这里撰写你想要执行的动作
end;
11.2 如何得知是否表格上所有组件都已加载完成?
Loaded 方法是在加载完成后接着被呼叫的。
procedure TMyClass.Loaded;
begin
inherited Loaded;
// 将ComponentState中的 csLoading 状态清除
// 在这里撰写你想要执行的动作
end;
11.3 在哪里绘制组件最适合?
你应该拦截 WM_PAINT 窗口讯息然后利用 Canvas 来绘制组件。然而 VCL 己经帮你拦 截好了,你只须改写组件的 Paint 方法即可。
procedure TMyClass.Paint;
begin
// 如果你的组件是己存在的组件继承下来的,那么必须在这里呼叫 inherited Paint
inherited Paint ;
// 在这里撰写你想要执行的动作
end;
11.4 如何改变组件的窗口式样?
CreateParams方法用来设定组件的窗口式样及其它必须传递至 CreateWindowEx API 的 参数。要改变组件的窗口式样,例如增加或拿掉组件的垂直滚动条只要改写 CreateParams 方法:
procedure TMyControl.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if IWantAScrollBar then
Params.Style := Params.Style or WS_VSCROLL
else
Params.Style := Params.Style and not WS_VSCROLL;
end;
--------------------------------------------------------------------------------
第十二部分 Windows API
12.1 组件卷动时闪动的很厉害,如何克服这种情况?
要卷动组件本身最简单的方法就是改变它的坐标然后重画组件,但是这方法会导致组件闪动的很厉害。
比较好的方法是呼叫 ScrollWindow 或 ScrollWindowEx Windows API。
闪动的另一个原因可能来自于 WM_PAINT 及 WM_ERASEBKGND。你可以试着拦截 WM_ERASEBKGND 及 WM_PAINT 讯息然后自己处理绘图动作,包括绘制背景的动作,或许可以改善闪动的情况。
12.2 如何重新激活Windows?
使用 ExitWindowsEx Windows API。
12.3 如何快速大量地更改组件资料?
在进行大量资料更改前后,利用 WM_SETREDRAW 讯息来控制你的组件暂时不要重画,这不但可以使资料设定速度增快也防止组件闪烁的情况。
--------------------------------------------------------------------------------
第十三部分 控件边框
13.1 为什么我的组件的 Ctl3D 属性设为 True 之后,它依然没有 3D 的边框呢?
如果 ControlStyle 属性内没有包含 csFramed 旗帜那么 Ctl3D 属性就会没有作用。在组件 的建构函式内加上:
ControlStyle := ControlStyle + [csFramed];
13.2 如何实作 BorderStyle 属性?
在控件设定有没有边框之后要重新建立 window handle:
FBorderStyle: TBorderStyle;
procedure SetBorderStyle(Style: TBorderStyle);
property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle;
procedure CreateParams(var Params: TCreateParams); override;
procedure TMyControl.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if FBorderStyle = bsSingle then
Params.Style := Params.Style or WS_BORDER
else
Params.Style := Params.Style and not WS_BORDER;
end;
procedure TMyControl.SetBorderStyle(Style: TBorderStyle);
begin
if Style <> FBorderStyle then
begin
FBorderStyle := Style;
// 重新建立window handle
RecreateWnd;
end;
end;
--------------------------------------------------------------------------------
第十四部分 控件式样
14.1 当组件重绘时如何防止闪动的情况?
如果组件的 ComponentStyle 属性没有包含 csOpaque 旗帜的话,呼叫 Invalidate方法时 会导致组件的背景先被擦掉再重绘。如果你在 Paint 方法中绘制背景,那你应该在组件的建构函式中加上:
ComponentStyle := ComponentStyle + [csOpaque];
Max Nilson的回答:
引起闪动另一个原因可能是 WM_ERASEBKGND 讯息的处理。当 VCL 控件收到一个 WM_ERASEBKGND 讯息时,它会将组件的背景擦掉然后设定成预设的颜色。如果你的组件衍生自 TWinControl,而且组件的颜色与背景颜色不同(例如图形),每次重画以前都会将组件先清成背景颜色再重绘,这就是造成闪动的原因了!
解决的方法不难,你必须告诉 Windows 你要自行解决『所有的』绘图动作。不过有一个前提是,你一定要确定你的 Paint 方法将整个组件都画过,如果你漏了什么地方忘了画,那个部分的资料会由随机数组成,你能想见这情况吗?使用这个方法可以加速你的组件绘制动作(稍微快一点点),因为少了一个填满背景颜色的动作。
type
TMyComponent = class (TWinControl)
...
protected
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
...
end;
procedure TBMyComponent.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
// 不要重绘背景,这会造成组件闪动
Message.Result := 0
end;
--------------------------------------------------------------------------------
第十五部分 窗口讯息
15.1 为什么我的组件得不到方向键的讯息?
你必须拦截 WM_GETDLGCODE 才能处理方向键的讯息,在 WM_GETDLGCODE 的讯息处理 者中传回 DLGC_WANTARROWS。如果你不这样做,那方向键的功用就只能用来移动窗口焦点而己。
Max Nilson 的回答:
想要你的组件能够处理方向键,你必须要拦截 CM_WANTSPECIALKEY 组件讯息。 CM_WANTSPECIALKEY 组件讯息提供你比拦截 WM_GETDLGCODE 窗口讯息更容易且灵活的判断方法来决定是否需要某些特殊键的讯息。当控件收到任何一个特殊键时就会送出CM_WANTSPECIALKEY 组件讯息给控件。
特殊键包括:VK_TAB、VK_LEFT、VK_RIGHT、VK_UP、VK_DOWN、VK_RETURN、VK_EXECUTE 、VK_ESCAPE 及 VK_CANCEL。如果讯息传回值是非零值,这个键就会被送至 KeyPress 方法以供处理,否则这个键的讯息会被送至组件的父控件,以预设方式来处理。
一个简单的范例:
type
TMyComponent = class (TWinControl)
...
protected
procedure CMWantSpecialKey(var Message: TCMWantSpecialKey); message CM_WANTSPECIALKEY;
...
end;
procedure TMyComponent.CMWantSpecialKey(var Message: TCMWantSpecialKey); begin
inherited;
// 我们只想处理向左方向键,其它的特殊键都给 Windows 处理
if Message.CharCode = VK_LEFT then
Message.Result := 1;
end;
CM_WANTSPECIALKEY 组件讯息比 WM_GETDLGCODE 讯息更具有弹性的地方在这儿。我们甚至可以根据是按下的是哪个特殊键才决定是否处理这个键。例如,你的控件有三张影像,你可以让使用者利用左右方向键来回检视它们,如果翻到最后一张影像再按向右键时,焦点就让它离开组件,剩下的全部都让 Delphi 来处理。
15.2 有没有与 Visual Basic『DoEvents』同样功能的函式?
有。Application.ProcessMessages方法。
关键词:Delphi 组件撰写常问问题