When Windows Vista was introduced, many moons ago now, many Delphi programs ran into a problem where buttons, checkboxes and the like would disappear when the user pressed the Alt key. This was due to a change in how and when the underscores that displayed shortcuts on these buttons was displayed.
The problem was duly noted on Borland/CodeGear/Embarcadero’s QualityCentral forum, and several workarounds were documented. The one that became most popular online seemed to be the one known as VistaAltFixUnit.pas. Due to its popularity, we ended integrated that unit into our code, without spending a lot of time looking at it. Surely many eyes had already reviewed it?
Now I don’t really want to be harsh but unfortunately there are a number of issues with the unit, ranging from performance to stability issues. Here’s a rundown.
- The most egregious problem is the replacement of each
TForm
‘sWindowProc
procedure.constructor TFormObj.Create(aForm: TForm; aRepaintAll: Boolean); begin inherited Create; Form := aForm; RepaintAll := aRepaintAll; Used := True; OrgProc := Form.WindowProc; Form.WindowProc := WndProc; end; procedure TFormObj.WndProc(var Message: TMessage); begin OrgProc(Message); if (Message.Msg = WM_UPDATEUISTATE) then NeedRepaint := True; end;
Like any naively chained procedure hook, if another component or other code also attempts to replace the
WindowProc
, the two will end up interfering with each other at some point, as neither component will be aware of the other, and destruction of either component, or attempts to unhook, are likely to end in access violations and tears shortly thereafter.Now, we also make use of the TNT Unicode components (as we have not yet moved to a fully Unicode version of Delphi — that’s a big job!), and it turns out that these components also use this approach for some of their own jiggery-pokery. The TNT Unicode components are more important to us than this one… guess which component suite got to keep their hook?
- Note that, possibly due to instabilities that became evident, the
TFormObj
class does not attempt to restore the originalWindowProc
when it is destroyed. - Performance-wise, this component does a lot of work checking form status at application idle time. As other users noted, the
TApplicationEvents
approach only works if no other code in the project has already assignedTApplication.OnIdle
.In the end, we rewrote the component from scratch, and used a Windows
CallWndProc
hook as this is clean, simple and robust. We also opted to live with a very minor flicker when the user first presses Alt, as this reduced the complexity of the code significantly, relying on Windows’ existing repaint infrastructure instead of reimplementing it ourselves.Any thoughts on the changes? What have we got wrong? Feel free to use this code without restrictions, for what it is worth — at your own risk of course!
unit VistaAltFixUnit2; interface uses Windows, Classes; type TVistaAltFix2 = class(TComponent) private FInstalled: Boolean; function VistaWithTheme: Boolean; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; procedure Register; implementation uses Messages, Themes; procedure Register; begin RegisterComponents('MEP', [TVistaAltFix2]); end; var FInstallCount: Integer = 0; FCallWndProcHook: Cardinal = 0; { TVistaAltFix2 } function CallWndProcFunc( nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; var p: PCWPSTRUCT; begin if nCode = HC_ACTION then begin p := PCWPSTRUCT(lParam); if p.message = WM_UPDATEUISTATE then begin InvalidateRect(p.hwnd, nil, False); end; end; Result := CallNextHookEx(FCallWndProcHook, nCode, wParam, lParam); end; constructor TVistaAltFix2.Create(AOwner: TComponent); begin inherited; if VistaWithTheme and not (csDesigning in ComponentState) then begin Inc(FInstallCount); // Allow more than 1 instance, assume single threaded as VCL is not thread safe anyway if FInstallCount = 1 then FCallWndProcHook := SetWindowsHookEx(WH_CALLWNDPROC, CallWndProcFunc, 0, GetCurrentThreadID); FInstalled := True; end; end; destructor TVistaAltFix2.Destroy; begin if FInstalled then begin Dec(FInstallCount); if FInstallCount = 0 then begin UnhookWindowsHookEx(FCallWndProcHook); FCallWndProcHook := 0; end; end; inherited Destroy; end; function TVistaAltFix2.VistaWithTheme: Boolean; var OSVersionInfo: TOSVersionInfo; begin OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo); if GetVersionEx(OSVersionInfo) and (OSVersionInfo.dwMajorVersion >= 6) and ThemeServices.ThemesEnabled then Result := True else Result := False; end; end.