【问题标题】:How to override the call to show the CapsLock hint window in a TEdit?如何覆盖调用以在 TEdit 中显示 CapsLock 提示窗口?
【发布时间】:2018-05-25 02:16:32
【问题描述】:

基本上我有这个问题:CapsLock password message in TEdit visually fails with VCL Styles.

我要做的不是解决答案或cmets中显示的问题。

我想完全禁用那个丑陋的提示窗口。而是显示一张图片,让用户知道大写字母已锁定。

喜欢这个

【问题讨论】:

    标签: delphi delphi-10-seattle


    【解决方案1】:

    我找到了我的问题的解决方案,它涉及一个我不想使用的 hack。

    事情是这样的。

    1. 覆盖 WndProc。

    代码

    type
      TEdit = class (Vcl.StdCtrls.TEdit)
      protected
        procedure WndProc(var Message: TMessage); override;
      end;
    
    1. 拦截EM_SHOWBALLOONTIPmessage 就完成了

    代码

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

    有关更多信息,请查看 MSDN 文档:

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


    这是 TEdit 的后代,如果某些 FOnPasswordCaps 事件分配有 PasswordChar <> #0,则允许禁止密码编辑控件上的 CapsLock 警告

    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.
    

    Kobik 先生编写了一段非常优雅的代码,我认为 PasteBin 不应该被托管,所以我决定在这里添加它。

    据我了解,它可以让您在一个事件处理程序中处理TPasswordCapsLockState,当TPasswordEdit 获得焦点、失去焦点、在焦点上按下CapsLock 键以及PasswordChar 更改时可选的触发时触发。

    使用这种方法,我可以使用OnPasswordCapsLock 事件来显示/隐藏我的问题中的图像,而不是强制组件的使用者为每个状态使用两个事件处理程序(顺便说一句非常聪明,而且不容易出错) .

    只要LNeedHandle := FBlockCapsBalloonTip and IsPassword;True 我还有另一个添加到TPasswordEdit 的功能,也就是在OnPasswordCapsLock 中处理OnEnterOnExit

    那么 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;
    

    【讨论】:

    • 这只是解决您问题的一半。您可以使用它来触发图标的显示。但是当用户切换 Caps Lock 时 Edit 控件将关闭工具提示时,没有相应的通知。因此,您必须自己跟踪 Caps Lock 的状态才能隐藏您的图标。因此,您也可以使用它来显示图标,并使用此解决方案来关闭编辑的工具提示。
    • 这个最终代码可以大大简化。方法的命名也没有什么好处。
    • @Victoria 我倾向于忘记我创建方法的目的是什么,所以我总是尝试在命名它们时牢记细节(名称太长,有时更令人困惑)。至于简化最终代码,如果您想添加/减去某些内容,请随时编辑我的答案,我一点也不介意。 :-)。感谢您的评论。
    • 您的WndProc 可以简化为:if (Message.Msg = EM_SHOWBALLOONTIP) and FBlockCapsBalloonTip then Exit; inherited;。另请注意,这不是唯一可能的提示。例如尝试使用 CTRL-C 复制密码,你会得到一个提示 "Not Allowed...". 我不确定是否还有其他方法,是否有办法识别显示的是哪一个。
    • 事实上,你可以通过Edit_ShowBalloonTip为Edit控件设置你自己的气球提示。这对于指示输入不正确(或其他)很有用。因此您的解决方案消除了所有气球提示,但我想这符合您对密码编辑的要求。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-05
    相关资源
    最近更新 更多