Shared.TaskDialogFunc.pas 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. unit Shared.TaskDialogFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. TaskDialogMsgBox function integrating with CmnFunc's MsgBox functions
  8. }
  9. interface
  10. uses
  11. Windows, Shared.CommonFunc.Vcl;
  12. function TaskDialogMsgBox(const Icon, Instruction, Text, Caption: String; const Typ: TMsgBoxType; const Buttons: Cardinal; const ButtonLabels: array of String; const ShieldButton: Integer; const VerificationText: String = ''; const pfVerificationFlagChecked: PBOOL = nil): Integer;
  13. implementation
  14. uses
  15. Classes, StrUtils, Math, Forms, Dialogs, SysUtils, Themes,
  16. Commctrl, Shared.CommonFunc, {$IFDEF SETUPPROJ} Setup.InstFunc, {$ENDIF} PathFunc;
  17. var
  18. TaskDialogIndirectFunc: function(const pTaskConfig: TTaskDialogConfig;
  19. pnButton: PInteger; pnRadioButton: PInteger;
  20. pfVerificationFlagChecked: PBOOL): HRESULT; stdcall;
  21. function ShieldButtonCallback(hwnd: HWND; msg: UINT; wParam: WPARAM; lParam: LPARAM; lpRefData: LONG_PTR): HResult; stdcall;
  22. begin
  23. if (msg = TDN_CREATED) and (lpRefData <> 0) then
  24. SendMessage(hwnd, TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE, lpRefData, 1);
  25. Result := S_OK;
  26. end;
  27. function DoTaskDialog(const hWnd: HWND; const Instruction, Text, Caption, Icon: PWideChar; const CommonButtons: Cardinal; const ButtonLabels: array of String; const ButtonIDs: array of Integer; const ShieldButton: Integer; const RightToLeft: Boolean; const TriggerMessageBoxCallbackFuncFlags: LongInt; var ModalResult: Integer; const VerificationText: PWideChar; const pfVerificationFlagChecked: PBOOL): Boolean;
  28. var
  29. Config: TTaskDialogConfig;
  30. NButtonLabelsAvailable: Integer;
  31. ButtonItems: TTaskDialogButtons;
  32. ButtonItem: TTaskDialogButtonItem;
  33. I: Integer;
  34. ActiveWindow: Windows.HWND;
  35. WindowList: Pointer;
  36. begin
  37. if Assigned(TaskDialogIndirectFunc) then begin
  38. ZeroMemory(@Config, Sizeof(Config));
  39. Config.cbSize := SizeOf(Config);
  40. if (StrPos(Text, ':\') <> nil) or (StrPos(Text, '\\') <> nil) then
  41. Config.dwFlags := Config.dwFlags or TDF_SIZE_TO_CONTENT;
  42. if RightToLeft then
  43. Config.dwFlags := Config.dwFlags or TDF_RTL_LAYOUT;
  44. Config.hInstance := HInstance;
  45. Config.hwndParent := hWnd;
  46. Config.dwCommonButtons := CommonButtons;
  47. Config.pszWindowTitle := Caption;
  48. Config.pszMainIcon := Icon;
  49. Config.pszMainInstruction := Instruction;
  50. Config.pszContent := Text;
  51. if VerificationText <> '' then
  52. Config.pszVerificationText := VerificationText;
  53. if ShieldButton <> 0 then begin
  54. Config.pfCallback := ShieldButtonCallback;
  55. Config.lpCallbackData := ShieldButton;
  56. end;
  57. ButtonItems := nil;
  58. try
  59. NButtonLabelsAvailable := Length(ButtonLabels);
  60. if NButtonLabelsAvailable <> 0 then begin
  61. ButtonItems := TTaskDialogButtons.Create(nil, TTaskDialogButtonItem);
  62. Config.dwFlags := Config.dwFlags or TDF_USE_COMMAND_LINKS;
  63. for I := 0 to NButtonLabelsAvailable-1 do begin
  64. ButtonItem := TTaskDialogButtonItem(ButtonItems.Add);
  65. ButtonItem.Caption := ButtonLabels[I];
  66. ButtonItem.ModalResult := ButtonIDs[I];
  67. end;
  68. Config.pButtons := ButtonItems.Buttons;
  69. Config.cButtons := ButtonItems.Count;
  70. end;
  71. TriggerMessageBoxCallbackFunc(TriggerMessageBoxCallbackFuncFlags, False);
  72. ActiveWindow := GetActiveWindow;
  73. WindowList := DisableTaskWindows(Config.hwndParent);
  74. { Temporarily clear SystemHooks to stop it from breaking the title bar. Does not make it dark.
  75. Also see BrowseFunc's NewGetOpenOrSaveFileName. }
  76. const SaveHooks = TStyleManager.SystemHooks;
  77. TStyleManager.SystemHooks := [];
  78. try
  79. Result := TaskDialogIndirectFunc(Config, @ModalResult, nil, pfVerificationFlagChecked) = S_OK;
  80. finally
  81. TStyleManager.SystemHooks := SaveHooks;
  82. EnableTaskWindows(WindowList);
  83. SetActiveWindow(ActiveWindow);
  84. TriggerMessageBoxCallbackFunc(TriggerMessageBoxCallbackFuncFlags, True);
  85. end;
  86. finally
  87. ButtonItems.Free;
  88. end;
  89. end else
  90. Result := False;
  91. end;
  92. procedure DoInternalError(const Msg: String);
  93. begin
  94. {$IFDEF SETUPPROJ}
  95. InternalError(Msg);
  96. {$ELSE}
  97. raise Exception.Create(Msg);
  98. {$ENDIF}
  99. end;
  100. function TaskDialogMsgBox(const Icon, Instruction, Text, Caption: String; const Typ: TMsgBoxType; const Buttons: Cardinal; const ButtonLabels: array of String; const ShieldButton: Integer; const VerificationText: String = ''; const pfVerificationFlagChecked: PBOOL = nil): Integer;
  101. begin
  102. Application.Restore; { See comments in AppMessageBox }
  103. { Set icon }
  104. var IconP: PChar;
  105. if Icon <> '' then
  106. IconP := PChar(Icon)
  107. else begin
  108. case Typ of
  109. mbInformation: IconP := TD_INFORMATION_ICON;
  110. mbError: IconP := TD_WARNING_ICON;
  111. mbCriticalError: IconP := TD_ERROR_ICON;
  112. else
  113. IconP := nil; { No other TD_ constant available, MS recommends to use no icon for questions now and the old icon should only be used for help entries }
  114. end;
  115. end;
  116. { Set ButtonIDs and TDCommonButtons }
  117. const NButtonLabelsAvailable = Length(ButtonLabels);
  118. var ButtonIDs: TArray<Integer>;
  119. var TDCommonButtons: Cardinal;
  120. case Buttons of
  121. MB_OK, MB_OKCANCEL:
  122. begin
  123. if NButtonLabelsAvailable = 0 then
  124. TDCommonButtons := TDCBF_OK_BUTTON
  125. else begin
  126. TDCommonButtons := 0;
  127. ButtonIDs := [IDOK];
  128. end;
  129. if Buttons = MB_OKCANCEL then
  130. TDCommonButtons := TDCommonButtons or TDCBF_CANCEL_BUTTON;
  131. end;
  132. MB_YESNO, MB_YESNOCANCEL:
  133. begin
  134. if NButtonLabelsAvailable = 0 then
  135. TDCommonButtons := TDCBF_YES_BUTTON or TDCBF_NO_BUTTON
  136. else begin
  137. TDCommonButtons := 0;
  138. ButtonIDs := [IDYES, IDNO];
  139. end;
  140. if Buttons = MB_YESNOCANCEL then
  141. TDCommonButtons := TDCommonButtons or TDCBF_CANCEL_BUTTON;
  142. end;
  143. MB_RETRYCANCEL:
  144. begin
  145. if NButtonLabelsAvailable = 0 then
  146. TDCommonButtons := TDCBF_RETRY_BUTTON
  147. else begin
  148. TDCommonButtons := 0;
  149. ButtonIDs := [IDRETRY];
  150. end;
  151. TDCommonButtons := TDCommonButtons or TDCBF_CANCEL_BUTTON;
  152. end;
  153. MB_ABORTRETRYIGNORE:
  154. begin
  155. if NButtonLabelsAvailable = 0 then
  156. DoInternalError('TaskDialogMsgBox: Invalid ButtonLabels')
  157. else
  158. ButtonIDs := [IDRETRY, IDIGNORE, IDABORT]; { Notice the order, abort label must be last }
  159. TDCommonButtons := 0;
  160. end;
  161. else
  162. begin
  163. DoInternalError('TaskDialogMsgBox: Invalid Buttons');
  164. TDCommonButtons := 0; { Silence compiler }
  165. end;
  166. end;
  167. { Allow extra label to replace TDCBF_CANCEL_BUTTON by an IDCANCEL button id }
  168. if (TDCommonButtons or TDCBF_CANCEL_BUTTON <> 0) and
  169. (NButtonLabelsAvailable-1 = Length(ButtonIDs)) then begin
  170. TDCommonButtons := TDCommonButtons and not TDCBF_CANCEL_BUTTON;
  171. SetLength(ButtonIDs, NButtonLabelsAvailable);
  172. ButtonIDs[NButtonLabelsAvailable-1] := IDCANCEL;
  173. end;
  174. { Check }
  175. if Length(ButtonIDs) <> NButtonLabelsAvailable then
  176. DoInternalError('TaskDialogMsgBox: Invalid ButtonLabels');
  177. { Go }
  178. if not DoTaskDialog(GetOwnerWndForMessageBox, PChar(Instruction), PChar(Text),
  179. GetMessageBoxCaption(PChar(Caption), Typ), IconP, TDCommonButtons, ButtonLabels, ButtonIDs, ShieldButton,
  180. GetMessageBoxRightToLeft, IfThen(Typ in [mbError, mbCriticalError], MB_ICONSTOP, 0), Result, PChar(VerificationText), pfVerificationFlagChecked) then //note that MB_ICONEXCLAMATION (used by mbError) includes MB_ICONSTOP (used by mbCriticalError)
  181. Result := 0;
  182. end;
  183. procedure InitCommonControls; external comctl32 name 'InitCommonControls';
  184. initialization
  185. InitCommonControls;
  186. TaskDialogIndirectFunc := GetProcAddress(GetModuleHandle(comctl32), 'TaskDialogIndirect');
  187. end.