【发布时间】:2011-09-01 14:06:59
【问题描述】:
我编写了一段代码,用于创建大量控件并将它们布局在画布上以可视化一棵树。现在这段代码可能会花费很多时间,特别是因为它有时必须查询外部服务以查看是否有更多的子节点。
所以我想在这段代码执行时显示一个进度条。对于我程序的其他部分,我使用了一个后台工作人员来报告进度。但是,由于我必须创建以后可以交互的控件,所以我在这里看不到如何使用后台工作程序或其他线程解决方案。
由于这是 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 或单独的线程并调用
Invoke()/BeginInvoke()是正确的方法。如果您遇到线程异常,您可以发布您的代码吗?另外,您确定要在启动“工作...”线程之前创建进度条吗? -
嗨 havardhu 和 David Lively,感谢您的提示。我知道 Invoke/BeginInvoke。但这仍然会让我在后台线程上创建一个控件,所以我应该花一些时间来解决这个问题。你们如何看待使用 progressChanged 事件来执行由其他线程提供的委托来执行这些操作?
-
三个 1 秒线程睡眠调用的目的是什么?
-
@Valentin Kuzub,模拟做实际工作,这只是测试代码,看看这个想法是否有效:)
标签: c# wpf progress-bar multithreading