简体   繁体   English

在BackgroundWorker中调用ShowDialog

[英]Calling ShowDialog in BackgroundWorker

I have a WinForms application in which my background worker is doing a sync task, adding new files, removing old ones etc. 我有一个WinForms应用程序,我的后台工作人员正在执行同步任务,添加新文件,删除旧文件等。

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. 在我的后台工作者代码中,我想向用户显示一个自定义表单,告诉他将删除什么以及如果他继续将添加什么,使用YES / NO按钮获取他的反馈。

I was wondering if it is ok to do something like this in background worker's doWork method? 我想知道在后台工作者的doWork方法中做这样的事情是否可行? 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...
}

If you try this you will see for yourself that it will not work because the BackgroundWorker thread is not STA (it comes from the managed thread pool ). 如果您尝试这样做,您将自己看到它将无法工作,因为BackgroundWorker线程不是STA (它来自托管线程池 )。

The essence of the matter is that you cannot show user interface from a worker thread¹, so you must work around it. 问题的实质是你不能从工作线程¹显示用户界面,所以你必须解决它。 You should pass a reference to a UI element of your application (the main form would be a good choice) and then use Invoke to marshal a request for user interaction to your UI thread. 您应该将引用传递给应用程序的UI元素(主窗体将是一个不错的选择),然后使用Invoke将用户交互请求编组到UI线程。 A barebones example: 一个准确的例子:

class MainForm
{

    // all other members here

    public bool AskForConfirmation()
    {
        var confirmationForm = new ConfirmationForm();
        return confirmationForm.ShowDialog() == DialogResult.Yes;
    }
}

And the background worker would do this: 后台工作人员会这样做:

// I assume that mainForm has been passed somehow to BackgroundWorker
var result = (bool)mainForm.Invoke(mainForm.AskForConfirmation);
if (result) { ... }

¹ Technically, you cannot show user interface from a thread that is not STA. ¹从技术上讲,您无法从不是STA的线程显示用户界面。 If you create a worker thread yourself you can choose to make it STA anyway, but if it comes from the thread pool there is no such possibility. 如果您自己创建一个工作线程,您可以选择将其设为STA,但如果它来自线程池则没有这种可能性。

I usually create a method to execute a delegate on the UI thread: 我通常创建一个在UI线程上执行委托的方法:

  private void DoOnUIThread(MethodInvoker d) {
     if (this.InvokeRequired) { this.Invoke(d); } else { d(); }
  }

With this, you can change your code to: 有了这个,您可以将代码更改为:

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...
}

IMO answers stating that you should launch a thread to handle this are misguided. IMO回答说你应该启动一个线程来处理这个被误导。 What you need is to jump the window back to the main dispatcher thread. 你需要的是将窗口跳回主调度程序线程。

In WPF 在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; }

and then, when some event occurs on another thread (thread pool thread in this case): 然后,当某个事件发生在另一个线程上时(在这种情况下是线程池线程):

public void Consume(ImageFound message)
{
    var model = _container.Resolve<ChoiceViewModel>();
    model.ForImage(message);
    UIDispatcher.BeginInvoke(new Action(() => _windows.ShowWindow(model)));
}

WinForms equivalent WinForms等价

Don't set UIDispatcher to anything, then you can do have: 不要将UIDispatcher设置为任何东西,那么你可以拥有:

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

DRYing it up for WPF: 为WPF干掉它:

Man, so much code... 伙计,这么多代码......

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

and in the view model: 并在视图模型中:

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

Hope it helps. 希望能帮助到你。

You should bring up the dialog before you run the backgroundworker. 您应该在运行backgroundworker之前调出对话框。 And in the progresschanged-event, you can update the dialog. 在progresschanged事件中,您可以更新对话框。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM