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

1

6

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

Punic answered 11/12, 2017 at 14:20 Comment(0)
P
7

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;
Punic answered 11/12, 2017 at 15:34 Comment(10)
Not too surprising... After all, VCL Styles are already a hack in the first place :-)Ultramontanism
This is only half the solution to your question. You can use this to trigger the display of your icon. But there is no corresponding notification when the Edit control would dismiss the tooltip when the user toggles the Caps Lock. So you will have to track the state of the Caps Lock yourself to hide your icon. So you may as well use that to show the icon too, and use this solution just to dismiss the Edit's tooltip.Spirit
@RemyLebeau Right as usual. I have made a descendant of TEdit to do the job an solve my problem in general. Thanks for your commentPunic
This final code could have been much simplified. Also naming of methods there is no good.Cropdusting
@Cropdusting I tend to forget what is the purpose of methods I create, so I always try to name them with details in mind (names get too long and sometimes more confusing). As for simplifying the final code, if you want to add/substract something, please feel free to edit my answer I do not mind at all. :-). Thank you for your comment.Punic
Your WndProc could be simplified: if (Message.Msg = EM_SHOWBALLOONTIP) and FBlockCapsBalloonTip then Exit; inherited;. Also note that this is not the only possible tip. for example try to copy the password with CTRL-C, and you get a tip "Not Allowed...". I'm not sure if there are other and if there is a way to identify which one is shown.Instant
In fact, you can set your own balloon hint for the Edit control via: Edit_ShowBalloonTip. this can be useful to indicate that the input is incorrect (or whatever). so your solution eliminates all balloon hints but I guess that fits your requirements for password Edit.Instant
@Instant Thank you, I have made an edit to my WndProc. As for the "Not Allowed...". balloon hint I made a test and the results are satisfactory. As you said my solution eliminates all balloon hints which is what I want for my TEdit as a behavior. Dealing with all Unicode/localization problems for now seemed to be very time wasting, so for now I will skip it by a visual effect (after all the translation of Caps Lock in arabic is قبعات مقفلة = hats closed !!!!)Punic
As for Edit_ShowBalloonTip, I think it is a great solution to show additional balloon hints for custom warnings. thank you for bringing this up.Punic
@Instant very good approach I added it here on SO. I hope it is ok. see my edit.Punic

© 2022 - 2024 — McMap. All rights reserved.