7

With default Windows settings, accelerator keys are not meant to be shown on dialogs until the user presses the ALT key.

Delphi's TLabel control does not obey this convention, as shown below:

Although both label and check box have an accelerator key specified, the check box correctly hides it, but the label does not. Of course, when ALT is pressed, the accelerator shows for the check box, but it's the behaviour prior to that which is incorrect.

My understanding of why this happens is that the VCL code that implements this behaviour is contained in TWinControl, for example the UpdateUIState method, and relies on sending the underlying windowed control a WM_CHANGEUISTATE message. Since TLabel is not windowed, it misses out on this handling.

Can anybody suggest a way to achieve the desired behaviour for non-windowed controls?

Update 1

I've just discovered that group boxes and radio groups don't respond to UI state either.

Update 2

QC#97044.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490

3 Answers3

7

I think I've worked out a way of handling it.

function HideAccelFlag(Control: TControl): Integer;
begin
  //ask the top level window about its UI state
  while Assigned(Control.Parent) do begin
    Control := Control.Parent;
  end;
  if (Control.Perform(WM_QUERYUISTATE, 0, 0) and UISF_HIDEACCEL)=UISF_HIDEACCEL then begin
    Result := DT_HIDEPREFIX;
  end else begin
    Result := 0;
  end;
end;

type
  TUIStateAwareLabel = class(TLabel)
  protected
    procedure DoDrawText(var Rect: TRect; Flags: Longint); override;
  end;

procedure TUIStateAwareLabel.DoDrawText(var Rect: TRect; Flags: Integer);
begin
  if ShowAccelChar then begin
    Flags := Flags or HideAccelFlag(Self);
  end;
  inherited;
end;

I make sure that I always create TUIStateAwareLabel rather than TLabel by hooking the form streaming mechanism with TReader.OnFindComponentClass.

Dealing with TCustomGroupBox descendents is more messy. For them I resorted to copying the source code of TCustomGroupBox.Paint into my descendent and making use of HideAccelFlag again.

Next task is to write it up as a QC report.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • For the *"hook the form streaming mechanism with `TReader.OnFindComponentClass`"*, see [Replacing a component class in delphi](https://stackoverflow.com/q/4685863/850848). – Martin Prikryl Dec 27 '18 at 10:46
2

You could use TStaticText instead of TLabel.

From the doc page:

Use TStaticText instead of TLabel when the component's accelerator key must belong to a windowed control.

Andriy M
  • 76,112
  • 17
  • 94
  • 154
  • +1 The thought had crossed my mind too, but if I recall, there are minor wrinkles when switching. Does it not do auto size as well perhaps? Anyway, it still leaves group box & friends! – David Heffernan Aug 02 '11 at 18:21
  • @David: It does auto size, but, as of Delphi 2010, it doesn't do vertical layout of the caption, nor word wrapping. Also it doesn't have these `TLabel` properties: `EllipsisPosition`, `GlowSize`, `Touch (TTouchManager)`. In Delphi 6 it cannot be transparent, but it can already in Delphi 2006. – Andriy M Aug 03 '11 at 04:48
0

Sorry no code, but maybe a solution direction.

I see TWinControl using NotifyControls to BroadCast a message to all contained controls. It is used to notify controls of changes in the Parent* properties, for example

procedure TWinControl.CMShowHintChanged(var Message: TMessage);
begin
  inherited;
  NotifyControls(CM_PARENTSHOWHINTCHANGED);
end;

I guess what you could do is code a message handler on your form to get the WM_CHANGEUITSTATE and pass it on to all controls parented by the form, using either NotifyControls, or something similar that only passes it to containers and TLabels.

You then still need a way to have your TLabel's' react to that message. I guess (haven't looked into it) that you could either override (descendant or interceptor) the WndProc method and/or do something with FDefWndProc (protected property).

And you will have to cater for frames in forms in ...

Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • Unfortunately, iterating and notifying children is something that I already know how to do. It's the actual suppression of the accelerator and the detection of when to clear the suppression that flumoxes me. – David Heffernan Aug 02 '11 at 14:18
  • @David: and I notice you found a way for the labels... upvoted it. – Marjan Venema Aug 02 '11 at 18:45