Watch, Follow, &
Connect with Us
Public Report
Report From: Delphi-BCB/VCL/Standard Controls    [ Add a report in this area ]  
Report #:  37403   Status: Closed
ALT Key press causes controls to disappear under Themes in Vista and XP
Project:  Delphi Build #:  7.0.8.1
Version:    7.0 Submitted By:   Jeff Hamblin
Report Type:  Basic functionality failure Date Reported:  12/6/2006 10:37:49 AM
Severity:    Serious / Highly visible problem Last Updated: 3/20/2012 2:24:39 AM
Platform:    All versions Internal Tracking #:   243408
Resolution: Fixed (Resolution Comments) Resolved in Build: : 11.0.2594.4328
Duplicate of:  None
Voting and Rating
Overall Rating: (23 Total Ratings)
5.00 out of 5
Total Votes: 31
Description
There seems to be a problem with THEMES support in Delphi, in which TButton, TCheckBox, TRadioButton and TStaticText standard controls vanish in VISTA when the ALT key is pressed. (TStaticText vanishes in XP as well in my recent tests). The Delphi application must have a manifest resource or file that enables Common Controls version 6 for this bug to exhibit.

In XP and Vista using the default theme, the default behavior is for accelerator key underscores to be hidden. Pressing the ALT key displays the underline under the accelerator keys. This is where the problem occurs.

When the ALT key is pressed, the control is painted over by DrawThemeParentBackground, but only the underscore for the accelerator key (if there is on) is drawn after that.

The mentioned controls vanish only the first time ALT is pressed for a given form's lifetime. They can be restored by forcing a repaint of the control by minimizing an restoring the form, dragging another window in front of it, moving the mouse over it (not TStaticText), or calling repaint in code.

I have tested this on systems with both nVidia and ATI video cards with same results.
Steps to Reproduce:
1. Drop some TButton, TCheckBox, TRadioButton and TStaticText on a form.
2. Build the application with an XP manifest resource or include a manifest file to enable themes via ComCtrl v6.
3. Run the application in a default installation of Vista or XP (only the TStaticText will be affected in XP).
4. Press the ALT key.

Expected Behavior:
  The underscore under the accelerator character in the caption of controls should appear.

Actual Behavior:
  The affected controls vanish (overpainted with background), while the underscore only is drawn. Anything that forces a repaint will bring them back to normal appearance.
Screenshots are in attachments.

Additional info posted by Chris Burrows (which I confirmed):
As well as the the underscores that remain after the controls vanish, if one of the controls is a focused checkbox its focus rectangle remains after the checkbox (and caption) disappears.

Buttons that are set as 'default' do not disappear unless their DoubleBuffered property is set true.

If a default DoubleBuffered button has disappeared it reappears when you switch to another application. The other ghost controls remain invisible.
Workarounds
One workaround is to create a component to drop on each form which has a method to call via the form's OnKeyDown event. The method checks if it has been called before, if not it checks for ALT key, then launches a timer which fires a single OnTimer event (10 MSec delay is good), which calls a procedure which recursively calls repaint for all controls on the form of the affected type. (thank you goes to Fredrik Nordbakke for suggestion to use timer)

See source in attachments for workaround (see last update note below for best workaround).

Update: When a PageControl is on the form, each time the page is changed to a new tabsheet, pressing the ALT key wipes ALL the affected controls, whether showing on the sheet or form. So I had to eliminate the feature of the workaround that only checks once.

Update (Dec 14 2006): In an MDI application, all open child forms are affected when ALT key is pressed. I uploaded new source for the workaround that iterates the mainform's childcount to repaint controls on them as well.
   There is still an issue to resolve in the workaround noted by per-erik andersson in comments. Pressing TAB or Arrow keys in just about any control (TEdit, TMemo, TStringGrid, etc.) that has focus after pressing ALT key twice (to bring focus back from menu) causes controls to vanish again.

Update (Dec 14 2006) #2: New workaround uploaded in attachments. Handles TAB and Arrow issue noted above.

