我有一个WinForms应用程序,我的后台工作人员正在执行同步任务,添加新文件,删除旧文件等。
在我的后台工作人员代码中,我想向用户显示一个自定义表单,告诉他将删除什么以及如果他继续将添加什么,使用YES / NO按钮获取他的反馈。
我想知道在后台工作者的doWork方法中做这样的事情是否可行? 如果没有,我该怎么做?
请告知..
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
MyForm f = new MyForm();
f.FilesToAddDelete(..);
DialogResult result = f.ShowDialog();
if(No...)
return;
else
//keep working...
}
答案 0 :(得分:8)
如果你试试这个,你会发现它不会起作用,因为BackgroundWorker
主题不是STA(它来自managed thread pool)。
问题的实质是你不能从工作线程¹显示用户界面,所以你必须解决它。您应该将引用传递给应用程序的UI元素(主窗体将是一个不错的选择),然后使用Invoke
来组织用户与UI线程交互的请求。一个准系统的例子:
class MainForm
{
// all other members here
public bool AskForConfirmation()
{
var confirmationForm = new ConfirmationForm();
return confirmationForm.ShowDialog() == DialogResult.Yes;
}
}
后台工作人员会这样做:
// I assume that mainForm has been passed somehow to BackgroundWorker
var result = (bool)mainForm.Invoke(mainForm.AskForConfirmation);
if (result) { ... }
¹从技术上讲,您无法从不是STA的线程显示用户界面。如果您自己创建一个工作线程,您可以选择将其设为STA,但如果它来自线程池则没有这种可能性。
答案 1 :(得分:4)
我通常创建一个在UI线程上执行委托的方法:
private void DoOnUIThread(MethodInvoker d) {
if (this.InvokeRequired) { this.Invoke(d); } else { d(); }
}
有了这个,您可以将代码更改为:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
DialogResult result = DialogResult.No;
DoOnUIThread(delegate() {
MyForm f = new MyForm();
f.FilesToAddDelete(..);
result = f.ShowDialog();
});
if(No...)
return;
else
//keep working...
}
答案 2 :(得分:2)
IMO回答说你应该启动一个线程来处理这个被误导。你需要的是将窗口跳回主调度程序线程。
public ShellViewModel(
[NotNull] IWindowManager windows,
[NotNull] IWindsorContainer container)
{
if (windows == null) throw new ArgumentNullException("windows");
if (container == null) throw new ArgumentNullException("container");
_windows = windows;
_container = container;
UIDispatcher = Dispatcher.CurrentDispatcher; // not for WinForms
}
public Dispatcher UIDispatcher { get; private set; }
然后,当某个事件发生在另一个线程上时(在这种情况下是线程池线程):
public void Consume(ImageFound message)
{
var model = _container.Resolve<ChoiceViewModel>();
model.ForImage(message);
UIDispatcher.BeginInvoke(new Action(() => _windows.ShowWindow(model)));
}
不要将UIDispatcher设置为任何东西,那么你可以拥有:
public void Consume(ImageFound message)
{
var model = _container.Resolve<ChoiceViewModel>();
model.ForImage(message);
this.Invoke( () => _windows.ShowWindow(model) );
}
男人,这么多代码......
public interface ThreadedViewModel
: IConsumer
{
/// <summary>
/// Gets the UI-thread dispatcher
/// </summary>
Dispatcher UIDispatcher { get; }
}
public static class ThreadedViewModelEx
{
public static void BeginInvoke([NotNull] this ThreadedViewModel viewModel, [NotNull] Action action)
{
if (viewModel == null) throw new ArgumentNullException("viewModel");
if (action == null) throw new ArgumentNullException("action");
if (viewModel.UIDispatcher.CheckAccess()) action();
else viewModel.UIDispatcher.BeginInvoke(action);
}
}
并在视图模型中:
public void Consume(ImageFound message)
{
var model = _container.Resolve<ChoiceViewModel>();
model.ForImage(message);
this.BeginInvoke(() => _windows.ShowWindow(model));
}
希望它有所帮助。
答案 3 :(得分:0)
在运行backgroundworker之前,应该调出对话框。在progresschanged事件中,您可以更新对话框。