Search code examples
delphidelphi-10-seattle

How to override the call to show the CapsLock hint window in a TEdit?


Basically I have this problem: CapsLock password message in TEdit visually fails with VCL Styles.

What I want to do is not to solve the problem as shown in the answer or the comments.

I want to disable that ugly hint window entirely. and instead show an image letting the user know that the caps are locked.

like this

enter image description here


Solution

  • I found the solution to my problem, It involves a hack that I would rather not use.

    It goes like this.

    1. Override WndProc.

    code

    type
      TEdit = class (Vcl.StdCtrls.TEdit)
      protected
        procedure WndProc(var Message: TMessage); override;
      end;
    
    1. Intercept the EM_SHOWBALLOONTIPmessage and you are done

    code

    procedure TEdit.WndProc(var Message: TMessage);
    begin
     if Message.Msg = EM_SHOWBALLOONTIP then
       showmessage('Do your thing.')
     else
      inherited;
    end;
    

    For more information check the MSDN documentation:

    How do I suppress the CapsLock warning on password edit controls?


    This is a descendant of TEdit that would allow to suppress the CapsLock warning on password edit controls, if a certain FOnPasswordCaps events are assigned with PasswordChar <> #0

    unit NCREditUnit;
    
    interface
    
    uses
      Vcl.StdCtrls,
      vcl.Controls,
      Winapi.Messages,
      System.Classes;
    
    type
      TNCREdit = class(TEdit)
      private
        FOnPasswordCapsLocked: TNotifyEvent;
        FIsCapsLocked: boolean;
        FOnPasswordCapsFreed: TNotifyEvent;
        FBlockCapsBalloonTip: boolean;
        FValuePasswordChrOnCaps: boolean;
        procedure SetOnPasswordCapsEvents;
        procedure SetOnPasswordCapsFreed(const aValue: TNotifyEvent);
        procedure SetOnPasswordCapsLocked(const aValue: TNotifyEvent);
      protected
        procedure WndProc(var Message: TMessage); override;
        procedure KeyUp(var Key: Word; Shift: TShiftState); override;
        procedure DoEnter; override;
        procedure DoExit; override;
      published
        property BlockCapsBalloonTip: boolean read FBlockCapsBalloonTip write FBlockCapsBalloonTip default False;
        property ValuePasswordChrOnCaps: boolean read FValuePasswordChrOnCaps write FValuePasswordChrOnCaps default True;
    
    //... The usual property declaration of TEdit
    
        property OnPasswordCapsLocked: TNotifyEvent read FOnPasswordCapsLocked write SetOnPasswordCapsLocked;
        property OnPasswordCapsFreed: TNotifyEvent read FOnPasswordCapsFreed write SetOnPasswordCapsFreed;
      end;
    
    
    implementation
    
    uses
      Winapi.CommCtrl,
      Winapi.Windows;
    
    { TNCREdit }
    
    procedure TNCREdit.DoEnter;
    begin
      inherited;
      if FBlockCapsBalloonTip then
        begin
          FIsCapsLocked := Odd(GetKeyState(VK_CAPITAL));
          SetOnPasswordCapsEvents;
        end;
    end;
    
    procedure TNCREdit.DoExit;
    begin
      if FBlockCapsBalloonTip and (FIsCapsLocked) then
        begin
          FIsCapsLocked := False;
          SetOnPasswordCapsEvents;
        end;
      inherited;
    end;
    
    procedure TNCREdit.KeyUp(var Key: Word; Shift: TShiftState);
    begin
      if Key = VK_CAPITAL then
        FIsCapsLocked := not FIsCapsLocked;
      SetOnPasswordCapsEvents;
      inherited;
    end;
    
    procedure TNCREdit.SetOnPasswordCapsEvents;
    begin
      if FIsCapsLocked then
        begin
          if Assigned(FOnPasswordCapsLocked) and
             ((self.PasswordChar <> #0) or ( not FValuePasswordChrOnCaps)) then
          begin
          FOnPasswordCapsLocked(Self);
          end;
        end
      else
        begin
          if Assigned(FOnPasswordCapsLocked) and
             ((self.PasswordChar <> #0) or ( not FValuePasswordChrOnCaps)) then
          begin
          FOnPasswordCapsFreed(Self);
          end;
        end;
    end;
    
    procedure TNCREdit.SetOnPasswordCapsFreed(const aValue: TNotifyEvent);
    begin
      FOnPasswordCapsFreed := aValue;
      FBlockCapsBalloonTip := True;
    end;
    
    procedure TNCREdit.SetOnPasswordCapsLocked(const aValue: TNotifyEvent);
    begin
      FOnPasswordCapsLocked := aValue;
      FBlockCapsBalloonTip := True;
    end;
    
    procedure TNCREdit.WndProc(var Message: TMessage);
    begin
      if (Message.Msg = EM_SHOWBALLOONTIP) and FBlockCapsBalloonTip then Exit; 
      inherited;
    end;
    
    end.
    

    Mr Kobik made a very elegant piece of code that I think PasteBin should not be trusted to host, so I decided to add it here.

    From what I understood it lets you handle TPasswordCapsLockState in one event handler that is fired when the TPasswordEdit receives focus, loses focus, CapsLock key pressed while on focus and an optional firing when PasswordChar is changed.

    Using this approach I could use the OnPasswordCapsLock event to show/hide the image in my question instead of forcing the consumer of the component to use two event handlers for each state (very clever by the way and less error prone).

    also as long as LNeedHandle := FBlockCapsBalloonTip and IsPassword; is True I have another added feature to TPasswordEdit which is the handling of OnEnter and OnExit in OnPasswordCapsLock as well,

    So what can I say Mr Kobik Je vous tire mon chapeau.

    type
      TPasswordCapsLockState = (pcsEnter, pcsExit, pcsKey, pcsSetPasswordChar);
    
      TPasswordCapsLockEvent = procedure(Sender: TObject;
        Locked: Boolean; State: TPasswordCapsLockState) of object;
    
      TPasswordEdit = class(TCustomEdit)
      private
        FIsCapsLocked: boolean;
        FBlockCapsBalloonTip: boolean;
        FOnPasswordCapsLock: TPasswordCapsLockEvent;
      protected
        procedure WndProc(var Message: TMessage); override;
        procedure KeyUp(var Key: Word; Shift: TShiftState); override;
        procedure DoEnter; override;
        procedure DoExit; override;
        procedure HandlePasswordCapsLock(State: TPasswordCapsLockState); virtual;
        function GetIsPassword: Boolean; virtual;
      public
        property IsPassword: Boolean read GetIsPassword;
      published
        property BlockCapsBalloonTip: boolean read FBlockCapsBalloonTip write FBlockCapsBalloonTip default False;
    //... The usual property declaration of TEdit
        property OnPasswordCapsLock: TPasswordCapsLockEvent read FOnPasswordCapsLock write FOnPasswordCapsLock;
      end;
    
    implementation
    
    function TPasswordEdit.GetIsPassword: Boolean;
    begin
      Result := ((PasswordChar <> #0) or
       // Edit control can have ES_PASSWORD style with PasswordChar == #0
       // if it was creaed with ES_PASSWORD style
       (HandleAllocated and (GetWindowLong(Handle, GWL_STYLE) and ES_PASSWORD <> 0)));
    end;
    
    procedure TPasswordEdit.HandlePasswordCapsLock;
    var
      LNeedHandle: Boolean;
    begin
      LNeedHandle := FBlockCapsBalloonTip and IsPassword;
      if LNeedHandle then
      begin
        FIsCapsLocked := Odd(GetKeyState(VK_CAPITAL));
        if Assigned(FOnPasswordCapsLock) then
          FOnPasswordCapsLock(Self, FIsCapsLocked, State);
      end;
    end;
    
    procedure TPasswordEdit.DoEnter;
    begin
      inherited;
      HandlePasswordCapsLock(pcsEnter);
    end;
    
    procedure TPasswordEdit.DoExit;
    begin
      inherited;
      HandlePasswordCapsLock(pcsExit);
    end;
    
    procedure TPasswordEdit.KeyUp(var Key: Word; Shift: TShiftState);
    begin
      inherited;
      if Key = VK_CAPITAL then
        HandlePasswordCapsLock(pcsKey);
    end;
    
    procedure TPasswordEdit.WndProc(var Message: TMessage);
    begin
      if (Message.Msg = EM_SHOWBALLOONTIP) and FBlockCapsBalloonTip and IsPassword then
        Exit;
      // Optional - if password char was changed
      if (Message.Msg = EM_SETPASSWORDCHAR) and Self.Focused then
        HandlePasswordCapsLock(pcsSetPasswordChar);
      inherited;
    end;