WM_NCHITTEST 是否应该由 Win10 以每秒 100 次的频率永久生成,即使鼠标处于空闲状态?
Is WM_NCHITTEST supposed to be perpetually generated by Win10, at a frequency of 100's per second, even if mouse is idle?
我遇到 WM_NCHITTEST
条消息的异常行为。
总而言之,一旦我将鼠标悬停在目标(即:Hooked)控件上并让鼠标静止(或空闲),我就会收到 无休止的 每秒数百条 WM_NCHITTEST
条消息。无论我用 WindowProc()
子 class 该控件的 WndProc
还是 覆盖 后代中的 WndProc
方法,都会发生这种情况class(为了简单起见,我在下面的代码中添加了class)。
据我从在线 Win32 API 文档和其他资源中找到的信息,我怀疑此消息是否会以这种频率触发,但我可能是错的。或者可能有一个明显的解释我完全错过了,或者可能 APIs 中发生了一些我不知道的变化。无论如何,我真的很想知道它是什么,或者发生了什么。
我在两个不同的系统上测试了相同的代码(下面的示例),结果相同,尽管两个系统的 Delphi/OS 版本和配置相同。我已经尝试 运行 在 IDE 之外设置应用程序(因此没有调试挂钩),在调试和发布配置中(后者没有调试信息),针对 32 位和 64 位,我总是得到相同的结果。
我正在 Win10 Pro 64 位下使用 Delphi XE7 Enterprise 进行开发,版本 20H2(我相信是最新的 Windows 版本)。
这是一个非常简单的程序来重现我正在经历的事情:一个 TForm
和一个 TPanel
、一个 TCheckBox
和一个 TLabel
。面板是选中复选框时被挂钩的控件,标签显示 WndProc()
方法接收了多少 WM_NCHITTEST
消息:
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.
要重现:运行 应用程序,选中 CheckBox,将鼠标悬停在面板上并使其空闲(静止)。就我而言,我每秒收到 100 条 WM_NCHITTEST
条消息,而且它永远不会停止。这应该发生吗?
有人能解释一下这里发生了什么吗?
我使用 Microsoft Spy++ 工具来查看会发生什么以及何时发生。
它是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('##,##0 messages', FMessageCount);
end;
end.
我遇到 WM_NCHITTEST
条消息的异常行为。
总而言之,一旦我将鼠标悬停在目标(即:Hooked)控件上并让鼠标静止(或空闲),我就会收到 无休止的 每秒数百条 WM_NCHITTEST
条消息。无论我用 WindowProc()
子 class 该控件的 WndProc
还是 覆盖 后代中的 WndProc
方法,都会发生这种情况class(为了简单起见,我在下面的代码中添加了class)。
据我从在线 Win32 API 文档和其他资源中找到的信息,我怀疑此消息是否会以这种频率触发,但我可能是错的。或者可能有一个明显的解释我完全错过了,或者可能 APIs 中发生了一些我不知道的变化。无论如何,我真的很想知道它是什么,或者发生了什么。
我在两个不同的系统上测试了相同的代码(下面的示例),结果相同,尽管两个系统的 Delphi/OS 版本和配置相同。我已经尝试 运行 在 IDE 之外设置应用程序(因此没有调试挂钩),在调试和发布配置中(后者没有调试信息),针对 32 位和 64 位,我总是得到相同的结果。
我正在 Win10 Pro 64 位下使用 Delphi XE7 Enterprise 进行开发,版本 20H2(我相信是最新的 Windows 版本)。
这是一个非常简单的程序来重现我正在经历的事情:一个 TForm
和一个 TPanel
、一个 TCheckBox
和一个 TLabel
。面板是选中复选框时被挂钩的控件,标签显示 WndProc()
方法接收了多少 WM_NCHITTEST
消息:
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.
要重现:运行 应用程序,选中 CheckBox,将鼠标悬停在面板上并使其空闲(静止)。就我而言,我每秒收到 100 条 WM_NCHITTEST
条消息,而且它永远不会停止。这应该发生吗?
有人能解释一下这里发生了什么吗?
我使用 Microsoft Spy++ 工具来查看会发生什么以及何时发生。
它是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('##,##0 messages', FMessageCount);
end;
end.