在 delphi 下自定义通用对话框
--------------自定义打开文件对话框
几次碰到有人在论坛里问如何在 delphi 下自定义通用对话框,本人对此问题也比较感兴趣,所以抽点时间搞了下,现在把“成果”与大家分享。本文的题目大了点,通用对话框有好多,但这里只以打开文件对话框为例。其实自定义所有通用对话框的原理是一样的。
第一步:建立对话框模版
先来说个概念:对话框模版。对话框模版是一种资源,在 .rc 文件中定义,编译后生成 .res 文件,最终一般存在于资源动态链接库(dll)中或可执行程序中。在专业的共享软件中一般都大量使用模版来创建对话框。通过模版生成的对话框一般用来采集用户输入,上面可以放标准的 windows 控件,比如 button, label, textbox, listbox, listview, treeview 等。
通用对话框也是由对话框模版生成的窗体,只不过这些对话框模版由操作系统定义,自定义通用对话框就是通过更改这些模版来实现的(打开和保存文件对话框例外,它们是通过添加新的模版来自定义的)。所以第一步要知道怎样定义对话框模版,可以在 notpad 里直接敲 .rc 文件(这种方法这里就不使用了),还可以使用现有的工具,我机器上最好的工具是 visual studio .net ide,只需要点几下鼠标即可。(用它也可以查看、修改可执行文件中的资源,直接点打开->文件,打开可执行文件即可)。现在就先在 vs.net 中定义一个对话框模版(过程略),该模版就是我们在打开文件对话框上自定义的部分,需要注意的是该模版必须具有 ds_3dlook 、ds_control 、ws_child 、ws_clipsiblings 风格且不能有 border,因为通用对话框是将我们的整个模版当作子窗体subclass 到原有对话框的(类似 button 等标准控件与其拥有者的关系)。我将对话框模版的外观和 .rc 文件的内容贴出来:

// .rc 文件内容
131 dialogex 0, 0, 282, 36
style ds_setfont | ds_3dlook | ds_fixedsys | ds_control | ws_child |
ws_clipsiblings
font 8, "ms shell dlg", 400, 0, 0x1
begin
ltext "文件名称:",-1,7,8,40,8
ltext "此静态控件用来显示文件名称",1004,51,8,224,8,
ss_pathellipsis
control "如果选中文件是图片文件则进行预览",1005,"button",
bs_autocheckbox | ws_tabstop,7,18,268,10
end
上面对话框模版中的 checkbox 没有任何实际意义,只用来说明功能。将此 .rc 文件编译成 .res 文件(可以用 vs.net 直接编译,也可以用 delphi 带的 brcc32 工具)以备后面例子使用。
第二步:继承 topendialog 类
调用打开文件对话框只需一个 api:getopenfilename,这个 api 需要一个 openfilename 结构的参数,自定义对话框时将该结构的 lptemplatename 成员指定为对话框模版的标识(identifier)并在 flags 成员中包含 ofn_enabletemplate 常数即可。
对话框模版及其包含的每个控件都应该有自己唯一的标识,而且这些标识不能与通用对话框上原有控件的标识重复。标识有2种:字符串标识和数字标识,在本例中使用数字标识:对话框模版的标识为 131,第一个静态控件的标识为 -1,第二个静态控件的标识为 1004,checkbox 的标识为 1005。(标识为 -1 的控件一般为内容固定不变的静态控件。) openfilename 结构的 lptemplatename 成员的类型是 null-terminated 字符串指针,如果对话框模版的标识为字符串,则可直接赋值,比如某模版的标识为 idd_mydialog,那么赋值语句为:lptemplatename := pchar('idd_mydialog'),如果为数字,比如本例中的模版的标识为 131,赋值语句则为:lptemplatename := windows.makeintresource(131)。
delphi 中所有通用对话框类都继承自 tcommondialg 抽象类,在这个抽象类中定义了个受保护的(protected)属性:template,类型为 pchar。这个属性就是用来存放模版标识的,但遗憾的是我们不能在除它的继承类以外的地方访问到它(因为受保护)。所以我们需要在它的孙子类中重新定义一公有(public)属性来间接访问它。
在 delphi 里新建一个 vcl 类 tmyopendialog 继承自 topendialog 类,定义一公有属性 templateres ,通过其写入方法为受保护的 template 属性赋值,下面列出代码:
//********************************************
// myopendialog.pas
// tmyopendialog 类实现自定义打开文件对话框
// by: joe huang date: 2004-01-05
//********************************************
unit myopendialog;
interface
uses
sysutils, classes, dialogs, windows, messages, commdlg;
type
tcommandevent = procedure (controlid: word) of object;
tmyopendialog = class(topendialog)
private
{ private declarations }
ftemplateres: pchar;
foncommand: tcommandevent;
procedure settemplateres(const value: pchar);
protected
{ protected declarations }
procedure wndproc(var message: tmessage); override;
public
{ public declarations }
//该属性用来指定自定义模版的标识
property templateres: pchar read ftemplateres write settemplateres;
published
{ published declarations }
property oncommand: tcommandevent read foncommand write foncommand;
end;
procedure register;
implementation
procedure register;
begin
registercomponents('samples', [tmyopendialog]);
end;
{ tmyopendialog }
procedure tmyopendialog.settemplateres(const value: pchar);
begin
ftemplateres := value;
self.template := value;
end;
procedure tmyopendialog.wndproc(var message: tmessage);
begin
message.result := 0;
if (message.msg = wm_command) then
begin
if assigned(foncommand) then
foncommand(message.wparamlo);
end;
inherited wndproc(message);
end;
end.
tmyopendialog 类还定义了一个事件用来捕获对话框模版上控件状态发生的改变(后面说明此事件的用法)。将该类注册到组件面板的 samples 页中。
第三步:建立工程实现自定义对话框
新建一工程保存至目录,将第一步中的 .res 文件放至该目录中并加入到工程文件中。从 samples 页中把我们的新控件拖入 form1 中,名字为 myopendialog1,再在 form1 上放入 button1,button1 的 on_click 事件代码如下:
procedure tform1.button1click(sender: tobject); begin myopendialog1.templateres := windows.makeintresource(131); myopendialog1.execute; end;
myopendialog1 的 on_show 事件代码如下:
procedure tform1.myopendialog1show(sender: tobject); begin //标识为 1004 的控件为第二个静态控件,用来显示选择的文件全名 //此事件一般用来初始化模版上的控件内容和状态 setdlgitemtext(myopendialog1.handle, 1004, '初始化...'); end;
myopendialog1 的 on_selectionchange 事件代码如下: