2010-03-07 163 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