Update (Dec 18 2006): Per-Erik Andersson came up with a new approach that is MUCH BETTER. It hooks the WM_UPDATEUISTATE message and only requires a single component instance to handle all forms in an application. Source is available at http://cc.codegear.com/item/24282.
Attachment
Temp.zip
Comments

Chris Burrows at 12/6/2006 2:42:20 PM -
The same problem occurs in apps developed using BDS2006

Aleksander Oven at 12/9/2006 2:12:22 PM -
The problem disappears if you compile a new XP manifest and replace WindowsXP.res with it.

Here's the manifest source I used:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="*"
    name="ThemeAwareApplication"
    type="win32"/>
  <dependency>
    <dependentAssembly>
     <assemblyIdentity
       type="win32"
       name="Microsoft.Windows.Common-Controls"
       version="6.0.0.0"
       processorArchitecture="*"
       publicKeyToken="6595b64144ccf1df"
       language="*"/>
    </dependentAssembly>
  </dependency>
</assembly>

Jeff Hamblin at 12/9/2006 11:55:09 PM -
This QC report is based on tests using a brcc32 compiled resource with both themes and UAC sections. It is included in the source files attached to the QC report. I tried your manifest, which is only slightly different from the one I used, and it made no difference -- controls still vanished an ALT key press.

I also tried your method of compiling the manifest resource posted on the newsgroups:

XP_MANIFEST RCDATA ".\XP.manifest"

A test app compiled with a resource built with the above instructions has the resource under RCDATA, and when the app was run under XP and Vista it did not use themes. That is why the controls don't disappear.

From everything I've read, the .rc file needs to be:

1 24 "some.manifest"

Which ends up in a resource viewer as:
XP Theme Manifest
  1
    [manifest entry here]

So, unless I missed something, I don't believe this is a fix.

Per-Erik Andersson at 12/14/2006 6:26:11 AM -
When I test the workaround in your application, I still get some problem. With the memo control focused, press Alt and everything looks fine. Pressing Tab twice (to go to another control) after that makes the control disappear again.

Jeff Hamblin at 12/14/2006 12:27:21 PM -
Thanks for catching this, per-erik!

Yes, I see the behavior you describe. And in my real-world application I am readying for Vista release it is even worse. Pressing TAB in just about any control that has focus (TMemo, TEdit, TStringGrid, ???) after pressing ALT key causes affected controls to vanish again! arghhh -- this is such an ugly bug!

I will put a fix for this in the workaround code and post it as soon as possible.

Jeff Hamblin at 12/14/2006 2:25:06 PM -
... even more: arrow keys also cause same problem as TAB. Probably because ALT, TAB and arrow keys cause the TWinControl.UpdateUIState to call Perform(WM_CHANGEUISTATE...

Jeff Hamblin at 12/14/2006 4:15:37 PM -
Just to be clear -- this issue with the TAB and Arrow keys was not a bug IN the workaround. It is an issue related to the ALT key bug that needed further work to workaround.

Jeff Hamblin at 12/14/2006 4:13:08 PM -
Fixes for TAB and Arrow keys issue uploaded to attachments. See Workaround for more info.

Per-Erik Andersson at 12/17/2006 7:34:53 AM -
I have added a component with a different approach to solving the problem in the download area http://cc.codegear.com/item/24282.
It only requires to be added once on the mainform, and no modification is needed on other forms.

Christo Carstens at 7/6/2009 3:44:36 PM -
Thanks for the fix! This needs to be added to avoid AVs though.

destructor TFormObj.Destroy;
begin
  // Restore the changes we made to avoid AV
  if Assigned(Form) then
    Form.WindowProc := OrgProc;
  inherited Destroy;
end;

Erik Ejveg#rd at 9/13/2007 4:13:45 AM -
This component dosen't work with custom windows. The code uses TForm and not TCustomForm. I have made the change and it works with my custom dialogs and forms.

Warren Kovach at 1/25/2007 10:12:41 PM -
This is indeed a much easier way to tackle this bug. However, there is one problem. If you app uses Application.OnIdle to do other things during idle time then the TApplicationEvents object within this component is disabled and the fix doesn't work.

I fixed this by simply moving my Application.OnIdle code to the OnIdle event handler of a new TApplicationEvents component that I dropped on my main form.

Jeff Hamblin at 12/18/2006 10:45:10 AM -
Excellent! Thank you for posting the source to your workaround. I spent the morning testing it and it seems to work great! I will update the notes in the Workaround section to point people to your component.

Chris Burrows at 12/18/2006 5:29:37 PM -
A possible alternative solution to the workarounds so far (and inspired by the comments there) involves linking a modified copy of vcl\Controls.pas into your app. Consequently, it will not be suitable for those who have to distribute their apps with the standard Borland runtime packages. The following changes relate to the version of Control.pas supplied with BDS2006 Pro, Update 2.

The two procedures affected are:

=====================================

1. procedure TWinControl.CMInvalidate

Change from:

for I := 0 to ControlCount - 1 do
  if csParentBackground in Controls[I].ControlStyle then
    Controls[I].Invalidate;
    
Change to:

for I := 0 to ControlCount - 1 do
  Controls[I].Invalidate;

=====================================

2. procedure TWinControl.UpdateUIState(CharCode: Word);

Change from:

  if Assigned(Form) then
    case CharCode of
      VK_LEFT..VK_DOWN, VK_TAB:
        Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEFOCUS), 0);
      VK_MENU:
        Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEACCEL), 0);
    end;

