Delphi - 与 TButton 相比,点击 TImage 的速度较慢
Delphi - Rate of clicking TImage slow compared with TButton
我有一个同时带有 TImage 和 TButton 控件的窗体。我注意到 TImage 对 OnClick 事件的响应速度似乎有点慢(快速点击!)所以我测量了它。对于 100 多次点击(并尽可能快地点击,使每个控件的速率尽可能保持一致),我得到了指标:
TButton:平均 ~105-116ms
TImage:平均 ~220-235ms
我重复了几次,结果相似。为什么 TImage 处理点击的速度大约是 TButton 的一半?处理从 WM_LBUTTON_DOWN 到 OnClick 事件的 Windows 消息队列会不会更慢?如果它们在上一次点击的 N 毫秒内,它可能正在吞噬点击?
TImage 的属性中似乎没有任何影响此的内容。
注意:如果相关,请在此处使用 Delphi 7 和标准 VCL 控件。
编辑:这里有一些示例代码演示了我是如何计时的:
// Define variables (in class definition)
m_dwBtnClicks, m_dwImgClicks: DWORD;
m_dwLastBtnClickTicks, m_dwLastImgClickTicks: DWORD;
m_fTotalBtnClicksTicks, m_fTotalImgClicksTicks: Single;
// Initialise variables (in form's OnCreate event)
m_dwBtnClicks := 0;
m_dwImgClicks := 0;
m_dwLastBtnClickTicks := 0;
m_dwLastImgClickTicks := 0;
m_fTotalImgClicksTicks := 0.0;
m_fTotalImgClicksTicks := 0.0;
// OnClick events
procedure TfrmQwerty.btnClick(Sender: TObject);
var
dwTime: DWORD;
begin
// TButton click!
Inc(m_dwBtnClicks);
dwTime := GetTickCount();
if (m_dwLastBtnClickTicks > 0) then
m_fTotalBtnClicksTicks := (m_fTotalBtnClicksTicks + (dwTime - m_dwLastBtnClickTicks));
m_dwLastBtnClickTicks := dwTime;
end;
procedure TfrmQwerty.imgClick(Sender: TObject);
var
dwTime: DWORD;
begin
// TImage click!
Inc(m_dwImgClicks);
dwTime := GetTickCount();
if (m_dwLastImgClickTicks > 0) then
m_fTotalImgClicksTicks := (m_fTotalImgClicksTicks + (dwTime - m_dwLastImgClickTicks));
m_dwLastImgClickTicks := dwTime;
end;
// Some TTimer::OnTimer event to update the results on-screen
procedure TfrmQwerty.OnTextEntryTimer(Sender: TObject);
var
fTime: Single;
begin
// Stop the timer
TextEntryTimer.Enabled := False;
if (m_dwBtnClicks > 1) then
begin
fTime := m_fTotalBtnClicksTicks / m_dwBtnClicks;
lblButtonClicks.Caption := Format('BtnClicks = %d [Avg = %.3fms]', [
m_dwBtnClicks, fTime]);
end;
if (m_dwImgClicks > 1) then
begin
fTime := m_fTotalImgClicksTicks / m_dwImgClicks;
lblImageClicks.Caption := Format('ImgClicks = %d [Avg = %.3fms]', [
m_dwImgClicks, fTime]);
end;
// Restart the timer
TextEntryTimer.Enabled := True;
end;
VCL 源代码是您的朋友。如前所述,这是由于 Windows 在单击足够快以生成它们时发送的双击消息引起的。
让我们看看当点击足够快以触发双击时会发生什么:
第 1 步 - 鼠标左键按下 :
procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);
begin
SendCancelMode(Self);
inherited;
if csCaptureMouse in ControlStyle then
MouseCapture := True;
if csClickEvents in ControlStyle then // !! Note here
Include(FControlState, csClicked); // Storing that we've been clicked
DoMouseDown(Message, mbLeft, []);
end;
第 2 步 - 鼠标左键向上移动。
procedure TControl.WMLButtonUp(var Message: TWMLButtonUp);
begin
inherited;
if csCaptureMouse in ControlStyle then MouseCapture := False;
if csClicked in ControlState then // !! Note here
begin // Firing CLICK event primed
Exclude(FControlState, csClicked); // from the method above
if ClientRect.Contains(SmallPointToPoint(Message.Pos)) then
Click;
end;
DoMouseUp(Message, mbLeft);
end;
第 3 步 - 再次按下鼠标左键。
这次是双击!请注意,这是处理完全不同的消息 - 来自 OS 的双击消息,而不是鼠标按下消息。此处的处理程序仍会触发 MouseDown
事件,但 不会在鼠标按钮恢复时使控件触发单击事件。
procedure TControl.WMLButtonDblClk(var Message: TWMLButtonDblClk);
begin
SendCancelMode(Self);
inherited;
if csCaptureMouse in ControlStyle then MouseCapture := True;
if csClickEvents in ControlStyle then DblClick;
DoMouseDown(Message, mbLeft, [ssDouble]);
end;
因为按钮是一个特殊的 TWinControl
它会收到特殊的 BN_CLICKED
消息,该消息在任何时候单击按钮时生成,无论是否双击。作为一个简单的控件,它的工作很简单,因此当您快速单击时(比双击速度快),您会看到来自按钮的单击事件是其两倍。
procedure TCustomButton.CNCommand(var Message: TWMCommand);
begin
if Message.NotifyCode = BN_CLICKED then Click;
end;
您还可以注意到,由于 TButton
将收到这些特殊消息,因此创建时 没有 ControlStyle
中的 csClickEvents
选项],因此尽管它也是 TControl
,但上述步骤中用于 TImage
(和其他)控件的处理不适用(即:启动 Click
WMLButtonDown
处理程序)。
如您所见,OnMouseDown
或 OnMouseUp
事件将允许您在 TImage
控件中捕获所有此类事件,无论它们是否应被视为点击或双击。
或者,如果您不关心 TImage
处理双击,您可以将控件样式设置为:
Image1.ControlStyle := Image1.ControlStyle - [csDoubleClicks];
此处,在 TControl.WndProc
中:
if not (csDoubleClicks in ControlStyle) then
case Message.Msg of
WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:
Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
end;
您可以看到双击事件已转换为简单的鼠标按下事件。
我有一个同时带有 TImage 和 TButton 控件的窗体。我注意到 TImage 对 OnClick 事件的响应速度似乎有点慢(快速点击!)所以我测量了它。对于 100 多次点击(并尽可能快地点击,使每个控件的速率尽可能保持一致),我得到了指标: TButton:平均 ~105-116ms TImage:平均 ~220-235ms
我重复了几次,结果相似。为什么 TImage 处理点击的速度大约是 TButton 的一半?处理从 WM_LBUTTON_DOWN 到 OnClick 事件的 Windows 消息队列会不会更慢?如果它们在上一次点击的 N 毫秒内,它可能正在吞噬点击?
TImage 的属性中似乎没有任何影响此的内容。
注意:如果相关,请在此处使用 Delphi 7 和标准 VCL 控件。
编辑:这里有一些示例代码演示了我是如何计时的:
// Define variables (in class definition)
m_dwBtnClicks, m_dwImgClicks: DWORD;
m_dwLastBtnClickTicks, m_dwLastImgClickTicks: DWORD;
m_fTotalBtnClicksTicks, m_fTotalImgClicksTicks: Single;
// Initialise variables (in form's OnCreate event)
m_dwBtnClicks := 0;
m_dwImgClicks := 0;
m_dwLastBtnClickTicks := 0;
m_dwLastImgClickTicks := 0;
m_fTotalImgClicksTicks := 0.0;
m_fTotalImgClicksTicks := 0.0;
// OnClick events
procedure TfrmQwerty.btnClick(Sender: TObject);
var
dwTime: DWORD;
begin
// TButton click!
Inc(m_dwBtnClicks);
dwTime := GetTickCount();
if (m_dwLastBtnClickTicks > 0) then
m_fTotalBtnClicksTicks := (m_fTotalBtnClicksTicks + (dwTime - m_dwLastBtnClickTicks));
m_dwLastBtnClickTicks := dwTime;
end;
procedure TfrmQwerty.imgClick(Sender: TObject);
var
dwTime: DWORD;
begin
// TImage click!
Inc(m_dwImgClicks);
dwTime := GetTickCount();
if (m_dwLastImgClickTicks > 0) then
m_fTotalImgClicksTicks := (m_fTotalImgClicksTicks + (dwTime - m_dwLastImgClickTicks));
m_dwLastImgClickTicks := dwTime;
end;
// Some TTimer::OnTimer event to update the results on-screen
procedure TfrmQwerty.OnTextEntryTimer(Sender: TObject);
var
fTime: Single;
begin
// Stop the timer
TextEntryTimer.Enabled := False;
if (m_dwBtnClicks > 1) then
begin
fTime := m_fTotalBtnClicksTicks / m_dwBtnClicks;
lblButtonClicks.Caption := Format('BtnClicks = %d [Avg = %.3fms]', [
m_dwBtnClicks, fTime]);
end;
if (m_dwImgClicks > 1) then
begin
fTime := m_fTotalImgClicksTicks / m_dwImgClicks;
lblImageClicks.Caption := Format('ImgClicks = %d [Avg = %.3fms]', [
m_dwImgClicks, fTime]);
end;
// Restart the timer
TextEntryTimer.Enabled := True;
end;
VCL 源代码是您的朋友。如前所述,这是由于 Windows 在单击足够快以生成它们时发送的双击消息引起的。
让我们看看当点击足够快以触发双击时会发生什么:
第 1 步 - 鼠标左键按下 :
procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);
begin
SendCancelMode(Self);
inherited;
if csCaptureMouse in ControlStyle then
MouseCapture := True;
if csClickEvents in ControlStyle then // !! Note here
Include(FControlState, csClicked); // Storing that we've been clicked
DoMouseDown(Message, mbLeft, []);
end;
第 2 步 - 鼠标左键向上移动。
procedure TControl.WMLButtonUp(var Message: TWMLButtonUp);
begin
inherited;
if csCaptureMouse in ControlStyle then MouseCapture := False;
if csClicked in ControlState then // !! Note here
begin // Firing CLICK event primed
Exclude(FControlState, csClicked); // from the method above
if ClientRect.Contains(SmallPointToPoint(Message.Pos)) then
Click;
end;
DoMouseUp(Message, mbLeft);
end;
第 3 步 - 再次按下鼠标左键。
这次是双击!请注意,这是处理完全不同的消息 - 来自 OS 的双击消息,而不是鼠标按下消息。此处的处理程序仍会触发 MouseDown
事件,但 不会在鼠标按钮恢复时使控件触发单击事件。
procedure TControl.WMLButtonDblClk(var Message: TWMLButtonDblClk);
begin
SendCancelMode(Self);
inherited;
if csCaptureMouse in ControlStyle then MouseCapture := True;
if csClickEvents in ControlStyle then DblClick;
DoMouseDown(Message, mbLeft, [ssDouble]);
end;
因为按钮是一个特殊的 TWinControl
它会收到特殊的 BN_CLICKED
消息,该消息在任何时候单击按钮时生成,无论是否双击。作为一个简单的控件,它的工作很简单,因此当您快速单击时(比双击速度快),您会看到来自按钮的单击事件是其两倍。
procedure TCustomButton.CNCommand(var Message: TWMCommand);
begin
if Message.NotifyCode = BN_CLICKED then Click;
end;
您还可以注意到,由于 TButton
将收到这些特殊消息,因此创建时 没有 ControlStyle
中的 csClickEvents
选项],因此尽管它也是 TControl
,但上述步骤中用于 TImage
(和其他)控件的处理不适用(即:启动 Click
WMLButtonDown
处理程序)。
如您所见,OnMouseDown
或 OnMouseUp
事件将允许您在 TImage
控件中捕获所有此类事件,无论它们是否应被视为点击或双击。
或者,如果您不关心 TImage
处理双击,您可以将控件样式设置为:
Image1.ControlStyle := Image1.ControlStyle - [csDoubleClicks];
此处,在 TControl.WndProc
中:
if not (csDoubleClicks in ControlStyle) then
case Message.Msg of
WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:
Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
end;
您可以看到双击事件已转换为简单的鼠标按下事件。