2011-09-01 58 views
0

我寫了一段代碼,它創建了很多控件並在畫布上佈局它們以可視化一棵樹。現在這段代碼可能需要很長時間,尤其是因爲它有時需要查詢外部服務來查看是否有更多的子節點。在創建大量控件時顯示更新的進度條

所以我想顯示一個進度條,而這段代碼正在執行。對於我的程序的其他部分,我使用報告進度的後臺工作人員。然而,因爲我必須創建後來可以交互的控件,所以我沒有看到如何在這裏使用後臺工作程序或其他線程解決方案。

既然這是WPF,我也不能調用Application.DoEvents()。所以我的問題是,我怎樣才能創建很多控件,同時還能夠定期更新GUI的可視化部分?

對於我的其他代碼,我使用一個Adorner來佈局應用程序的繁忙部分,我更喜歡一個解決方案,我可以繼續使用它,我也更喜歡使用BackgroundWorker的解決方案,但我很漂亮肯定這是不可能的。

我看了其他SO話題,但我不能找到一個很好的答案至今

Creating controls in a non-UI thread

Creating a WinForm on the main thread using a backgroundworker

編輯:

根據這個MSDN文章http://msdn.microsoft.com/en-us/magazine/cc163328.aspx如果需要,BackgroundWorker應該自動在UI線程上調用,但這不是我所看到的行爲,因爲我仍然看到了跨線程異常。

EDIT2:NVM,這並不完全正確:BackgroundWorker still needs to call Invoke?

EDIT3:一些更多的閱讀和一些技巧後,這是解決方案我來。任何人有任何提示/提示?

 // Events for reporting progress 
     public event WorkStarted OnWorkStarted; 
     public event WorkStatusChanged OnWorkStatusChanged; 
     public event WorkCompleted OnWorkCompleted; 


     private BackgroundWorker worker; 
     private delegate void GuiThreadWork(object state); 
     private PopulatableControlFactory factory = new PopulatableControlFactory(); 
     public Canvas canvas; 

     public void PerformLayout(TreeNode node) 
     { 
      OnWorkStarted(this, "Testing"); 

      worker = new BackgroundWorker(); 
      worker.WorkerReportsProgress = true; 
      worker.DoWork += new DoWorkEventHandler(worker_DoWork);    
      worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); 
      worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); 
      worker.RunWorkerAsync(node); 
     } 

     private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      OnWorkCompleted(this); 
     } 

     private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
     { 
      var workTuple = (Tuple<GuiThreadWork, TreeNode>)e.UserState; 
      workTuple.First.Invoke(workTuple.Second); //Or begin invoke? 

      if (OnWorkStatusChanged != null) 
       OnWorkStatusChanged(this, e.ProgressPercentage); 
     } 

     private void worker_DoWork(object sender, DoWorkEventArgs e) 
     { 
      TreeNode node = (TreeNode)e.Argument;    
      Thread.Sleep(1000); 
      worker.ReportProgress(33, Tuple.New(Place(node), node)); 
      Thread.Sleep(1000); 
      worker.ReportProgress(66, Tuple.New(Place(node.children[0]), node.children[0])); 
      Thread.Sleep(1000); 
      worker.ReportProgress(100, Tuple.New(Place(node.children[1]), node.children[1])); 
     } 

     private GuiThreadWork Place(TreeNode node) 
     {    
      GuiThreadWork threadWork = delegate(object state) 
      { 
       PopulatableControl control = factory.GetControl((TreeNode)state); 
       Canvas.SetLeft(control, 100); 
       Canvas.SetTop(control, 100); 
       canvas.Children.Add(control); 
      }; 

      return threadWork; 
     } 

簡而言之:我使用後臺工作器的progressChanged事件,因爲它總是編組到GUI線程。我把它傳遞給一個代表和一些狀態的元組。這樣我總是在GUI線程上創建控件,並在那裏執行所有操作,同時仍然靈活。

+1

我覺得一個BackgroundWorker是這個完全可行的,只要你叫你添加控件時調用。 – havardhu

+0

使用BackgroundWorker或單獨的線程並調用'Invoke()'/'BeginInvoke()'是正確的方法。如果你得到一個線程異常,你可以發佈你的代碼嗎?此外,你確定你正在創建你的進度條* *啓動「工作...」線程之前? –

+0

嘿havardhu和David Lively,謝謝你的提示。我知道Invoke/BeginInvoke。但是這仍然會讓我在後臺線程上創建控件,所以我應該花些時間來解決這個問題。你們怎麼看待使用progressChanged事件來執行由另一個線程給出的委託去做這些事情? –

回答

0

一般來說,我不使用的BackgroundWorker常常但我可以建議如下:

邏輯的DoWork - 節點其對非UI線程執行

  1. 獲取計數,所以你可以報告實際進展
  2. 開始建造樹(和呼籲UI Dispatcher所以UI線程 被添加節點Invoke),並報告進度ReportProgress爲(已 添加節點)/(總數節點),而枚舉通過ProgressChanged所有節點

erating只需更新一些進度與新價值