zoukankan      html  css  js  c++  java
  • 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解析

  • 相关阅读:
    Caffe_Example之训练mnist
    监督学习和无监督学习
    linux 命令cp拷贝
    Caffe solver.prototxt学习
    caffe下python环境的编译
    ubuntu 绘制lenet网络结构图遇到的问题汇总
    1-6 能否形成三角形
    Python的四个内置数据类型list, tuple, dict, set
    Python 函数(二)
    Python 函数(一)
  • 原文地址:https://www.cnblogs.com/neugls/p/2176733.html
Copyright © 2011-2022 走看看