7

I have a WinForms application in which my background worker is doing a sync task, adding new files, removing old ones etc.

In my background worker code I want to show a custom form to user telling him what will be deleted and what will be added if he continues, with YES/NO buttons to get his feedback.

I was wondering if it is ok to do something like this in background worker's doWork method? If not, how should I do it?

Please advise..

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
   MyForm f = new MyForm();
   f.FilesToAddDelete(..);
   DialogResult result = f.ShowDialog();
   if(No...)
   return;
   else
   //keep working...
}
4

4 回答 4

8

如果你尝试这个,你会自己发现它不会工作,因为BackgroundWorker线程不是STA(它来自托管线程池)。

问题的本质是您无法从工作线程中显示用户界面¹,因此您必须解决它。您应该传递对应用程序 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,但如果它来自线程池,则没有这种可能性。

于 2012-05-08T12:26:20.120 回答
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...
}
于 2012-05-08T12:38:48.417 回答
2

IMO 回答说您应该启动一个线程来处理这个问题是错误的。您需要将窗口跳回主调度程序线程。

在 WPF 中

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)));
}

WinForms 等效

不要将 UIDispatcher 设置为任何内容,那么您可以:

public void Consume(ImageFound message)
{
    var model = _container.Resolve<ChoiceViewModel>();
    model.ForImage(message);
    this.Invoke( () => _windows.ShowWindow(model) );
}

为 WPF 干燥它:

大佬,这么多代码...

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));
    }

希望能帮助到你。

于 2012-05-08T12:31:47.030 回答
0

您应该在运行后台工作程序之前打开对话框。而在progresschanged-event 中,你可以更新对话框。

于 2012-05-08T12:37:29.460 回答