Change to:

  if Assigned(Form) then begin
    case CharCode of
      VK_LEFT..VK_DOWN, VK_TAB:
        Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEFOCUS), 0);
      VK_MENU:
        Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEACCEL), 0);
    end;
    Form.Invalidate
  end

=====================================

I'm not sure if these changes would have any unintended negative consequences - I'd like to be advised if they do.

Andrey Filatkin at 12/19/2006 2:22:01 AM -
Thank you! This work.
But form is blinking, if you keep ALT or TAB button pressed. My modification is:

procedure TWinControl.UpdateUIState(CharCode: Word);
var
  Form: TCustomForm;
begin
  Form := GetParentForm(Self);
  if Assigned(Form) then begin
    case CharCode of
      VK_LEFT..VK_DOWN, VK_TAB: begin
        if (Win32MajorVersion >= 6) and (Win32Platform = VER_PLATFORM_WIN32_NT) then begin
          if (Form.Perform(WM_QUERYUISTATE, 0, 0) and UISF_HIDEFOCUS) = UISF_HIDEFOCUS then begin
            Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEFOCUS), 0);
            Form.Invalidate;
          end;
        end else
          Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEFOCUS), 0);
      end;
      VK_MENU: begin
        if (Win32MajorVersion >= 6) and (Win32Platform = VER_PLATFORM_WIN32_NT) then begin
          if ((Form.Perform(WM_QUERYUISTATE, 0, 0) and UISF_HIDEACCEL) = UISF_HIDEACCEL) then begin
            Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEACCEL), 0);
            Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEFOCUS), 0);
            Form.Invalidate;
          end;
        end else
          Form.Perform(WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEACCEL), 0);
      end;
    end;
  end;
end;

Craig Stuntz at 12/19/2006 6:09:32 AM -
Wouldn't it make more sense to test whether themes are enabled explicitly (ThemeServices.ThemesEnabled) than to test Win32MajorVersion, etc.?

Andrey Filatkin at 12/20/2006 12:28:21 AM -
I think what test of Win32MajorVersion is needs, because WM_QUERYUISTATE message was introduced only in Windows 2000. Test of ThemeServices.ThemesEnabled is optional, it have not effect on anything.

Craig Stuntz at 1/24/2007 7:55:40 AM -
Themes can never be enabled pre-2000, so no worries there.

However, without testing ThemesEnabled, you're changing behavior which isn't buggy (i.e., an un-themed app in WinXP).

Sebastian Zierer at 2/5/2007 12:37:32 AM -
The following patch to StdCtrls.pas seems to work excellent:

procedure TCustomCheckBox.CreateParams(var Params: TCreateParams);
[...]
begin
  inherited CreateParams(Params);
  if CheckWin32Version(5, 1) then
    Params.ExStyle := Params.ExStyle or WS_EX_COMPOSITED;

