Shared.TaskDialogFunc.pas 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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;
  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: LongInt;
  38. var ModalResult: Integer; const VerificationText: PChar; const pfVerificationFlagChecked: PBOOL): Boolean;
  39. var
  40. Config: TTaskDialogConfig;
  41. NButtonLabelsAvailable: Integer;
  42. ButtonItems: TTaskDialogButtons;
  43. ButtonItem: TTaskDialogButtonItem;
  44. I: Integer;
  45. ActiveWindow: Windows.HWND;
  46. WindowList: Pointer;
  47. begin
  48. if Assigned(TaskDialogIndirectFunc) then begin
  49. ZeroMemory(@Config, Sizeof(Config));
  50. Config.cbSize := SizeOf(Config);
  51. if (StrPos(Text, ':\') <> nil) or (StrPos(Text, '\\') <> nil) then
  52. Config.dwFlags := Config.dwFlags or TDF_SIZE_TO_CONTENT;
  53. if RightToLeft then
  54. Config.dwFlags := Config.dwFlags or TDF_RTL_LAYOUT;
  55. Config.hInstance := HInstance;
  56. Config.hwndParent := hWnd;
  57. Config.dwCommonButtons := CommonButtons;
  58. Config.pszWindowTitle := Caption;
  59. Config.pszMainIcon := Icon;
  60. Config.pszMainInstruction := Instruction;
  61. Config.pszContent := Text;
  62. if VerificationText <> '' then
  63. Config.pszVerificationText := VerificationText;
  64. if ShieldButton <> 0 then begin
  65. Config.pfCallback := ShieldButtonCallback;
  66. Config.lpCallbackData := ShieldButton;
  67. end;
  68. ButtonItems := nil;
  69. try
  70. NButtonLabelsAvailable := Length(ButtonLabels);
  71. if NButtonLabelsAvailable <> 0 then begin
  72. ButtonItems := TTaskDialogButtons.Create(nil, TTaskDialogButtonItem);
  73. Config.dwFlags := Config.dwFlags or TDF_USE_COMMAND_LINKS;
  74. for I := 0 to NButtonLabelsAvailable-1 do begin
  75. ButtonItem := TTaskDialogButtonItem(ButtonItems.Add);
  76. ButtonItem.Caption := ButtonLabels[I];
  77. ButtonItem.ModalResult := ButtonIDs[I];
  78. end;
  79. Config.pButtons := ButtonItems.Buttons;
  80. Config.cButtons := ButtonItems.Count;
  81. end;
  82. TriggerMessageBoxCallbackFunc(TriggerMessageBoxCallbackFuncFlags, False);
  83. ActiveWindow := GetActiveWindow;
  84. WindowList := DisableTaskWindows(Config.hwndParent);
  85. { Temporarily clear SystemHooks to stop it from breaking the title bar. Does not make it dark.
  86. Also see BrowseFunc's NewGetOpenOrSaveFileName. }
  87. const SaveHooks = TStyleManager.SystemHooks;
  88. TStyleManager.SystemHooks := [];
  89. try
  90. Result := TaskDialogIndirectFunc(Config, @ModalResult, nil, pfVerificationFlagChecked) = S_OK;
  91. finally
  92. TStyleManager.SystemHooks := SaveHooks;
  93. EnableTaskWindows(WindowList);
  94. SetActiveWindow(ActiveWindow);
  95. TriggerMessageBoxCallbackFunc(TriggerMessageBoxCallbackFuncFlags, True);
  96. end;
  97. finally
  98. ButtonItems.Free;
  99. end;
  100. end else
  101. Result := False;
  102. end;
  103. procedure DoInternalError(const Msg: String);
  104. begin
  105. {$IFDEF SETUPPROJ}
  106. InternalError(Msg);
  107. {$ELSE}
  108. raise Exception.Create(Msg);
  109. {$ENDIF}
  110. end;
  111. function TaskDialogMsgBox(const Icon, Instruction, Text, Caption: String; const Typ: TMsgBoxType;
  112. const Buttons: Cardinal; const ButtonLabels: array of String; const ShieldButton: Integer;
  113. const VerificationText: String = ''; const pfVerificationFlagChecked: PBOOL = nil): Integer;
  114. begin
  115. Application.Restore; { See comments in MsgBox }
  116. { Set icon }
  117. var IconP: PChar;
  118. if Icon <> '' then
  119. IconP := PChar(Icon)
  120. else begin
  121. case Typ of
  122. mbInformation: IconP := TD_INFORMATION_ICON;
  123. mbError: IconP := TD_WARNING_ICON;
  124. mbCriticalError: IconP := TD_ERROR_ICON;
  125. else
  126. 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 }
  127. end;
  128. end;
  129. { Set ButtonIDs and TDCommonButtons }
  130. const NButtonLabelsAvailable = Length(ButtonLabels);
  131. var ButtonIDs: TArray<Integer>;
  132. var TDCommonButtons: Cardinal;
  133. case Buttons of
  134. MB_OK, MB_OKCANCEL:
  135. begin
  136. if NButtonLabelsAvailable = 0 then
  137. TDCommonButtons := TDCBF_OK_BUTTON
  138. else begin
  139. TDCommonButtons := 0;
  140. ButtonIDs := [IDOK];
  141. end;
  142. if Buttons = MB_OKCANCEL then
  143. TDCommonButtons := TDCommonButtons or TDCBF_CANCEL_BUTTON;
  144. end;
  145. MB_YESNO, MB_YESNOCANCEL:
  146. begin
  147. if NButtonLabelsAvailable = 0 then
  148. TDCommonButtons := TDCBF_YES_BUTTON or TDCBF_NO_BUTTON
  149. else begin
  150. TDCommonButtons := 0;
  151. ButtonIDs := [IDYES, IDNO];
  152. end;
  153. if Buttons = MB_YESNOCANCEL then
  154. TDCommonButtons := TDCommonButtons or TDCBF_CANCEL_BUTTON;
  155. end;
  156. MB_RETRYCANCEL:
  157. begin
  158. if NButtonLabelsAvailable = 0 then
  159. TDCommonButtons := TDCBF_RETRY_BUTTON
  160. else begin
  161. TDCommonButtons := 0;
  162. ButtonIDs := [IDRETRY];
  163. end;
  164. TDCommonButtons := TDCommonButtons or TDCBF_CANCEL_BUTTON;
  165. end;
  166. MB_ABORTRETRYIGNORE:
  167. begin
  168. if NButtonLabelsAvailable = 0 then
  169. DoInternalError('TaskDialogMsgBox: Invalid ButtonLabels')
  170. else
  171. ButtonIDs := [IDRETRY, IDIGNORE, IDABORT]; { Notice the order, abort label must be last }
  172. TDCommonButtons := 0;
  173. end;
  174. else
  175. begin
  176. DoInternalError('TaskDialogMsgBox: Invalid Buttons');
  177. TDCommonButtons := 0; { Silence compiler }
  178. end;
  179. end;
  180. { Allow extra label to replace TDCBF_CANCEL_BUTTON by an IDCANCEL button id }
  181. if (TDCommonButtons and TDCBF_CANCEL_BUTTON <> 0) and
  182. (NButtonLabelsAvailable-1 = Length(ButtonIDs)) then begin
  183. TDCommonButtons := TDCommonButtons and not TDCBF_CANCEL_BUTTON;
  184. SetLength(ButtonIDs, NButtonLabelsAvailable);
  185. ButtonIDs[NButtonLabelsAvailable-1] := IDCANCEL;
  186. end;
  187. { Check }
  188. if Length(ButtonIDs) <> NButtonLabelsAvailable then
  189. DoInternalError('TaskDialogMsgBox: Invalid ButtonLabels');
  190. { Go }
  191. const MessageBoxCaption = GetMessageBoxCaption(PChar(Caption), Typ);
  192. const TriggerMessageBoxCallbackFuncFlags = 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. 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.