2013-07-11 51 views
2

考慮給定按鈕的OnClick事件中執行下面的代碼:德爾福2010年,儘管相應的按鈕處理Click事件被禁用

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; 

德爾福2010年,如果點擊這個按鈕,你以後在第3行中執行繁忙時再次執行另一個 點擊,隨後的點擊 事件將顯然存儲在命令隊列中,因此當調用Release(第5行)過程時,應用程序將嘗試過程 它。因此點擊事件將再次被觸發。第二次 左右,按鈕組件已經被銷燬,因此引發了「訪問違規」錯誤。

當相應的 按鈕被禁用時,系統確認第二次點擊的整個概念似乎並不健全。對這個黑幕行爲的任何解釋?

+2

這就是你使用邪惡的Application.Processmessages時得到的結果。一個聲音提示,如果你有一些長處理要做,將它移動到另一個線程... – whosrdaddy

+1

'Release()'也是一個排隊命令,所以任何已經在調用Release()之前的消息隊列中的點擊將被調用在表單獲得釋放之前進行處理。 –

+0

@Khatchig你從未投過票,從未接受過答案。我懷疑你還沒有學會如何去做這些事情。請閱讀以下內容:http://meta.stackexchange.com/questions/5234/ –

回答

4

該系統的行爲與設計完全相同,但請注意,您的代碼違背了所有聲音設計原則。具體來說,在輸入事件處理程序中使用SleepProcessMessages都是不滿意的。

該程序的行爲這種方式的原因是如下:

  1. 用戶通過點擊鼠標產生輸入消息。
  2. 此輸入事件被放置在相應線程的輸入隊列中。
  3. 該線程沒有爲它的輸入隊列提供服務(它正在休眠),因此輸入消息(鼠標向下,鼠標向上組合)位於那裏。
  4. 線程喚醒並啓用按鈕。
  5. 按鈕OnClick處理程序返回並繼續應用程序的消息循環。
  6. 在適當的時候,處理鼠標向下和鼠標向上的消息(在CM_RELEASE消息之前),因此按鈕OnClick處理程序再次運行。
  7. 按鈕OnClick處理程序調用ProcessMessages然後處理CM_RELEASE並殺死表單。
  8. BOOM!

當各個按鈕被禁用時,系統確認第二次點擊的整體概念似乎沒有聲音。

關鍵的一點是,當輸入的消息是處理而不是當輸入消息是產生按鈕的啓用狀態進行檢查。它必須是這樣的,因爲輸入消息是非常低級的東西,只有應用程序可以將它們解釋爲按鈕點擊之類的東西。

有很多方法可以修復你的代碼,但是我不喜歡提出任何建議,因爲這明顯是用於說明的代碼。但是我會說所有的解決方案都會涉及去掉Sleep和ProcessMessages的調用。

+0

感謝David的明確解釋。只是我希望系統不要在隊列中接受按鈕點擊消息,而在點擊時按鈕本身被禁用。正如您已經指出的那樣,這僅僅是一個示例代碼。我遇到的真實情況是在我已經引入上述SLEEP函數的位置執行了一些冗長的過程。 – Johny

+0

@Khatchig重點在於系統不會在隊列中放置按鈕點擊消息。它在隊列中放置鼠標向下/向上的一對事件。只有應用程序本身才能確定如何處理這對事件。在你的情況下,它是一個標準的Windows BUTTON控件。但是以自己的方式實現'Enabled'的自定義控件呢?輸入事件生成器如何知道這一點。 –

+0

哎呀。謝謝。 – Johny

0

在睡眠期間,您的應用程序無響應。點擊消息是排隊的,只有在您重新啓用按鈕後纔會處理(實際上在事件處理程序方法完全執行並且應用程序再次空閒後)。

爲了解決這個問題,在啓用按鈕之前,還要在睡眠之後執行Application.ProcessMessages。這將首先清空您的消息隊列並放棄點擊消息。

或者根本不啓用按鈕。無論如何,如果你要釋放表單,你爲什麼會這樣?

一個(可能)更好的解決方案是在單獨的線程中執行Sleep,但由於這裏的Sleep可能只是一些真實代碼的存根,所以很難說這需要多少努力。

無論如何,您當前的應用程序並不好,在這種情況下調用Application.ProcessMessages可能會偶爾產生「隨機」錯誤。你所能做的最好的事情是限制風險,但是沒有很好的方法來解決它,除了從根本上改變這個實現。

+0

*要解決該問題,請在啓用按鈕之前在睡眠後執行Application.ProcessMessages。這將首先清空你的消息隊列,並放棄點擊消息。*即使那樣也會有一個漏洞。在對ProcessMessages的額外調用返回之後,並且在調用Release之前,任何輸入消息都將被添加到隊列之前的'CM_RELEASE'。然後我們會回到原點。這裏的整個方法從根本上被打破了。 –

+0

是的。想想看,如果你打算髮布表單,爲什麼還要重新啓用按鈕...... – GolezTrol

+0

@GolezTol,重新啓用按鈕在上面的示例代碼中確實沒有意義,正如您所指出的那樣。在這種情況下,我只是對德爾福的行爲感到好奇。 – Johny