procedure TButton.CreateParams(var Params: TCreateParams);
[...]
begin
  inherited CreateParams(Params);
  if CheckWin32Version(5, 1) then
    Params.ExStyle := Params.ExStyle or WS_EX_COMPOSITED;

Eric Fortier at 2/8/2007 7:06:44 PM -
Using WS_EX_COMPOSITED effectively double-buffers all your forms and could have side-effects that should be considered/tested before deploying any apps.

See this post by Steve Trefethen:

http://feeds.feedburner.com/~r/SteveTrefethensWeblog/~3/86660348/UsingTheWSEXCOMPOSITEWindowStyleToEliminateFlickerOnWindowsXP.aspx

Sebastian Zierer at 2/10/2007 12:32:16 AM -
That is why I set this flag on the CheckBoxes / Buttons only. Do you know about any other side effects in this case?

lin zhenqun at 2/9/2007 12:51:55 AM -
It really is a ugly BUG,I hoped it can be fixed as soon as possible

Frederik Slijkerman at 2/15/2007 1:49:04 AM -
The workarounds suggested so far deal only with how to invalidate the button controls after they have disappeared. Here's what happens behind the scenes so we can try to fix the underlying problem.

TButtonControl in StdCtrls, which is the base class for TButton and TCheckBox etc, overrides CN_CTLCOLORSTATIC and draws the parent background there. (Since this handler is invoked from the WM_CTLCOLORSTATIC handler of the parent, it should really just return a brush handle.)

On Vista, on encountering a WM_UPDATEUISTATE message, e.g. a checkbox sends WM_CTLCOLORSTATIC to its parent to update its text color, and then it only draws the accelerator lines below the existing text (presumably with DrawText and the DT_PREFIXONLY flag). Because WM_CTLCOLORSTATIC erases the entire background, only the accelerator lines remain.

So, a better fix would be to change TButtonControl.CNCtlColorStatic like this:

if ThemeServices.ThemesEnabled and IsWindowsVista then
  Message.Result := GetStockObject(HOLLOW_BRUSH)
else begin
  << OLD CODE GOES HERE >>
end;

This works because themed controls on Vista recognize and work with the hollow brush correctly. On XP however, they will draw a black background this way, and that's when the old code kicks in.

Note: The best fix is to not draw anything, but return a bitmap brush that contains the parent background, and this is what property sheets on XP do. You can retrieve the background bitmap dimensions with GetThemePartSize, then draw into this bitmap with DrawThemeBackground. (Interestingly, with the Royale theme on XP, the page control body bitmap is 600 pixels high, and if you try to create a property sheet on XP with a height > 600 pixels, it will draw incorrectly because of this.)

Jan Goyvaerts at 2/20/2007 6:03:25 PM -
> Yes, I patched my copy of the D7 VCL source accordingly and it works
> very well. For TButton, you also need to give CN_CTLCOLORBTN the same
> treatment. So...
>
> TButtonControl, TStaticText: CN_CTLCOLORSTATIC
> TButton: CN_CTLCOLORBTN as well (static via TButtonControl)

I applied these changes to the D7 and D2006 VCL source.
Your solution seems to be working just fine.

Hanno Nagland at 2/21/2008 1:32:56 PM -
This solution works perfectly for Delphi 6 / C++ Builder 6 + Michael Lischke ThemeManager, thanks. Anyway, rebuilding this separated package is much easier than rebuilding VCL parts...

Tip: MSDN says that NULL_BRUSH and HOLLOW_BRUSH are equal.

Eric Fortier at 2/16/2007 9:01:16 AM -
The solution you provide is the most simple so far in term of code, but personally I would prefer staying away from conditional code that checks which version it is running under.

IMHO, this is not the best way in going forward for the VCL. At least "IsWindowsVersionGreaterThanXP" would be slightly better.

While I currently don't see it, there might be a way around this problem without the need to check for versions or handling stuff in the idle handler of the app. At least I hope there is!

Then again, I'm just a nobody ;)

Server Response from: ETNACODE01