2013-11-04 68 views
1

有人能幫我理解爲什麼這段代碼不起作用嗎?WPF C#鎖定/解鎖按鈕並執行線程

我想要做的是:單擊該按鈕,更改按鈕的內容和鎖定,執行外螺紋,最後,解鎖按鈕,再次改變

private void button3_Click(object sender, RoutedEventArgs e) 
{ 
    button3.Content = "Printing..."; 
    button3.IsEnabled = false; 

    Thread.Sleep(1000); 

    button3.IsEnabled = true; 
    button3.Content = "Print"; 
} 
+0

已知問題。您需要使用調度程序來將用戶界面同步到您的內容。 –

+0

@daveL,按鈕沒有Text屬性 –

+1

在UI事件處理程序中調用Thread.Sleep會阻止UI線程。搜索「UI凍結」或「阻止UI線程」或類似的StackOverflow以獲得大量答案。 – Clemens

回答

2

您看到此行爲的原因是因爲您的方法(它處理按鈕的Click事件)正在與UI相同的線程上運行。使用UI代碼時,這個概念是常見的問題來源。

簡而言之,直到您的方法完成後才能重新繪製UI。在此方法中,您禁用按鈕,等待1秒鐘,然後重新啓用按鈕 - 然後然後用戶界面重新繪製,啓用按鈕。出於這個原因,您永遠不會看到屏幕上的按鈕被禁用(並且您的應用程序將凍結1秒,而Sleep發生)。

您需要做的是調查可用於在後臺線程上運行任務的方法之一。您的方法應該創建並啓動此線程,將該按鈕設置爲禁用,然後完成 - 允許UI線程將按鈕繪製爲禁用。當你的任務完成後,你應該將按鈕設置爲啓用(注意,通常在WPF中這意味着使用Dispatcher.Invoke來確保button.Enabled是從UI線程設置的)。

要創建後臺任務,你可以看看System.Threading namepaces(特別是Thread.Start方法),或Task Parallel Library,或BackgroundWorker類。我懷疑TPL將是您最簡單的起點。

+0

大部分都是正確的。但是這行 - 「直到你的方法完成才能重新繪製UI」總是不正確的。如果我在UI調度器上優先使用「Render」排隊任務,UI將在方法完成之前刷新。在我的答案中查看詳細信息。 –

-2

要禁用按鈕內容當它被點擊但不能再次啓用。你能做什麼是,

1. initiate a timer 

when button is clicked then, 
1. change content, disable the button 
2. start the timer 
3. after a certain duration when the external thread is executed enable the button again and change it's content. 

希望這會有所幫助 謝謝。

+1

行「button3.IsEnabled = true;」在發佈的代碼會建議你的答案不正確... –

+0

是的,你是對的,但我只提到另一種方式。而已。 –

2

你是sleeping on UI thread它凍結了你的UI線程,因此你在用戶界面上看不到任何更新。

控件的重畫是由UI調度器完成的,但調度器根據任務的優先級設置執行 操作。在調度程序優先級設置爲Render時,對 控件進行重繪。設置 按鈕的內容在具有優先級渲染的調度程序中排隊,但是隻有在所有具有較高優先級的任務完成後纔會執行的操作 。

正如其他人已經建議你應該在單獨的線程上移動你的長時間運行的任務,但還有另一個workaroud在睡在UI線程之前刷新GUI。就像我提到的,一旦所有高於DispatcherPriority.Render的任務完成,UI將重新繪製。所以,你可以做的是before sleeping on thread enqueue an empty delegate with render priority synchronously這將force dispatcher to perform all tasks above and with priority render之前轉移到下一個任務。這就是我的意思 -

private void button3_Click(object sender, RoutedEventArgs e) 
{ 
    button3.Content = "Printing..."; 
    button3.IsEnabled = false; 

    Dispatcher.Invoke((Action)(() => { }), DispatcherPriority.Render); <-- HERE 
    // button3.Refresh(); <-- After having extension method on UIElement. 
    Thread.Sleep(1000); 

    button3.IsEnabled = true; 
    button3.Content = "Print"; 
} 

你也可以有這樣的方法擴展方法上的UIElement -

public static void Refresh(this UIElement uiElement) 
{ 
    uiElement.Dispatcher.Invoke((Action)(() => { }), DispatcherPriority.Render); 
} 

而且睡在UI線程之前調用簡單button3.Refresh()

但是,這有一個缺點太,因爲它不僅將刷新BUTTON3 但其在UI未決刷新所有其他控件,因爲調度員將移動到下一個之前完成所有任務 優先渲染或更高版本任務。

但請記住永遠不要在UI線程上睡覺。

+0

從技術上講,這將起作用,但它的工作原理是通過屈服於調度程序隊列上的其他所有內容,並且具有高於「渲染」的優先級。這是一個非常不確定的大錘來破解一個堅果 - 它也不是最明顯或可讀的代碼。你是對的,因爲幾乎總是有*方式來改變通常的行爲,但我絕不會主張這一點。如果我在代碼審查中看到這個代碼,我會拒絕它。 –

+0

@丹 - 這也是我不鼓勵的,這就是爲什麼最後提到使用這種方法的缺點。我同意你在答案中提到的所有內容,因爲它是刷新UI的方法之一(絕對不鼓勵):) –

0

使處理異步需要照顧的問題:

private async void button3_Click(object sender, RoutedEventArgs e) 
{ 
    button3.Content = "Printing..."; 
    button3.IsEnabled = false; 

    await Task.Delay(1000); 

    button3.IsEnabled = true; 
    button3.Content = "Print"; 
}