在这篇文章中,我将大概的从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 TaskDialogIndirectfunction 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, 然后动态地添加了一些设置. 写到这里或许可以解答一些人的问题: 对话框是不是一个窗口? 答案是:是.
你还可能会问: 为什么对话框可以停留在那一行代码直到用户操作完毕后再往下执行, 这里就需要了解一下模态窗口的知识。
原文链接:http://www.cnblogs.com/neugls/archive/2011/09/14/2176733.html
【编辑推荐】
- Delphi与C#之父:技术理想架构开发传奇
- Delphi 2010初体验:彻底告别内存泄露
- 开发热点周报:Delphi 2010出炉 mixin进驻JavaFX
- Delphi XE2将出 一场技术革命即将打响
- 9月TIOBE编程语言排行榜发布 Delphi东山再起