2010-03-07 121 views
5

如何防止在事件处理已经运行时启动新的事件处理?德尔福并防止事件处理

我按下按钮1并启动事件处理程序。打印速度慢。 在窗体按钮,编辑,组合中有几个控件,我希望只有在完成运行处理程序后才允许新事件。

我已经使用fRunning变量来锁定共享事件处理程序中的处理程序。有更聪明的方法来处理这个问题吗?

procedure TFormFoo.Button_Click(Sender: TObject);  
begin 
    if not fRunning then 
    try 
    fRunning := true; 
    if (Sender = Button1) then // Call something slow ... 
    if (Sender = Button2) then // Call something ... 
    if (Sender = Button3) then // Call something ... 
    finally 
    fRunning := false; 
    end; 
end; 

回答

6

另一种选择(即不需要标志字段)将临时分配NIL到事件:

procedure TForm1.Button1Click(Sender: TObject); 
var 
    OldHandler: TNotifyEvent; 
begin 
    OldHandler := (Sender as TButton).OnClick; 
    (Sender as TButton).OnClick := nil; 
    try 
    ... 
    finally 
    (Sender as TButton).OnClick := OldHandler; 
    end; 
end; 

为方便起见,这可能被裹入的接口:

interface 

function TempUnassignOnClick(_Btn: TButton): IInterface; 

implementation 

type 
    TTempUnassignOnClick = class(TInterfacedObject, IInterface) 
    private 
    FOldEvent: TNotifyEvent; 
    FBtn: TButton; 
    public 
    constructor Create(_Btn: TButton); 
    destructor Destroy; override; 
    end; 

constructor TTempUnassignOnClick.Create(_Btn: TButton); 
begin 
    Assert(Assigned(_Btn), 'Btn must be assigned'); 

    inherited Create; 
    FBtn := _Btn; 
    FOldEvent := FBtn.OnClick; 
    FBtn.OnClick := NIL; 
end; 

destructor TTempUnassignOnClick.Destroy; 
begin 
    FBtn.OnClick := FOldEvent; 
    inherited; 
end; 

function TempUnassignOnClick(_Btn: TButton): IInterface; 
begin 
    Result := TTempUnassignOnClick(_Btn); 
end; 

要使用这样的:

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    TempUnassignOnClick(Sender as TButton); 
    ... 
end; 
+1

如果在窗体中只有一个Button1,这个很好的解决方案。 Button1的OnClick被禁用,但如果在Button1事件处理期间调用Application.ProcessMessages,则Button2可以触发新的OnClick事件。 – pKarelian 2010-03-08 07:47:02

+0

谢谢dummzeuch。接口包装器是非常方便的方式来销毁临时事件对象。您不必拨打免费()。 – pKarelian 2010-03-08 07:53:16

+1

+1但在*理论*中,有可能该事件在一段时间内不会重新连接。在实践中,我认为可以肯定的是,接口实例在调用完成时被破坏(并且事件处理程序被重新连接)。 – 2010-03-08 10:08:42

2

你没有做到这一点在所有的,因为所有这一切都是在main(VCL)线程发生: 没有其他按钮(VCL)事件可以进入,直到前(VCL)事件处理程序已经返回... 另一个事件处理程序的同时执行只能在意外发生,如果其他某个线程抢先进入第二个按钮事件(在第一个按钮事件完成之前),但这不会发生,因为只有一个VCL线程。

现在,如果您正在做的冗长的事情是在另一个线程中完成的,因为您不希望它阻止GUI,那么只需将Button.Enabled属性设置为false,直到完成处理即可。
如果您决定只是坚持按钮事件,直到一切都完成,在处理循环中经常使用application.processmessages以防止gui冻结。在这种情况下,是的,您必须禁用原始按钮以防再入。

+1

对不起,这是不正确的。 – pKarelian 2010-03-07 10:32:54

+1

然后我很想在这里解释一下VCL如何在单线程上下文中同时执行两个按钮事件。 – filofel 2010-03-07 10:45:54

+6

不正确。事件可以嵌套,递归很容易发生,特别是在事件代码中调用Application.ProcessMessages时(正如我们经常要做的那样)。 – 2010-03-07 11:03:44

0

如果您的应用程序是单线程应用程序,那么当您的事件处理程序代码正在运行时,您的应用程序无法运行其他代码,因此对该事件处理程序的所有调用都将被序列化,并且您不需要担心。

如果您的事件处理程序正在运行任何异步作业,那么您可以使用您在问题中提出的技术。

+4

除非该代码调用Application.ProcessMessages。在这种情况下,通风口处理程序可以被调用两次。 – 2010-03-07 11:38:53

2

您的解决方案是可以的。您还可以将按钮点击链接到TAction.OnUpdate事件处理程序中的操作和启用/禁用操作,但仍需要执行fRunning标志。在“如果没有fRunning”行可能不这里所必要的,但因为它是更安全的,我不删除它:

// Button1.Action = acButton1, Button2.Action = acButton2, etc 

procedure TForm1.acButtonExecute(Sender: TObject); 
begin 
    if not fRunning then 

    try 
    fRunning:= True; 
    if (Sender = acButton1) then // Call something slow ... 
    if (Sender = acButton2) then // Call something ... 
    if (Sender = acButton3) then // Call something ... 
    finally 
    fRunning:= False; 
    end; 

end; 

procedure TForm1.acButtonUpdate(Sender: TObject); 
begin 
    (Sender as TAction).Enabled:= not fRunning; 
end; 
+2

另一种方法是在表单级别设置Enabled:= False。这显然是最好的尝试...最后处理程序 – 2010-03-08 00:03:07

+0

谢谢Serg。我会尝试TActionList解决方案。 – pKarelian 2010-03-08 17:41:15

2

正如格里的评论之一已经提到的,你可以禁用整个表单:

procedure TFormFoo.Button_Click(Sender: TObject);  
begin 
    try 
    Enabled := False; 
    //... 
    finally 
    Enabled := True; 
    end; 
end; 
+0

谢谢Torbins。漂亮而简单的解决方案 – pKarelian 2010-03-08 17:38:45