我寫了一段代碼,它創建了很多控件並在畫布上佈局它們以可視化一棵樹。現在這段代碼可能需要很長時間,尤其是因爲它有時需要查詢外部服務來查看是否有更多的子節點。在創建大量控件時顯示更新的進度條
所以我想顯示一個進度條,而這段代碼正在執行。對於我的程序的其他部分,我使用報告進度的後臺工作人員。然而,因爲我必須創建後來可以交互的控件,所以我沒有看到如何在這裏使用後臺工作程序或其他線程解決方案。
既然這是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線程上創建控件,並在那裏執行所有操作,同時仍然靈活。
我覺得一個BackgroundWorker是這個完全可行的,只要你叫你添加控件時調用。 – havardhu
使用BackgroundWorker或單獨的線程並調用'Invoke()'/'BeginInvoke()'是正確的方法。如果你得到一個線程異常,你可以發佈你的代碼嗎?此外,你確定你正在創建你的進度條* *啓動「工作...」線程之前? –
嘿havardhu和David Lively,謝謝你的提示。我知道Invoke/BeginInvoke。但是這仍然會讓我在後臺線程上創建控件,所以我應該花些時間來解決這個問題。你們怎麼看待使用progressChanged事件來執行由另一個線程給出的委託去做這些事情? –