WM_NCHITTEST 是否应该由 Win10 以每秒 100 次的频率永久生成,即使鼠标处于空闲状态?

问题描述

我遇到了 WM_NCHITTEST 消息的奇怪行为。

总而言之,一旦我将鼠标悬停在目标(即:Hooked)控件上并保持鼠标静止(或空闲),我会不断收到数百个{{ 1}} 条消息每秒。无论我使用 WM_NCHITTEST 对该控件的 WndProc 进行子类化,还是在子类中覆盖 WindowProc() 方法(我在下面的代码中子类化),都会发生这种情况为简单起见)。

就我从在线 Win32 API 文档和其他来源中找到的信息而言,我怀疑此消息是否以这种频率触发,但我可能是错的。或者可能有一个我完全错过的明显解释,或者我不知道的 API 发生了一些变化。无论如何,我真的很想知道它是什么,或者正在发生什么。

我在两个不同的系统上测试了相同的代码(下面的示例),结果相同,尽管两个系统都在相同的 Delphi/OS 版本和配置中。我尝试在 IDE 之外运行应用程序(因此没有调试挂钩),在调试和发布配置(后者没有调试信息)中,针对 32 位和 64 位,我总是得到相同的结果。

我正在 Win10 Pro 64 位版本 20H2(我认为是最新的 Windows 版本)下使用 Delphi XE7 Enterprise 进行开发。

这是一个非常简单的程序来重现我所经历的:一个 WndProc 带有一个 TForm、一个 TPanel 和一个 TCheckBox。面板是勾选复选框时被挂钩的控件,标签显示TLabel方法接收了多少WM_NCHITTEST消息:

WndProc()

要重现:运行应用程序,选中 CheckBox,将鼠标悬停在面板上并让它保持空闲(静止)。就我而言,我每秒收到 100 条 unit Unit5; interface uses Winapi.Windows,Winapi.Messages,System.SysUtils,System.Variants,System.Classes,Vcl.Graphics,Vcl.Controls,Vcl.Forms,Vcl.Dialogs,Vcl.ExtCtrls,Vcl.StdCtrls; type TForm5 = class(TForm) CheckBox1: TCheckBox; Label1: TLabel; Panel1: TPanel; procedure FormDestroy(Sender: TObject); procedure CheckBox1Click(Sender: TObject); private FHookedCtrl: TControl; FHookedCtrlWndProc: TWndMethod; FMessageCount: Integer; procedure SetHookedCtrl(const Value: TControl); public procedure ControlWndProc(var Message: TMessage); property HookedCtrl: TControl read FHookedCtrl write SetHookedCtrl; end; var Form5: TForm5; implementation {$R *.dfm} { TForm5 } procedure TForm5.CheckBox1Click(Sender: TObject); begin //checkbox activates or deactivates the hook if CheckBox1.Checked then //hook the panel's WndProc by subclassing HookedCtrl := Panel1 //release the hook on WndProc else HookedCtrl := nil; end; procedure TForm5.ControlWndProc(var Message: TMessage); begin case Message.Msg of WM_NCHITTEST: begin //show how many messages received with the label's caption Inc(FMessageCount); Label1.Caption := FormatFloat('##,##0 messages',FMessageCount); end; end; //not really handling the messsage,just counting. FHookedCtrlWndProc(Message); end; procedure TForm5.FormDestroy(Sender: TObject); begin //make sure to clear the hook if assigned HookedCtrl := nil; end; procedure TForm5.SetHookedCtrl(const Value: TControl); begin if (Value <> FHookedCtrl) then begin if Assigned(FHookedCtrl) then begin //release the hook FHookedCtrl.WindowProc := FHookedCtrlWndProc; FHookedCtrlWndProc := nil; FMessageCount := 0; end; FHookedCtrl := Value; if Assigned(FHookedCtrl) then begin //hook the panel (i.e. Value) FHookedCtrlWndProc := FHookedCtrl.WindowProc; FHookedCtrl.WindowProc := ControlWndProc; end; end; end; end. 消息,而且它永远不会停止。这应该发生吗?

有人能解释一下这里发生了什么吗?

解决方法

我使用 Artificial Beings: the Conscience of a Conscious Machine 工具查看会发生什么以及何时发生。

它是 WM_NCHITTEST 处理程序中的以下行

Label1.Caption := FormatFloat('##,##0 messages',FMessageCount);

导致问题的原因。当您删除它时,不再有所有这些 WM_NCHITTEST 消息。要查看消息数量,请使用间隔为 1 秒的 TTimer 并在标签中显示消息计数。您会看到每次计时器触发时都会收到一个 WM_NCHITTEST(如果您有一个空的 OnTimer 处理程序,您仍然会收到一条消息),当然还有鼠标移动时。

这是我使用的代码:

unit Unit5;


interface

uses
  Winapi.Windows,Winapi.Messages,System.SysUtils,System.Variants,System.Classes,Vcl.Graphics,Vcl.Controls,Vcl.Forms,Vcl.Dialogs,Vcl.ExtCtrls,Vcl.StdCtrls;

type
  TForm5 = class(TForm)
    Label1: TLabel;
    CheckBox1: TCheckBox;
    Panel1: TPanel;
    Timer1: TTimer;
    procedure FormDestroy(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    FHookedCtrl: TControl;
    FHookedCtrlWndProc: TWndMethod;
    FMessageCount: Integer;
    procedure SetHookedCtrl(const Value: TControl);
  public
    procedure ControlWndProc(var Message: TMessage);
    property HookedCtrl: TControl read FHookedCtrl write SetHookedCtrl;
  end;

var
  Form5: TForm5;

implementation

{$R *.dfm}

{ TForm5 }

procedure TForm5.CheckBox1Click(Sender: TObject);
begin
  //checkbox activates or deactivates the hook
  if CheckBox1.Checked then
    //hook the panel's WndProc by subclassing
    HookedCtrl := Panel1
  else
    //release the hook on WndProc
    HookedCtrl := nil;
end;

procedure TForm5.ControlWndProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_NCHITTEST:
        //Count how many messages received
        Inc(FMessageCount);
  end;
  //not really handling the messsage,just counting.
  FHookedCtrlWndProc(Message);
end;

procedure TForm5.FormDestroy(Sender: TObject);
begin
  //make sure to clear the hook if assigned
  HookedCtrl := nil;
end;

procedure TForm5.SetHookedCtrl(const Value: TControl);
begin
  if (Value <> FHookedCtrl) then begin
    if Assigned(FHookedCtrl) then begin
      //release the hook
      FHookedCtrl.WindowProc := FHookedCtrlWndProc;
      FHookedCtrlWndProc := nil;
      FMessageCount := 0;
    end;
    FHookedCtrl := Value;
    if Assigned(FHookedCtrl) then begin
      //hook the panel (i.e. Value)
      FHookedCtrlWndProc := FHookedCtrl.WindowProc;
      FHookedCtrl.WindowProc := ControlWndProc;
    end;
  end;
end;

procedure TForm5.Timer1Timer(Sender: TObject);
begin
   // Show how many message received
   Label1.Caption := FormatFloat('##,FMessageCount);
end;

end.

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...