Shared.TaskDialogFunc.pas 8.7 KB

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