2012-12-31 24 views
5

我想在WinForms C#應用程序中有一個單獨的線程啓動一個控制ProgressBar(字幕)的後臺工作。問題是,當我嘗試將欄設置爲可見時,它什麼都不做,我嘗試了很多形式的調用,但他們似乎沒有幫助。從另一個線程操縱UI元素

以下方法progressBarCycle從一個單獨的線程調用。

BackgroundWorker backgroundWorker = new BackgroundWorker(); 

    public void progressBarCycle(int duration) 
    { 
     backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); 
     backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged); 
     backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted); 
     backgroundWorker.WorkerReportsProgress = true; 
     backgroundWorker.WorkerSupportsCancellation = true; 
     backgroundWorker.RunWorkerAsync(duration); 
    } 

    private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     BackgroundWorker worker = sender as BackgroundWorker; 

     worker.ReportProgress(0); 

     DateTime end = DateTime.Now.AddMilliseconds((int)e.Argument); 
     while (DateTime.Now <= end) 
     { 
      System.Threading.Thread.Sleep(1000); 
     } 
    } 

    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     if (!this.IsHandleCreated) 
      this.CreateHandle(); 
     statusStrip1.Invoke((MethodInvoker)delegate 
     { 
      progressBar1.Visible = false; 
     }); 
     // if (!this.IsHandleCreated) 
     // { 
     //  this.CreateHandle(); 
     //  if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false)); 
     //  else progressBar1.Visible = false; 
     // } 
     // else 
     //  if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false)); 
     //  else progressBar1.Visible = false; 
    } 

    private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     if (!this.IsHandleCreated) 
      this.CreateHandle(); 
     statusStrip1.Invoke((MethodInvoker)delegate 
     { 
      progressBar1.Visible = true; 
     }); 
     // if (!this.IsHandleCreated) 
     // { 
     //  this.CreateHandle(); 
     //  if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true)); 
     //  else progressBar1.Visible = true; 
     // } 
     // else 
     //  if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true)); 
     //  else progressBar1.Visible = true; 
    } 

我在這裏錯過了一些明顯的東西嗎?評論部分是我嘗試過的其他事情。

回答

6

ProgressChanged已經在UI線程上產生了(通過sync-context);您ProgressChanged並不需要做的是Invoke - 它可以直接操作UI(相比之下,DoWork絕對可以做到這一點)。也許真正的問題是,你不會在環內做任何worker.ReportProgress(...)- 所以它只會在開始時發生一次。

這裏有一個完整的例子:從UI線程

using System; 
using System.ComponentModel; 
using System.Threading; 
using System.Windows.Forms; 
static class Program 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.EnableVisualStyles(); 
     using (var worker = new BackgroundWorker { 
      WorkerReportsProgress = true }) 
     using (var progBar = new ProgressBar { 
      Visible = false, Step = 1, Maximum = 100, 
      Dock = DockStyle.Bottom }) 
     using (var btn = new Button { Dock = DockStyle.Top, Text = "Start" }) 
     using (var form = new Form { Controls = { btn, progBar } }) 
     { 
      worker.ProgressChanged += (s,a) => { 
       progBar.Visible = true; 
       progBar.Value = a.ProgressPercentage; 
      }; 
      worker.RunWorkerCompleted += delegate 
      { 
       progBar.Visible = false; 
      }; 
      worker.DoWork += delegate 
      { 
       for (int i = 0; i < 100; i++) 
       { 
        worker.ReportProgress(i); 
        Thread.Sleep(100); 
       } 
      }; 
      btn.Click += delegate 
      { 
       worker.RunWorkerAsync(); 
      }; 
      Application.Run(form); 
     } 
    } 
} 
+0

我放入ProgressChanged和RunWorkerCompleted純粹是爲了看看它是否會解決我的問題。當我把所有的代碼都包含在DoWork中時,它會做同樣的事情。Visible的狀態永遠不會改變,無論有沒有調用。 - 哦,可能值得一提的是,這是一個字幕,所以我不打算使用進度報告。 – UncleDave

+0

@UncleDave然後它聽起來像進度條是在另一個控件(面板也許),是不可見的。 –

+0

進度條位於運行此代碼的窗體上,該窗體也是應用程序的開始窗體。 – UncleDave

2
  1. 運行progressBarCycleRunWorkerAsync將 爲您創建新線程。
  2. backgroundWorker_ProgressChanged只需撥打 progressBar1.Visible = true;。不需要Invoke
  3. 最好還加個progressBar1.Refresh();
2

另一種可能性要注意的是,在進度條上的UI線程上運行。爲了顯示進度條並重新繪製自己以顯示新的進度量,UI線程必須自由運行,在主應用程序循環中處理Windows消息。

所以,如果你開始你的後臺工作線程,但然後你的UI線程坐在一個繁忙的等待循環等待它完成,或關閉,並做了其他工作的負載,那麼它不會處理Windows消息,你的進度條會「無響應」。您需要釋放UI線程,以便此更新仍然發生(即從您的事件處理程序返回並允許UI繼續正常運行)。

這樣做的危險是,如果UI是活動的,那麼用戶仍然可以與它進行交互。因此,您必須在後臺工作人員處於活動狀態時編寫UI,並正確處理情況(問題可能包括:允許用戶在已經運行的情況下再次啓動後臺工作人員,UI嘗試在工作人員時顯示信息線程忙於更新它,用戶決定加載一個新的文檔或在後臺工作者忙時退出等)。解決這個問題的兩個主要方法是將UI中的每一個元素都保護起來,以防止在後臺工作正在運行時發起任何危險的事情(如果你有很多控制要以這種方式進行包裝,這可能是很多工作,而且很容易犯一個錯誤,讓錯誤通過)或離開UI「不受保護」,但添加一個IMessageFilter,通過抑制傳入的Windows消息(WM_KEYPRESS等)來阻止所有「危險的」用戶交互(點擊和按鍵)後臺處理是活動的。