2011-12-30 57 views
4

新手警告如何防止禁用時註冊按下NSButton?

我有一個簡單但令人煩惱的問題試圖禁用NSButton。這裏是示例代碼來說明問題:

- (IBAction)taskTriggeredByNSButtonPress:(id)sender { 
[ibOutletToNSButton setEnabled:NO]; 
//A task is performed here that takes some time, during which time 
//the button should not respond to presses. 
//Once the task is completed, the button should become responsive again. 
[ibOutletToNSButton setEnabled:YES]; 
} 

這是我觀察。我按下按鈕。該按鈕變爲禁用狀態(通過其褪色外觀來判斷),並且任務開始執行。當按鈕被禁用並且任務正在執行時,我再次按下該按鈕。沒有任何事情立即發生,但一旦任務完成,taskTriggeredByNSButtonPress:方法被稱爲第二次,這表明第二次按鈕按下被擱置,然後激活,一旦按鈕被重新啓用。

我試過各種黑客手段,以防止第二個按鈕按下被識別,包括在[ibOutletToNSButton setEnabled:NO];聲明後引入一段時間延遲,使按鈕隱藏而不是禁用,在此期間使用自定義視圖遮蓋按鈕它應該被禁用,將按鈕的enabled狀態綁定到一個屬性,以及其他我很尷尬的事情。

請幫我理解爲什麼我不能得到這個簡單的任務禁用按鈕的工作。

回答

4

此方法似乎直接鏈接到按鈕。您應該在另一個線程上執行長操作,否則直到方法返回後,主runloop纔會可用。主循環在事件不可用時不響應。

首先,創建方法:

- (void)someLongTask: (id)sender { 
    // Do some long tasks… 

    // Now re-enable the button on the main thread (as required by Cocoa) 
    [sender performSelectorOnMainThread: @selector(setEnabled:) withObject: YES waitUntilDone: NO]; 
} 

然後,在一個單獨的線程中執行該方法,當該按鈕的點擊:

- (IBAction)buttonPushed: (id)sender { 
    [sender setEnabled: NO]; 
    [self performSelectorInBackground: @selector(someLongTask) withObject: nil]; 
} 

您可以在示例與對象,其中替換self以上-someLongTask駐留。

通過多線程,您可以獨立且穩定地運行主runloop。也許,你的問題將得到解決。否則,你解決了響應問題。 (順便說一句,如果方法只被按鈕調用,sender參數設置爲按鈕。這樣,你不需要在方法中使用插座,但這只是一個提示。 )

+2

您的'buttonPushed:'方法會立即將按鈕設置爲啓用,因爲分離線程所需的時間可以忽略不計。您需要在處理完成後更新按鈕的狀態,這需要使用'performSelectorOnMainThread:withObject:'從輔助線程進行通知或方法調用。 – 2011-12-30 23:07:54

+0

你們太棒了。我使用NSThread在後臺線程上運行了長時間的任務,正如Rob建議的那樣,從輔助線程重新啓用了按鈕。瞧!用戶界面不僅保持響應,而且按鈕應該禁用並重新啓用。 Randy,感謝你提供關於使用'sender'的提示。這是一個很好的效率。 – scolfax 2011-12-31 04:09:36

+0

@RobKeniger對不起,我沒有注意到我的錯誤。謝謝,我會更新它。 – 2011-12-31 12:51:56

3

您不應在主事件循環中執行需要大量處理時間的任務。這就是你正在做的事情,當你的代碼執行時,應用程序的整個UI將會被阻塞。阻塞主線是造成死亡旋轉比薩™的原因。換句話說,不要這樣做。

你需要做的是打破你的耗時代碼,以便它在另一個線程中運行,即在後臺同時運行。當後臺任務完成時,它應該以某種方式通知在主線程中運行的代碼已完成。然後,主線程中的代碼可以適當地更新UI。

有很多方法可以做到這一點。

您可以使用Randy Marsh建議的NSThread方法。但是,您必須非常小心地閱讀文檔,因爲您不能只在後臺線程中調用任何舊方法並期望其工作。您必須在線程中創建自己的自動釋放池並正確處理它。您不得調用任何從輔助線程更新UI的方法。您必須非常小心,一次不會有多個線程訪問或修改變量。線程是一項複雜的業務。

-performSelectorInBackground:withObject:方法NSObject本質上是一種使用NSThread的簡單方法,並具有相同的附加條件。

您可以使用NSOperationNSOperationQueue方法,如果您可以將任務的工作細分爲可同時執行的小塊,那麼這種方法尤其好。

處理這個最簡單的方法是GCD (Grand Central Dispatch),它允許您使用內聯塊寫後臺進程:

- (IBAction)taskTriggeredByNSButtonPress:(id)sender 
    { 
     [ibOutletToNSButton setEnabled:NO]; 

     //get a reference to the global GCD thread queue 
     dispatch_queue_t queue = dispatch_get_global_queue(0,0); 

     //get a reference to the main thread queue 
     dispatch_queue_t main = dispatch_get_main_queue(); 

     //perform long-running operation 
     dispatch_async(queue,^{ 
      NSLog(@"Doing something"); 
      sleep(15); 

      //update the UI on the main thread 
      dispatch_async(main,^{ 
       [ibOutletToNSButton setEnabled:YES]; 
      }); 
     }); 
    } 

GCD是非常輕便,高效,我強烈建議如果可能的話,你使用它。

蘋果的Concurrency Programming Guide有很多更多的信息和細節,我建議你閱讀,儘管在這個階段有些細節可能會超出你。

+2

您也可以使用具有NSOperationQueue的塊。 – 2011-12-30 23:16:20

+0

感謝您對後臺運行繁重任務的選項提供了非常有幫助的描述,特別是對於正確使用NSThread的細微差別。由於我有一些NSThread的經驗,所以我採取了這種方法,並且如我上面提到的那樣,它運行得非常漂亮。 NSOperationQueue和GCD似乎是我期待學習和使用的優雅解決方案。再次感謝你的幫助。 – scolfax 2011-12-31 04:19:16

相關問題