分享
 
 
 

在 Delphi 下自定义通用对话框------自定义打开文件对话框

王朝delphi·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

在 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 事件代码如下:

procedure TForm1.MyOpenDialog1SelectionChange(Sender: TObject);

begin

//在第二个静态控件上显示选择的文件全名

SetDlgItemText(MyOpenDialog1.Handle, 1004,

PChar(MyOpenDialog1.FileName));

end;

MyOpenDialog1 的 On_Command 事件代码如下:

procedure TForm1.MyOpenDialog1Command(ControlID: Word);

var

hCtrl: HWND;

begin

if ControlID = 1005 then //模版上的 CheckBox 控件,当状态发生改变时触发此事件

begin

hCtrl := GetDlgItem(MyOpenDialog1.Handle, ControlID);

if hCtrl <= 0 then Exit;

if SendMessage(hCtrl, BM_GETCHECK, 0, 0) = BST_CHECKED then

ShowMessage('You checked me.')

else

ShowMessage('You unchecked me.');

end;

end;

运行后效果见下图:

小结

本例中的自定义部分默认的放在了对话框原有部分的下面,那么如何把对话框中自定义部分放在其它位置呢?其实这个工作不是在代码里做的,而是在第一步创建模版的时候。方法是在模版上放置一个不显示的静态控件(从 MSDN 带的例子来看可以放多个这样的控件),并且这个静态控件的标识必须为 stc32 ,即 0x045F 。通用对话框通过模版上其他控件相对于这个静态控件的位置来定位自定义部分相对于原有部分的位置,模版中位于 stc32 静态控件上方和左边的控件将被安放在通用对话框原有部分的上方和左边,同理,位于 stc32 静态控件下方和右边的控件将被安放在通用对话框原有部分的下方和右边。( All new controls above and to the left of the stc32 control are positioned the same amount above and to the left of the controls in the default dialog box. New controls below and to the right of the stc32 control are positioned below and to the right of the default controls. In general, each new control is positioned so that it has the same position relative to the default controls as it had to the stc32 control. )

再说说我个人认为 Delphi 封装通用对话框不够完美的地方,一般来说通用对话框都有一个父窗体(Parent Window)或拥有者(Owner),拿打开文件对话框来说:OPENFILENAME 结构的 hwndOwner 成员指定其父窗体或拥有者的句柄,但 Delphi 并没有让我们来指定,而是将它指定为 Application.Handle,其他通用对话框也都是这样。

MSDN 中有个 C++ 的完整例子,也是自定义打开文件对话框,很短也很容易看懂,它的对话框模版涉及到定位,可在下面位置找到该例子:

User Interface Design and Development

└User Interface Design & Usability

└Technicle Articles

└Using the Common Dialogs Under Windows 95

本文参考了 MSDN 以下部分:

User Interface Design and Development

└Windows Management

└User Input

└Common Dialog Box Library

User Interface Design and Development

└Windows Management

└Windowing

└Dialog Box

The end. 作者:Joe Huang 欢迎来 Email 讨论:happyjoe@21cn.com 时间:2004年1月5日 6:01 PM

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有