Delphi 对话框实现源码分析

 

简介

在这篇文章中,我将大概的从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

1

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解析

你可能感兴趣的:(Delphi)