2

考虑在给定按钮的 OnClick 事件中执行的以下代码:

procedure TForm1.Button1Click(Sender: TObject);
begin
  button1.enabled := false;    //Line 1  
  application.processmessages; //Line 2  
  Sleep(3000);                 //Line 3
  button1.enabled := True;     //Line 4
  Release;                     //Line 5
end;

在 Delphi 2010 中,如果在第 3 行执行忙时单击此按钮后您设法再次单击它,则随后的单击事件显然会存储在命令队列中,因此当 Release(第 5 行)过程是调用时,应用程序将尝试处理它。因此,点击事件将再次被触发。第二次,按钮组件已经被销毁,因此引发了“访问冲突”错误。

当相应按钮被禁用时,系统确认第二次点击的整个概念似乎并不合理。对这种阴暗的行为有什么解释吗?

4

2 回答 2

4

系统的行为与设计完全一致,但请注意您的代码违反了所有合理的设计原则。特别是在输入事件处理程序中的使用SleepProcessMessages在其中都是不受欢迎的。

程序以这种方式运行的原因如下:

  1. 用户通过单击鼠标生成输入消息。
  2. 此输入事件被放置在相应线程的输入队列中。
  3. 该线程没有为其输入队列提供服务(它正在休眠),因此输入消息(鼠标按下、鼠标按下组合)就坐在那里。
  4. 线程唤醒并启用按钮。
  5. 按钮OnClick处理程序返回,应用程序的消息循环继续。
  6. 在适当的时候,鼠标按下和鼠标按下消息被处理(在CM_RELEASE消息之前),因此按钮OnClick处理程序再次运行。
  7. 按钮OnClick处理程序调用ProcessMessages,然后处理CM_RELEASE并终止表单。
  8. 繁荣!

当相应按钮被禁用时,系统确认第二次点击的整个概念似乎并不合理。

关键是在处理输入消息时而不是在生成输入消息时检查按钮的启用状态。必须这样,因为输入消息是非常低级的东西,只有应用程序才能将它们解释为按钮点击之类的东西。

有很多方法可以修复您的代码,但我不愿意提出任何建议,因为这显然是用于说明的代码。但我会说,所有合理的解决方案都将涉及删除对Sleep`ProcessMessages 的调用。

于 2013-07-11T08:23:28.893 回答
0

在睡眠期间,您的应用程序没有响应。点击消息排队并且仅在您重新启用按钮后才被处理(实际上是在事件处理程序方法完全执行并且应用程序再次空闲之后)。

要解决它,还要在睡眠后执行 Application.ProcessMessages,然后再启用按钮。这将首先清空您的消息队列并丢弃点击消息。

或者根本不启用该按钮。如果您仍然要发布表格,为什么要发布?

一个(可能)更好的解决方案是在单独的线程中执行 Sleep,但由于 Sleep 这里可能只是一些真实代码的存根,所以很难说要花费多少精力来做到这一点。

无论如何,您当前的应用程序并不好,在这种情况下调用 Application.ProcessMessages 可能会偶尔产生“随机”错误。您能做的最好的事情就是限制风险,但没有好的方法可以解决它,除了从根本上改变这个实现。

于 2013-07-11T08:20:50.737 回答