Search code examples
delphicustom-controlsvcl

Show custom control hint when disabled


I've written a custom control (TCustomControl) which shows the standard built-in hint on hovering. However, when the control is disabled, the hint does not show. But, the TSpeedButton does show a hint when it's disabled, so there must be a way I can do the same in my control.

What do I need to do to show hints when my control is disabled?


Solution

  • You need to enable the window handle in order to get a WM_MOUSEMOVE which starts showing the hint. This has some implications.

    First, to enable the window handle (WinAPI), you need to delete the WS_DISABLED style from the window style, or use EnableWindow. This modification does not synchronize the VCL's Enabled property (unlike the other way around: setting the Enabled property dóes call EnableWindow), which is why this works.

    But enabling the window handle lets all mouse messages through, so you have to block them and activate the hint manually on WM_MOUSEMOVE:

    type
      TMyControl = class(TCustomControl)
      private
        FDisabledHint: Boolean;
        procedure CheckEnabled;
        procedure SetDisabledHint(Value: Boolean);
        procedure CMEnabledchanged(var Message: TMessage);
          message CM_ENABLEDCHANGED;
      protected
        procedure CreateParams(var Params: TCreateParams); override;
        procedure SetParent(AParent: TWinControl); override;
        procedure WndProc(var Message: TMessage); override;
      published
        property DisabledHint: Boolean read FDisabledHint write SetDisabledHint;
      end;
    
    { TMyControl }
    
    procedure TMyControl.CheckEnabled;
    begin
      if DisabledHint and HasParent and (not Enabled) and
          not (csDesigning in ComponentState) then
        EnableWindow(Handle, True);
    end;
    
    procedure TMyControl.CMEnabledchanged(var Message: TMessage);
    begin
      inherited;
      CheckEnabled;
    end;
    
    procedure TMyControl.CreateParams(var Params: TCreateParams);
    begin
      inherited CreateParams(Params);
      if DisabledHint and not Enabled then
        Params.Style := Params.Style and (not WS_DISABLED);
    end;
    
    procedure TMyControl.SetDisabledHint(Value: Boolean);
    begin
      if FDisabledHint <> Value then
      begin
        FDisabledHint := Value;
        CheckEnabled;
      end;
    end;
    
    procedure TMyControl.SetParent(AParent: TWinControl);
    begin
      inherited SetParent(AParent);
      CheckEnabled;
    end;
    
    procedure TMyControl.WndProc(var Message: TMessage);
    begin
      if not Enabled and DisabledHint and (Message.Msg = WM_MOUSEMOVE) then
        Application.HintMouseMessage(Self, Message);
      if Enabled or (Message.Msg < WM_MOUSEFIRST) or
          (Message.Msg > WM_MOUSELAST) then
        inherited WndProc(Message);
    end;
    

    I checked the working of the TabStop property, and this solution does not interfere with it. But beware of issues which I have not thought of yet.

    (Besides, why a disabled TControl shows a hint is because it receives a CM_MOUSEENTER from WndProc of its parent, despite of that same parent blocking all other mouse input via IsControlMouseMsg to prevent the mouse events from firing.)