Shared.CommonFunc.Vcl.pas 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. unit Shared.CommonFunc.Vcl;
  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. Common VCL functions
  8. }
  9. {$B-}
  10. interface
  11. uses
  12. Windows, Messages, SysUtils, Forms, Graphics, Controls, StdCtrls, Classes;
  13. type
  14. TWindowDisabler = class
  15. private
  16. FFallbackWnd, FOwnerWnd: HWND;
  17. FPreviousActiveWnd, FPreviousFocusWnd: HWND;
  18. FWindowList: Pointer;
  19. public
  20. constructor Create;
  21. destructor Destroy; override;
  22. end;
  23. { Note: This type is also present in Compiler.ScriptFunc.pas }
  24. TMsgBoxType = (mbInformation, mbConfirmation, mbError, mbCriticalError);
  25. TMsgBoxCallbackFunc = procedure(const Flags: Cardinal; const After: Boolean;
  26. const Param: NativeInt);
  27. TControlHelper = class helper for TControl
  28. procedure SetCurrentPPI(const CurrentPPI: Integer);
  29. end;
  30. { Useful constant }
  31. const
  32. EnableColor: array[Boolean] of TColor = (clBtnFace, clWindow);
  33. function AppCreateForm(const AClass: TCustomFormClass): TCustomForm;
  34. procedure UpdateHorizontalExtent(const ListBox: TCustomListBox);
  35. function MinimizePathName(const Filename: String; const Font: TFont;
  36. MaxLen: Integer): String;
  37. function MsgBox(const Text, Caption: PChar; Flags: Cardinal): Integer; overload;
  38. function MsgBox(const Text, Caption: String; const Typ: TMsgBoxType;
  39. const Buttons: Cardinal): Integer; overload;
  40. function MsgBoxFmt(const Text: String; const Args: array of const;
  41. const Caption: String; const Typ: TMsgBoxType; const Buttons: Cardinal): Integer;
  42. procedure SetMessageBoxRightToLeft(const ARightToLeft: Boolean);
  43. function GetMessageBoxRightToLeft: Boolean;
  44. procedure SetMessageBoxCallbackFunc(const AFunc: TMsgBoxCallbackFunc; const AParam: NativeInt);
  45. procedure TriggerMessageBoxCallbackFunc(const Flags: Cardinal; const After: Boolean);
  46. function GetOwnerWndForMessageBox: HWND;
  47. function IsWindowOnTaskbar(const Wnd: HWND): Boolean;
  48. procedure SetDarkTitleBar(const Form: TForm; const Dark: Boolean);
  49. implementation
  50. {$IF Defined(SETUPPROJ) and Defined(VCLSTYLES)}
  51. {$DEFINE USETASKDIALOGFORM}
  52. {$IFEND}
  53. uses
  54. DwmApi, Consts, PathFunc, Themes,
  55. {$IFDEF USETASKDIALOGFORM} CommCtrl, Setup.TaskDialogForm, {$ENDIF}
  56. {$IFDEF SETUPPROJ} Setup.InstFunc, {$ENDIF}
  57. Shared.CommonFunc;
  58. var
  59. MessageBoxRightToLeft: Boolean;
  60. MessageBoxCallbackFunc: TMsgBoxCallbackFunc;
  61. MessageBoxCallbackParam: NativeInt;
  62. MessageBoxCallbackActive: Boolean;
  63. function AppCreateForm(const AClass: TCustomFormClass): TCustomForm;
  64. { Creates a form, making it the main form if there isn't one already.
  65. Usage: AppCreateForm(TMyForm) as TMyForm
  66. This is a wrapper around Application.CreateForm, but with these advantages:
  67. - Safety: Returns a typed value instead of writing to an untyped parameter.
  68. - Safety: When used in an assignment statement: MyForm := AppCreateForm(...)
  69. the variable isn't modified until the form is fully constructed and the
  70. function exits. Application.CreateForm writes to its parameter, making the
  71. value public, before the form's constructor is executed, which could allow
  72. code outside the form to access the form before it's fully constructed.
  73. - When the result is casted with "as", it works with type inference.
  74. - When used in the .dpr, the Delphi IDE will never touch it. }
  75. begin
  76. Application.CreateForm(AClass, Result);
  77. end;
  78. type
  79. TListBoxAccess = class(TCustomListBox);
  80. procedure UpdateHorizontalExtent(const ListBox: TCustomListBox);
  81. var
  82. I: Integer;
  83. Extent, MaxExtent: Longint;
  84. DC: HDC;
  85. Size: TSize;
  86. TextMetrics: TTextMetric;
  87. begin
  88. DC := GetDC(0);
  89. try
  90. SelectObject(DC, TListBoxAccess(ListBox).Font.Handle);
  91. //Q66370 says tmAveCharWidth should be added to extent
  92. GetTextMetrics(DC, TextMetrics);
  93. MaxExtent := 0;
  94. for I := 0 to ListBox.Items.Count-1 do begin
  95. GetTextExtentPoint32(DC, PChar(ListBox.Items[I]), Length(ListBox.Items[I]), Size);
  96. Extent := Size.cx + TextMetrics.tmAveCharWidth;
  97. if Extent > MaxExtent then
  98. MaxExtent := Extent;
  99. end;
  100. finally
  101. ReleaseDC(0, DC);
  102. end;
  103. if MaxExtent > SendMessage(ListBox.Handle, LB_GETHORIZONTALEXTENT, 0, 0) then
  104. SendMessage(ListBox.Handle, LB_SETHORIZONTALEXTENT, MaxExtent, 0);
  105. end;
  106. function MinimizePathName(const Filename: String; const Font: TFont;
  107. MaxLen: Integer): String;
  108. procedure CutFirstDirectory(var S: String);
  109. var
  110. P: Integer;
  111. begin
  112. if Copy(S, 1, 4) = '...\' then
  113. Delete(S, 1, 4);
  114. P := PathPos('\', S);
  115. if P <> 0 then
  116. begin
  117. Delete(S, 1, P);
  118. S := '...\' + S;
  119. end
  120. else
  121. S := '';
  122. end;
  123. var
  124. DC: HDC;
  125. Drive, Dir, Name: String;
  126. DriveLen: Integer;
  127. begin
  128. DC := GetDC(0);
  129. try
  130. SelectObject(DC, Font.Handle);
  131. Result := FileName;
  132. Dir := PathExtractPath(Result);
  133. Name := PathExtractName(Result);
  134. DriveLen := PathDrivePartLength(Dir);
  135. { Include any slash following drive part, or a leading slash if DriveLen=0 }
  136. if (DriveLen < Length(Dir)) and PathCharIsSlash(Dir[DriveLen+1]) then
  137. Inc(DriveLen);
  138. Drive := Copy(Dir, 1, DriveLen);
  139. Delete(Dir, 1, DriveLen);
  140. while ((Dir <> '') or (Drive <> '')) and (GetTextWidth(DC, Result, False) > MaxLen) do
  141. begin
  142. if Dir <> '' then
  143. CutFirstDirectory(Dir);
  144. { If there's no directory left, minimize the drive part.
  145. 'C:\...\filename' -> '...\filename' }
  146. if (Dir = '') and (Drive <> '') then
  147. begin
  148. Drive := '';
  149. Dir := '...\';
  150. end;
  151. Result := Drive + Dir + Name;
  152. end;
  153. finally
  154. ReleaseDC(0, DC);
  155. end;
  156. end;
  157. procedure SetMessageBoxRightToLeft(const ARightToLeft: Boolean);
  158. begin
  159. MessageBoxRightToLeft := ARightToLeft;
  160. end;
  161. function GetMessageBoxRightToLeft: Boolean;
  162. begin
  163. Result := MessageBoxRightToLeft;
  164. end;
  165. procedure SetMessageBoxCallbackFunc(const AFunc: TMsgBoxCallbackFunc; const AParam: NativeInt);
  166. begin
  167. MessageBoxCallbackFunc := AFunc;
  168. MessageBoxCallbackParam := AParam;
  169. end;
  170. procedure TriggerMessageBoxCallbackFunc(const Flags: Cardinal; const After: Boolean);
  171. begin
  172. if Assigned(MessageBoxCallbackFunc) and not MessageBoxCallbackActive then begin
  173. MessageBoxCallbackActive := True;
  174. try
  175. MessageBoxCallbackFunc(Flags, After, MessageBoxCallbackParam);
  176. finally
  177. MessageBoxCallbackActive := False;
  178. end;
  179. end;
  180. end;
  181. function GetOwnerWndForMessageBox: HWND;
  182. { Returns window handle that Application.MessageBox, if called immediately
  183. after this function, would use as the owner window for the message box.
  184. Exception: If the window that would be returned is not shown on the taskbar,
  185. or is a minimized Application.Handle window, then 0 is returned instead.
  186. See comments in MsgBox. }
  187. begin
  188. { This is what Application.MessageBox does (Delphi 11.3) }
  189. Result := Application.ActiveFormHandle;
  190. if Result = 0 then { shouldn't be possible, but they have this check }
  191. Result := Application.Handle;
  192. { Now our overrides }
  193. if (Result = Application.Handle) and IsIconic(Result) then
  194. Exit(0);
  195. if not IsWindowOnTaskbar(Result) then
  196. Result := 0;
  197. end;
  198. function IsWindowOnTaskbar(const Wnd: HWND): Boolean;
  199. begin
  200. { Find the "root owner" window, which is what appears in the taskbar.
  201. We avoid GetAncestor(..., GA_ROOTOWNER) because it's broken in the same
  202. way as GetParent(): it stops if it reaches a top-level window that doesn't
  203. have the WS_POPUP style (i.e., a WS_OVERLAPPED window). }
  204. var RootWnd := Wnd;
  205. while True do begin
  206. { Visible WS_EX_APPWINDOW windows have their own taskbar button regardless
  207. of their root owner's visibility }
  208. if (GetWindowLong(RootWnd, GWL_EXSTYLE) and WS_EX_APPWINDOW <> 0) and
  209. (GetWindowLong(RootWnd, GWL_STYLE) and WS_VISIBLE <> 0) then
  210. Exit(True);
  211. var ParentWnd := HWND(GetWindowLongPtr(RootWnd, GWLP_HWNDPARENT));
  212. if ParentWnd = 0 then
  213. Break;
  214. RootWnd := ParentWnd;
  215. end;
  216. Result := (GetWindowLong(RootWnd, GWL_STYLE) and WS_VISIBLE <> 0) and
  217. (GetWindowLong(RootWnd, GWL_EXSTYLE) and WS_EX_TOOLWINDOW = 0);
  218. end;
  219. procedure SetDarkTitleBar(const Form: TForm; const Dark: Boolean);
  220. begin
  221. { Based on https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes
  222. Unlike this article we check for Windows 10 Version 2004 because that's the first version
  223. that introduced DWMWA_USE_IMMERSIVE_DARK_MODE as 20 (the now documented value) instead of 19 }
  224. if CurrentWindowsVersionAtLeast(10, 0, 19041) then begin
  225. if TStyleManager.FormBorderStyle = fbsCurrentStyle then
  226. Form.StyleElements := Form.StyleElements - [seBorder];
  227. const DWMWA_USE_IMMERSIVE_DARK_MODE: DWORD = 20;
  228. var value: BOOL := Dark;
  229. DwmSetWindowAttribute(Form.Handle, DWMWA_USE_IMMERSIVE_DARK_MODE, @value, SizeOf(value));
  230. end;
  231. end;
  232. {$IFDEF USETASKDIALOGFORM}
  233. var
  234. MsgBoxTaskDialogFormActive: Boolean;
  235. {$ENDIF}
  236. function MsgBox(const Text, Caption: PChar; Flags: Cardinal): Integer;
  237. {$IFDEF USETASKDIALOGFORM}
  238. procedure DoInternalError(const Msg: String);
  239. begin
  240. {$IFDEF SETUPPROJ}
  241. InternalError(Msg);
  242. {$ELSE}
  243. raise Exception.Create(Msg);
  244. {$ENDIF}
  245. end;
  246. procedure MsgBoxFlagsDecode(const Flags: Cardinal; out Icon: PChar;
  247. out TDCommonButtons: Cardinal; out DefCommonButton: Integer; out SetForeground: Boolean);
  248. begin
  249. case Flags and MB_ICONMASK of
  250. MB_ICONEXCLAMATION {equals MB_ICONWARNING}: Icon := TD_WARNING_ICON;
  251. MB_ICONINFORMATION {equals MB_ICONASTERISK}: Icon := TD_INFORMATION_ICON;
  252. MB_ICONQUESTION: Icon := TD_TASKFORM_HELP_ICON;
  253. MB_ICONSTOP {equals MB_ICONERROR and MB_ICONHAND}: Icon := TD_ERROR_ICON;
  254. else
  255. Icon := nil;
  256. end;
  257. case Flags and MB_TYPEMASK of
  258. MB_OK: TDCommonButtons := TDCBF_OK_BUTTON;
  259. MB_OKCANCEL: TDCommonButtons := TDCBF_OK_BUTTON or TDCBF_CANCEL_BUTTON;
  260. MB_YESNOCANCEL: TDCommonButtons := TDCBF_YES_BUTTON or TDCBF_NO_BUTTON or TDCBF_CANCEL_BUTTON;
  261. MB_YESNO: TDCommonButtons := TDCBF_YES_BUTTON or TDCBF_NO_BUTTON;
  262. MB_RETRYCANCEL: TDCommonButtons := TDCBF_RETRY_BUTTON or TDCBF_CANCEL_BUTTON;
  263. else
  264. DoInternalError('MsgBoxFlagsDecode: Invalid Flags');
  265. end;
  266. if (Flags and MB_DEFBUTTON2) <> 0 then
  267. DefCommonButton := 2
  268. else if (Flags and MB_DEFBUTTON3) <> 0 then
  269. DefCommonButton := 3
  270. else
  271. DefCommonButton := 0;
  272. SetForeground := Flags and MB_SETFOREGROUND <> 0;
  273. end;
  274. {$ENDIF}
  275. var
  276. ActiveWindow: HWND;
  277. WindowList: Pointer;
  278. begin
  279. { Always restore the app first if it's minimized. This makes sense from a
  280. usability perspective (e.g., it may be unclear which app generated the
  281. message box if it's shown by itself), but it's also a VCL bug mitigation
  282. (seen on Delphi 11.3):
  283. Without this, when Application.MainFormOnTaskBar=True, showing a window
  284. like a message box causes a WM_ACTIVATEAPP message to be sent to
  285. Application.Handle, and the VCL strangely responds by setting FAppIconic
  286. to False -- even though the main form is still iconic (minimized). If we
  287. later try to call Application.Restore, nothing happens because it sees
  288. FAppIconic=False. }
  289. Application.Restore;
  290. { Always try to bring the message box to the foreground. Task dialogs appear
  291. to do that by default.
  292. Due to Windows' protections against apps stealing the foreground, the
  293. message box won't actually come to the foreground in most cases. Instead,
  294. the taskbar button will flash. That's really all we need; the user just
  295. needs to be made aware that a message box is awaiting their response.
  296. (Note: Don't run under the debugger when testing because Windows allows
  297. debugged processes to steal the foreground with no restrictions.) }
  298. Flags := Flags or MB_SETFOREGROUND;
  299. if MessageBoxRightToLeft then
  300. Flags := Flags or (MB_RTLREADING or MB_RIGHT);
  301. TriggerMessageBoxCallbackFunc(Flags, False);
  302. try
  303. {$IFDEF USETASKDIALOGFORM}
  304. if not MsgBoxTaskDialogFormActive then begin { Protect against TaskDialogForm calling MsgBox }
  305. MsgBoxTaskDialogFormActive := True;
  306. try
  307. { We don't use MB_ABORTRETRYIGNORE or MB_CANCELTRYCONTINUE, but end users
  308. are liable to in [Code]. TaskDialogForm doesn't support them because
  309. currently we lack the strings needed for the required common buttons. }
  310. const Typ = (Flags and MB_TYPEMASK);
  311. const MB_CANCELTRYCONTINUE = $00000006;
  312. if (Typ <> Cardinal(MB_ABORTRETRYIGNORE)) and (Typ <> Cardinal(MB_CANCELTRYCONTINUE)) then begin
  313. const LStyle = TStyleManager.ActiveStyle;
  314. if not LStyle.IsSystemStyle then begin
  315. var Icon: PChar;
  316. var TDCommonButtons: Cardinal;
  317. var DefCommonButton: Integer;
  318. var SetForeground: Boolean;
  319. { Ignores MB_DEFBUTTON4 (there are never 4 buttons) and MB_RTLREADING+MB_RIGHT
  320. (TaskDialogForm has its own RTL detection) }
  321. MsgBoxFlagsDecode(Flags, Icon, TDCommonButtons, DefCommonButton, SetForeground);
  322. { Note: Shared.TaskDialogFunc also uses TaskDialogForm }
  323. Result := TaskDialogForm('', Text, Caption, Icon, TDCommonButtons, [], [], DefCommonButton, 0,
  324. Flags, '', nil, cfMessageBox, SetForeground);
  325. Exit;
  326. end;
  327. end;
  328. finally
  329. MsgBoxTaskDialogFormActive := False;
  330. end;
  331. end;
  332. {$ENDIF}
  333. { Application.MessageBox uses Application.ActiveFormHandle for the message
  334. box's owner window. If that window is Application.Handle AND it isn't
  335. currently shown on the taskbar [1], the result will be a message box
  336. with no taskbar button -- which can easily get lost behind other
  337. windows. Avoid that by calling MessageBox directly with no owner window.
  338. [1] That is the case when we're called while no forms are visible.
  339. But it can also be the case when Application.MainFormOnTaskBar=True
  340. and we're called while the application isn't in the foreground
  341. (i.e., GetActiveWindow=0). That seems like erroneous behavior on the
  342. VCL's part (it should return the same handle as when the app is in
  343. the foreground), and it causes modal TForms to get the 'wrong' owner
  344. as well. However, it can be worked around using a custom
  345. Application.OnGetActiveFormHandle handler (see IDE.MainForm).
  346. We also use the same MessageBox call when IsIconic(Application.Handle)
  347. is True to work around a separate issue:
  348. 1. Start with Application.MainFormOnTaskBar=False
  349. 2. Minimize the app
  350. 3. While the app is still minimized, call Application.MessageBox
  351. 4. Click the app's taskbar button (don't touch the message box)
  352. At this point, the form that was previously hidden when the app was
  353. minimized is shown again. But it's not disabled! You can interact with
  354. the form despite the message box not being dismissed (which can lead to
  355. reentrancy issues and undefined behavior). And the form is allowed to
  356. rise above the message box in z-order.
  357. The reason the form isn't disabled is that the VCL's DisableTaskWindows
  358. function, which is called by Application.MessageBox, ignores non-visible
  359. windows. Which seems wrong.
  360. When we call MessageBox here with no owner window, we pass the
  361. MB_TASKMODAL flag, which goes further than DisableTaskWindows and
  362. disables non-visible windows too. That prevents the user from
  363. interacting with the form. However, the form can still rise above the
  364. message box. But with separate taskbar buttons for the two windows,
  365. it's easier to get the message box back on top.
  366. (This problem doesn't occur when Application.MainFormOnTaskBar=True
  367. because the main form retains its WS_VISIBLE style while minimized.)
  368. UPDATE: Had to restrict the use of MB_TASKMODAL to only when
  369. MainFormOnTaskBar=False is set to work around *another* VCL issue.
  370. The above problem doesn't affect MainFormOnTaskBar=True so that should
  371. be fine.
  372. Details: When MainFormOnTaskBar=True and MessageBox is called with the
  373. MB_TASKMODAL flag after the main form is created but before the main
  374. form is shown, the message box appears on the screen but you can't
  375. interact with it using the keyboard; keys like Enter and Escape have no
  376. effect. The problem? The CM_ACTIVATE handler in TApplication.WndProc is
  377. calling SetFocus with a NULL window handle. This erroneous SetFocus call
  378. is only reached when the main form window is found to be disabled, which
  379. only happens when MB_TASKMODAL is used. As noted above, non-visible
  380. windows aren't disabled when only DisableTaskWindows is used.
  381. }
  382. if GetOwnerWndForMessageBox = 0 then begin
  383. ActiveWindow := GetActiveWindow;
  384. WindowList := DisableTaskWindows(0);
  385. try
  386. { Note: DisableTaskWindows doesn't disable invisible windows.
  387. MB_TASKMODAL will ensure that Application.Handle gets disabled too. }
  388. if not Application.MainFormOnTaskBar then
  389. Flags := Flags or MB_TASKMODAL;
  390. Result := MessageBox(0, Text, Caption, UINT(Flags));
  391. finally
  392. EnableTaskWindows(WindowList);
  393. SetActiveWindow(ActiveWindow);
  394. end;
  395. Exit;
  396. end;
  397. Result := Application.MessageBox(Text, Caption, Integer(Flags));
  398. finally
  399. TriggerMessageBoxCallbackFunc(Flags, True);
  400. end;
  401. end;
  402. function MsgBox(const Text, Caption: String; const Typ: TMsgBoxType;
  403. const Buttons: Cardinal): Integer;
  404. const
  405. IconFlags: array[TMsgBoxType] of Cardinal =
  406. (MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONEXCLAMATION, MB_ICONSTOP);
  407. begin
  408. var NewCaption := Caption;
  409. if NewCaption = '' then
  410. NewCaption := Application.Title;
  411. Result := MsgBox(PChar(Text), PChar(NewCaption), Buttons or IconFlags[Typ]);
  412. end;
  413. function MsgBoxFmt(const Text: String; const Args: array of const;
  414. const Caption: String; const Typ: TMsgBoxType; const Buttons: Cardinal): Integer;
  415. begin
  416. Result := MsgBox(Format(Text, Args), Caption, Typ, Buttons);
  417. end;
  418. { TWindowDisabler }
  419. const
  420. WindowDisablerWndClassName = 'TWindowDisabler-Window';
  421. var
  422. WindowDisablerWndClassAtom: TAtom;
  423. function WindowDisablerWndProc(Wnd: HWND; Msg: UINT; WParam: WPARAM;
  424. LParam: LPARAM): LRESULT; stdcall;
  425. begin
  426. if Msg = WM_CLOSE then
  427. { If the fallback window becomes focused (e.g. by Alt+Tabbing onto it) and
  428. Alt+F4 is pressed, we must not pass the message to DefWindowProc because
  429. it would destroy the window }
  430. Result := 0
  431. else
  432. Result := DefWindowProc(Wnd, Msg, WParam, LParam);
  433. end;
  434. constructor TWindowDisabler.Create;
  435. const
  436. WndClass: TWndClass = (
  437. style: 0;
  438. lpfnWndProc: @WindowDisablerWndProc;
  439. cbClsExtra: 0;
  440. cbWndExtra: 0;
  441. hInstance: 0;
  442. hIcon: 0;
  443. hCursor: 0;
  444. hbrBackground: COLOR_WINDOW + 1;
  445. lpszMenuName: nil;
  446. lpszClassName: WindowDisablerWndClassName);
  447. begin
  448. inherited Create;
  449. FPreviousActiveWnd := GetActiveWindow;
  450. FPreviousFocusWnd := GetFocus;
  451. FWindowList := DisableTaskWindows(0);
  452. { Create the "fallback" window.
  453. When a child process hides its last window, Windows will try to activate
  454. the top-most enabled window on the desktop. If all of our windows were
  455. disabled, it would end up bringing some other application to the
  456. foreground. This gives Windows an enabled window to re-activate, which
  457. is invisible to the user. }
  458. if WindowDisablerWndClassAtom = 0 then
  459. WindowDisablerWndClassAtom := Windows.RegisterClass(WndClass);
  460. if WindowDisablerWndClassAtom <> 0 then begin
  461. { Create an invisible owner window for the fallback window so that it
  462. doesn't display a taskbar button. (We can't just give it the
  463. WS_EX_TOOLWINDOW style because Windows skips tool windows when searching
  464. for a new window to activate.) }
  465. FOwnerWnd := CreateWindowEx(0, WindowDisablerWndClassName, '',
  466. WS_POPUP or WS_DISABLED, 0, 0, 0, 0, HWND_DESKTOP, 0, HInstance, nil);
  467. if FOwnerWnd <> 0 then begin
  468. FFallbackWnd := CreateWindowEx(0, WindowDisablerWndClassName,
  469. PChar(Application.Title), WS_POPUP, 0, 0, 0, 0, FOwnerWnd, 0,
  470. HInstance, nil);
  471. if FFallbackWnd <> 0 then
  472. ShowWindow(FFallbackWnd, SW_SHOWNA);
  473. end;
  474. end;
  475. { Take the focus away from whatever has it. While you can't click controls
  476. inside a disabled window, keystrokes will still reach the focused control
  477. (e.g. you can press Space to re-click a focused button). }
  478. SetFocus(0);
  479. end;
  480. destructor TWindowDisabler.Destroy;
  481. begin
  482. EnableTaskWindows(FWindowList);
  483. { Re-activate the previous window. But don't do this if GetActiveWindow
  484. returns zero, because that means another application is in the foreground
  485. (possibly a child process spawned by us that is still running). }
  486. if GetActiveWindow <> 0 then begin
  487. if FPreviousActiveWnd <> 0 then
  488. SetActiveWindow(FPreviousActiveWnd);
  489. { If the active window never changed, then the above SetActiveWindow call
  490. won't have an effect. Explicitly restore the focus. }
  491. if FPreviousFocusWnd <> 0 then
  492. SetFocus(FPreviousFocusWnd);
  493. end;
  494. if FOwnerWnd <> 0 then
  495. DestroyWindow(FOwnerWnd); { will destroy FFallbackWnd too }
  496. inherited;
  497. end;
  498. { TControlHelper }
  499. procedure TControlHelper.SetCurrentPPI(const CurrentPPI: Integer);
  500. begin
  501. FCurrentPPI := CurrentPPI;
  502. end;
  503. end.