显示在创建大量控件时更新的进度条
我编写了一段代码,它创建了很多控件并将它们布局在画布上以可视化树。现在,此代码可能会花费大量时间,特别是因为它有时必须查询外部服务以查看是否有更多子节点。
所以我想在执行此代码时显示一个进度条。对于程序的其他部分,我使用后台工作人员来报告进度。但是,由于我必须创建稍后可交互的控件,因此我不知道如何在此处使用后台工作人员或其他线程解决方案。
由于这是 WPF,我也无法调用 Application.DoEvents()。所以我的问题是,如何创建大量控件,同时仍然能够定期更新 GUI 的可视部分?
对于我的其他代码,我使用一个装饰器,我将其布局在应用程序的繁忙部分上,我更喜欢一个可以继续使用它的解决方案,我也仍然更喜欢使用BackgroundWorker的解决方案,但我很确定这不是可能的。
我看过其他主题,但到目前为止我找不到好的答案
编辑:
根据这篇MSDN文章 http://msdn.microsoft.com/en-us/magazine/cc163328.aspx 如果需要,BackgroundWorker 应自动在 UI 线程上异步调用,但这不是我看到的行为,因为我仍然看到跨线程异常。
Edit2:nvm,这并不完全正确: BackgroundWorker 仍然需要调用 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 线程上创建控件并在那里执行所有操作,同时仍然保持灵活性。
I've written a piece of code that creates a lot of controls and layouts them on a canvas to visualize a tree. Now this code can take a lot of time, especially since it sometimes has to query an external service to see if there are more child node.
So I would like to show a progress bar while this code is executing. For other parts of my program I use a background worker that reports progress. However since I have to create controls that are later interact-able I don't see how to use a background worker or other threading solution here.
Since this is WPF, I also can't call Application.DoEvents(). So my question is, how can I create a lot of controls while still being able to periodically update the visual part of the GUI?
For my other code I use an Adorner that I layout over the busy piece of my app, I would prefer a solution where I can keep using that, I would also still prefer a solution using BackgroundWorker, but I'm pretty sure that is not possible.
I've looked at other SO topics, but I can't find a good answer so far
Creating controls in a non-UI thread
Creating a WinForm on the main thread using a backgroundworker
Edit:
According to this MSDN article http://msdn.microsoft.com/en-us/magazine/cc163328.aspx the BackgroundWorker should automatically invoke asynchronously on the UI thread if required, but this is not the behaviour I'm seeing, since I still see a cross thread exception.
Edit2: nvm, that's not totally true: BackgroundWorker still needs to call Invoke?
Edit3: After some more reading and some tips, this is the solution I've come to. Anybody got any tips/hints?
// 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;
}
In short: I use the progressChanged event of the background worker because this is always marshalled to the GUI thread. I pass it a tuple of a delegate and some state. This way I always create the control on the GUI thread and do all actions there, while still being flexible.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
一般来说,我不经常使用BackgroundWorker,但我可以建议以下内容:
DoWork
的逻辑 - 它在非 UI 线程上执行在
,以便 UI 线程UI Dispatcher
上调用正在添加节点)并向
ReportProgress
报告进度(已经添加的节点)/(节点总数),同时枚举
ProgressChanged
中的所有节点,只需用新值更新一些 ProgressBarGenerally I don't use BackgroundWorker often but I can suggest the following:
Logic for
DoWork
- its executed on non UI threadInvoke
onUI Dispatcher
so UI threadis adding nodes) and report progress to
ReportProgress
as (alreadyadded nodes)/(total count nodes) while enumerating through all nodes
in
ProgressChanged
simply update some ProgressBar with new value