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.
Thanks for this improved code, I’m using Delphi 7 and just came across this problem. Have you encountered any problems using this code or are there any changes to it ?
Hi Mike, can you repost your question on my live blog at http://marc.durdin.net/2012/01/revisiting-vistaaltfixunitpas-code.html and I’ll reply there for everyone’s benefit? Sorry to be a pain, but this version of the blog is in test mode, and I am about to take it offline…
Thank you – it givess such a good feeling to see that others come to the same conclusions as I do. Right now I am tracing AV bugs on shutdown in my application and VistaAltFixUnit really seems to mess things up. TFormObj object are not freed either, are they?
I think TFormObj instances were held in a TObjectList, which frees its member items when they are removed from the list or the list itself is destroyed.
Thanks for sharing, it seems a more clean method than the original indeed… however… Is there any special reason why you set:
InvalidateRect(p.hwnd, nil, False);
and not:
InvalidateRect(p.hwnd, nil, True);
just for performance? however I did found a small issue with that, if cleartype is enabled then text captions will become “blurry” after the code is fired… apparently setting it to True (to invalidate background) solves it. If you aware of any possible disadvantage please let me know.
Yes, the reason it was set to false was primarily to reduce flicker. Note: you shouldn’t need this unit with any relatively recent version of Delphi.