简介
在这篇文章中,我将大概的从Delphi XE2 的Dialogs单元入手,分析ShowMessage,MessageBox等对话框运行原理,希望能帮助你理解Delphi,不求你爱上她,只求让你能快速地解决问题。
跟踪代码
为了了解这些对话框的运行原理,我们需要跟踪进源代码中去,为此,你需要做如下设置
1. 简单创建一个使用了ShowMessage的VCL应用程序
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(Edit1.Text);
MessageBox(Self.Handle,PChar(Edit1.Text),PChar(Application.Title),
MB_ICONINFORMATION or MB_OK);
MessageDlg(Edit1.Text,mtInformation,[mbOK,mbCancel],0);
end;
end.
DFM文件代码:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 243
ClientWidth = 472
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Edit1: TEdit
Left = 128
Top = 72
Width = 209
Height = 21
TabOrder = 0
TextHint = 'Message here'
end
object Button1: TButton
Left = 192
Top = 120
Width = 75
Height = 25
Caption = 'Message box'
TabOrder = 1
OnClick = Button1Click
end
end
2. 在29行里设置一个断点, 再在Edit里输入一些内容,按下Message Box按钮, 按F7跟踪到Dialogs单元, 经过一段时间的仔细跟踪, 你会发现程序运行到下面一段代码:
function MessageDlgPosHelp(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Longint; X, Y: Integer;
const HelpFileName: string): Integer;
begin
if (Win32MajorVersion >= 6) and UseLatestCommonDialogs and ThemeServices.ThemesEnabled then
Result := DoTaskMessageDlgPosHelp('', Msg, DlgType, Buttons,
HelpCtx, X, Y, HelpFileName)
else
Result := DoMessageDlgPosHelp(CreateMessageDialog(Msg, DlgType, Buttons),
HelpCtx, X, Y, HelpFileName);
end;
函数MessageDlgPosHelp指出, 如果当前系统是Vista,sever2008或以上版本的系统,那就调用DoTaskMessageDlgPosHelp函数进行对话框显示, 否则调用DoMessageDlgPosHelp显示对话框. 继续跟踪DoTaskMessageDlgPosHelp函数, 你会发现如下一段代码:
function TCustomTaskDialog.DoExecute(ParentWnd: HWND): Boolean;
const
CTaskDlgFlags: array[TTaskDialogFlag] of Cardinal = (
TDF_Enable_Hyperlinks, TDF_Use_Hicon_Main,
tdf_Use_Hicon_Footer, TDF_ALLOW_DIALOG_CANCELLATION,
TDF_USE_COMMAND_LINKS, TDF_USE_COMMAND_LINKS_NO_ICON,
TDF_EXPAND_FOOTER_AREA, TDF_EXPANDED_BY_DEFAULT,
TDF_VERIFICATION_FLAG_CHECKED, TDF_SHOW_PROGRESS_BAR,
TDF_SHOW_MARQUEE_PROGRESS_BAR, TDF_CALLBACK_TIMER,
TDF_POSITION_RELATIVE_TO_WINDOW, TDF_RTL_LAYOUT,
TDF_NO_DEFAULT_RADIO_BUTTON, TDF_CAN_BE_MINIMIZED);
CTaskDlgCommonButtons: array[TTaskDialogCommonButton] of Cardinal = (
TDCBF_OK_BUTTON, TDCBF_YES_BUTTON, TDCBF_NO_BUTTON,
TDCBF_CANCEL_BUTTON, TDCBF_RETRY_BUTTON, TDCBF_CLOSE_BUTTON);
CTaskDlgDefaultButtons: array[TTaskDialogCommonButton] of Integer = (
IDOK, IDYES, IDNO, IDCANCEL, IDRETRY, IDCLOSE);
var
LWindowList: TTaskWindowList;
LModalResult: Integer;
LRadioButton: Integer;
LFlag: TTaskDialogFlag;
LFocusState: TFocusState;
LVerificationChecked: LongBool;
LTaskDialog: TTaskDialogConfig;
LCommonButton: TTaskDialogCommonButton;
begin
if Win32MajorVersion < 6 then
raise EPlatformVersionException.CreateResFmt({$IFNDEF CLR}@{$ENDIF}SWindowsVistaRequired, [ClassName]);
if not ThemeServices.ThemesEnabled then
raise Exception.CreateResFmt({$IFNDEF CLR}@{$ENDIF}SXPThemesRequired, [ClassName]);
{$IF NOT DEFINED(CLR)}
FillChar(LTaskDialog, SizeOf(LTaskDialog), 0);
{$IFEND}
with LTaskDialog do
begin
// Set Size, Parent window, Flags
cbSize := SizeOf(LTaskDialog);
hwndParent := ParentWnd;
dwFlags := 0;
for LFlag := Low(TTaskDialogFlag) to High(TTaskDialogFlag) do
if LFlag in FFlags then
dwFlags := dwFlags or CTaskDlgFlags[LFlag];
// Set CommonButtons
dwCommonButtons := 0;
for LCommonButton := Low(TTaskDialogCommonButton) to High(TTaskDialogCommonButton) do
if LCommonButton in FCommonButtons then
dwCommonButtons := dwCommonButtons or CTaskDlgCommonButtons[LCommonButton];
// Set Content, MainInstruction, Title, MainIcon, DefaultButton
if FText <> '' then
pszContent := {$IFNDEF CLR}PWideChar{$ENDIF}(WideString(FText));
if FTitle <> '' then
pszMainInstruction := {$IFNDEF CLR}PWideChar{$ENDIF}(WideString(FTitle));
if FCaption <> '' then
pszWindowTitle := {$IFNDEF CLR}PWideChar{$ENDIF}(WideString(FCaption));
if tfUseHiconMain in FFlags then
hMainIcon := FCustomMainIcon.Handle
else
begin
if FMainIcon in [tdiNone..tdiShield] then
pszMainIcon := LPCWSTR(CTaskDlgIcons[FMainIcon])
else
pszMainIcon := LPCWSTR(MakeIntResourceW(Word(FMainIcon)));
end;
nDefaultButton := CTaskDlgDefaultButtons[FDefaultButton];
// Set Footer, FooterIcon
if FFooterText <> '' then
pszFooter := {$IFNDEF CLR}PWideChar{$ENDIF}(WideString(FFooterText));
if tfUseHiconFooter in FFlags then
hFooterIcon := FCustomFooterIcon.Handle
else
begin
if FFooterIcon in [tdiNone..tdiShield] then
pszFooterIcon := LPCWSTR(CTaskDlgIcons[FFooterIcon])
else
pszFooterIcon := LPCWSTR(MakeIntResourceW(Word(FFooterIcon)));
end;
// Set VerificationText, ExpandedInformation, CollapsedControlText
if FVerificationText <> '' then
pszVerificationText := {$IFNDEF CLR}PWideChar{$ENDIF}(WideString(FVerificationText));
if FExpandedText <> '' then
pszExpandedInformation := {$IFNDEF CLR}PWideChar{$ENDIF}(WideString(FExpandedText));
if FExpandButtonCaption <> '' then
pszCollapsedControlText := {$IFNDEF CLR}PWideChar{$ENDIF}(WideString(FExpandButtonCaption));
// Set Buttons
cButtons := FButtons.Count;
if cButtons > 0 then
pButtons := FButtons.Buttons;
if FButtons.DefaultButton <> nil then
nDefaultButton := FButtons.DefaultButton.ModalResult;
// Set RadioButtons
cRadioButtons := FRadioButtons.Count;
if cRadioButtons > 0 then
pRadioButtons := FRadioButtons.Buttons;
if not (tfNoDefaultRadioButton in FFlags) and (FRadioButtons.DefaultButton <> nil) then
nDefaultRadioButton := FRadioButtons.DefaultButton.ModalResult;
// Prepare callback
{$IF DEFINED(CLR)}
pfCallBack := @CallbackProc;
{$ELSE}
lpCallbackData := LONG_PTR(Self);
pfCallback := @TaskDialogCallbackProc;
{$IFEND}
end;
LWindowList := DisableTaskWindows(ParentWnd);
LFocusState := SaveFocusState;
try
Result := TaskDialogIndirect(LTaskDialog, {$IFNDEF CLR}@{$ENDIF}LModalResult,
{$IFNDEF CLR}@{$ENDIF}LRadioButton, {$IFNDEF CLR}@{$ENDIF}LVerificationChecked) = S_OK;
FModalResult := LModalResult;
if Result then
begin
FButton := TTaskDialogButtonItem(FButtons.FindButton(FModalResult));
FRadioButton := TTaskDialogRadioButtonItem(FRadioButtons.FindButton(LRadioButton));
if LVerificationChecked then
Include(FFlags, tfVerificationFlagChecked)
else
Exclude(FFlags, tfVerificationFlagChecked);
end;
finally
EnableTaskWindows(LWindowList);
SetActiveWindow(ParentWnd);
RestoreFocusState(LFocusState);
end;
end;
上面这段代码在Dialogs单元的第5407行, 该函数先进行可用性判断, 然后填充
LTaskDialog: TTaskDialogConfig;
一个TTaskDialogConfig的结构体, 该结构体定义在CommCtrl单元第9550行, 其定义如下:
type
{ $EXTERNALSYM TASKDIALOGCONFIG}
TASKDIALOGCONFIG = packed record
cbSize: UINT;
hwndParent: HWND;
hInstance: HINST; // used for MAKEINTRESOURCE() strings
dwFlags: DWORD; // TASKDIALOG_FLAGS (TDF_XXX) flags
dwCommonButtons: DWORD; // TASKDIALOG_COMMON_BUTTON (TDCBF_XXX) flags
pszWindowTitle: LPCWSTR; // string or MAKEINTRESOURCE()
case Integer of
0: (hMainIcon: HICON);
1: (pszMainIcon: LPCWSTR;
pszMainInstruction: LPCWSTR;
pszContent: LPCWSTR;
cButtons: UINT;
pButtons: PTaskDialogButton;
nDefaultButton: Integer;
cRadioButtons: UINT;
pRadioButtons: PTaskDialogButton;
nDefaultRadioButton: Integer;
pszVerificationText: LPCWSTR;
pszExpandedInformation: LPCWSTR;
pszExpandedControlText: LPCWSTR;
pszCollapsedControlText: LPCWSTR;
case Integer of
0: (hFooterIcon: HICON);
1: (pszFooterIcon: LPCWSTR;
pszFooter: LPCWSTR;
pfCallback: TFTaskDialogCallback;
lpCallbackData: LONG_PTR;
cxWidth: UINT // width of the Task Dialog's client area in DLU's.
// If 0, Task Dialog will calculate the ideal width.
);
);
end;
{$EXTERNALSYM _TASKDIALOGCONFIG}
_TASKDIALOGCONFIG = TASKDIALOGCONFIG;
PTaskDialogConfig = ^TTaskDialogConfig;
TTaskDialogConfig = TASKDIALOGCONFIG;
该结构体其实是从MSDN里翻译过来的, 定义在CommCtrl.h 头文件里(需要Windows Vista, Windows Server 2008及以上版本, 我是用Windows 7 64位进行的测试), 详细说明可以查看MSDN.
TCustomTaskDialog.DoExecute 填充完LTaskDialog结构体后, 直接调用:
Result := TaskDialogIndirect(LTaskDialog, {$IFNDEF CLR}@{$ENDIF}LModalResult,
{$IFNDEF CLR}@{$ENDIF}LRadioButton, {$IFNDEF CLR}@{$ENDIF}LVerificationChecked) = S_OK;
TaskDialogIndirect显示对话框, TaskDialogIndirect定义在CommCtrl单元, 其代码如下:
{ Task Dialog }
var
_TaskDialogIndirect: function(const pTaskConfig: TTaskDialogConfig;
pnButton: PInteger; pnRadioButton: PInteger;
pfVerificationFlagChecked: PBOOL): HRESULT; stdcall;
_TaskDialog: function(hwndParent: HWND; hInstance: HINST;
pszWindowTitle: LPCWSTR; pszMainInstruction: LPCWSTR; pszContent: LPCWSTR;
dwCommonButtons: DWORD; pszIcon: LPCWSTR; pnButton: PInteger): HRESULT; stdcall;
function TaskDialogIndirect(const pTaskConfig: TTaskDialogConfig;
pnButton: PInteger; pnRadioButton: PInteger; pfVerificationFlagChecked: PBOOL): HRESULT;
begin
if Assigned(_TaskDialogIndirect) then
Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton,
pfVerificationFlagChecked)
else
begin
InitComCtl;
Result := E_NOTIMPL;
if ComCtl32DLL <> 0 then
begin
@_TaskDialogIndirect := GetProcAddress(ComCtl32DLL, 'TaskDialogIndirect');
if Assigned(_TaskDialogIndirect) then
Result := _TaskDialogIndirect(pTaskConfig, pnButton, pnRadioButton,
pfVerificationFlagChecked)
end;
end;
end;
查看代码知道, TaskDialogIndirect 直接调用ComCtrl32.Dll里的函数:TaskDialogIndirect 显示对话框. 通过查询MSDN了解TaskDialogIndirect API的用途与用法:
The TaskDialogIndirect function creates, displays, and operates a task dialog. The task dialog contains application-defined icons, messages, title, verification check box, command links, push buttons, and radio buttons. This function can register a callback function to receive notification messages.
函数TaskDialogIndirect 用于创建, 显示, 运行一个任务对话框, 这个任务对话框可以包括由应用程序定义的图标,消息,标题,复选框,按钮,单选框. 该函数还可以接收一个回调函数用于接收通知信息
看到这里你或许会问:
如果我的系统是xp或其他低于Vista, server2008的系统呢? 由上文中可知, 如果是低版本的系统, 则调用DoMessageDlgPosHelp 函数进行对话框显示, 调用代码如下:
Result := DoMessageDlgPosHelp(CreateMessageDialog(Msg, DlgType, Buttons),
HelpCtx, X, Y, HelpFileName);
DoMessageDlgPosHelp代码:
function DoMessageDlgPosHelp(MessageDialog: TForm; HelpCtx: Longint; X, Y: Integer;
const HelpFileName: string): Integer;
begin
with MessageDialog do
try
HelpContext := HelpCtx;
HelpFile := HelpFileName;
if X >= 0 then Left := X;
if Y >= 0 then Top := Y;
if (Y < 0) and (X < 0) then Position := poScreenCenter;
Result := ShowModal;
finally
Free;
end;
end;
从DoMessageDlgPosHelp代码中可见, 该函数只是简单的将传递进来的TForm以模式窗口的形式显示在指定的位置.
下面是CreateMessageDialog代码:
function CreateMessageDialog(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; DefaultButton: TMsgDlgBtn): TForm;
const
mcHorzMargin = 8;
mcVertMargin = 8;
mcHorzSpacing = 10;
mcVertSpacing = 10;
mcButtonWidth = 50;
mcButtonHeight = 14;
mcButtonSpacing = 4;
var
DialogUnits: TPoint;
HorzMargin, VertMargin, HorzSpacing, VertSpacing, ButtonWidth,
ButtonHeight, ButtonSpacing, ButtonCount, ButtonGroupWidth,
IconTextWidth, IconTextHeight, X, ALeft: Integer;
B, CancelButton: TMsgDlgBtn;
{$IF DEFINED(CLR)}
IconID: Integer;
{$ELSE}
IconID: PChar;
{$IFEND}
TextRect: TRect;
LButton: TButton;
begin
Result := TMessageForm.CreateNew(Application);
with Result do
begin
BiDiMode := Application.BiDiMode;
BorderStyle := bsDialog;
Canvas.Font := Font;
KeyPreview := True;
PopupMode := pmAuto;
Position := poDesigned;
OnKeyDown := TMessageForm(Result).CustomKeyDown;
DialogUnits := GetAveCharSize(Canvas);
HorzMargin := MulDiv(mcHorzMargin, DialogUnits.X, 4);
VertMargin := MulDiv(mcVertMargin, DialogUnits.Y, 8);
HorzSpacing := MulDiv(mcHorzSpacing, DialogUnits.X, 4);
VertSpacing := MulDiv(mcVertSpacing, DialogUnits.Y, 8);
ButtonWidth := MulDiv(mcButtonWidth, DialogUnits.X, 4);
for B := Low(TMsgDlgBtn) to High(TMsgDlgBtn) do
begin
if B in Buttons then
begin
if ButtonWidths[B] = 0 then
begin
TextRect := Rect(0,0,0,0);
Windows.DrawText( canvas.handle,
{$IF DEFINED(CLR)}
ButtonCaptions[B], -1,
{$ELSE}
PChar(LoadResString(ButtonCaptions[B])), -1,
{$IFEND}
TextRect, DT_CALCRECT or DT_LEFT or DT_SINGLELINE or
DrawTextBiDiModeFlagsReadingOnly);
with TextRect do ButtonWidths[B] := Right - Left + 8;
end;
if ButtonWidths[B] > ButtonWidth then
ButtonWidth := ButtonWidths[B];
end;
end;
ButtonHeight := MulDiv(mcButtonHeight, DialogUnits.Y, 8);
ButtonSpacing := MulDiv(mcButtonSpacing, DialogUnits.X, 4);
SetRect(TextRect, 0, 0, Screen.Width div 2, 0);
DrawText(Canvas.Handle, Msg, Length(Msg)+1, TextRect,
DT_EXPANDTABS or DT_CALCRECT or DT_WORDBREAK or
DrawTextBiDiModeFlagsReadingOnly);
IconID := IconIDs[DlgType];
IconTextWidth := TextRect.Right;
IconTextHeight := TextRect.Bottom;
{$IF DEFINED(CLR)}
if DlgType <> mtCustom then
{$ELSE}
if IconID <> nil then
{$IFEND}
begin
Inc(IconTextWidth, 32 + HorzSpacing);
if IconTextHeight < 32 then IconTextHeight := 32;
end;
ButtonCount := 0;
for B := Low(TMsgDlgBtn) to High(TMsgDlgBtn) do
if B in Buttons then Inc(ButtonCount);
ButtonGroupWidth := 0;
if ButtonCount <> 0 then
ButtonGroupWidth := ButtonWidth * ButtonCount +
ButtonSpacing * (ButtonCount - 1);
ClientWidth := Max(IconTextWidth, ButtonGroupWidth) + HorzMargin * 2;
ClientHeight := IconTextHeight + ButtonHeight + VertSpacing +
VertMargin * 2;
Left := (Screen.Width div 2) - (Width div 2);
Top := (Screen.Height div 2) - (Height div 2);
if DlgType <> mtCustom then
{$IF DEFINED(CLR)}
Caption := Captions[DlgType] else
Caption := Application.Title;
if DlgType <> mtCustom then
{$ELSE}
Caption := LoadResString(Captions[DlgType]) else
Caption := Application.Title;
if IconID <> nil then
{$IFEND}
with TImage.Create(Result) do
begin
Name := 'Image';
Parent := Result;
Picture.Icon.Handle := LoadIcon(0, IconID);
SetBounds(HorzMargin, VertMargin, 32, 32);
end;
TMessageForm(Result).Message := TLabel.Create(Result);
with TMessageForm(Result).Message do
begin
Name := 'Message';
Parent := Result;
WordWrap := True;
Caption := Msg;
BoundsRect := TextRect;
BiDiMode := Result.BiDiMode;
ALeft := IconTextWidth - TextRect.Right + HorzMargin;
if UseRightToLeftAlignment then
ALeft := Result.ClientWidth - ALeft - Width;
SetBounds(ALeft, VertMargin,
TextRect.Right, TextRect.Bottom);
end;
if mbCancel in Buttons then CancelButton := mbCancel else
if mbNo in Buttons then CancelButton := mbNo else
CancelButton := mbOk;
X := (ClientWidth - ButtonGroupWidth) div 2;
for B := Low(TMsgDlgBtn) to High(TMsgDlgBtn) do
if B in Buttons then
begin
LButton := TButton.Create(Result);
with LButton do
begin
Name := ButtonNames[B];
Parent := Result;
{$IF DEFINED(CLR)}
Caption := ButtonCaptions[B];
{$ELSE}
Caption := LoadResString(ButtonCaptions[B]);
{$IFEND}
ModalResult := ModalResults[B];
if B = DefaultButton then
begin
Default := True;
ActiveControl := LButton;
end;
if B = CancelButton then
Cancel := True;
SetBounds(X, IconTextHeight + VertMargin + VertSpacing,
ButtonWidth, ButtonHeight);
Inc(X, ButtonWidth + ButtonSpacing);
if B = mbHelp then
OnClick := TMessageForm(Result).HelpButtonClick;
end;
end;
end;
end;
由代码可见, CreateMessageDialog只是创建了一个TMessageForm, 然后动态地添加了一些设置. 写到这里或许可以解答一些人的问题: 对话框是不是一个窗口? 答案是:是.
你还可能会问: 为什么对话框可以停留在那一行代码直到用户操作完毕后再往下执行, 这里就需要了解一下模态窗口的知识: 请参见这篇文章 Delphi ShowModal解析