Call ShowDialog in BackgroundWorker

I have a WinForms application in which my background worker performs a synchronization task, adds new files, deletes old ones, etc.

In my working paper, I want to show a user form so that the user tells him what will be deleted and what will be added if he continues, with the YES / NO buttons to get his feedback.

I was wondering if it is ok to do something like this in the background of doWork running? If not, how do I do this?

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... } 
+7
source share
4 answers

If you try this, you will see for yourself that this will not work, because the BackgroundWorker thread is not an STA (it comes from a managed thread pool ).

The point is that you cannot display the user interface from a workflow¹, so you should get around it. You must pass a link to the user interface element of your application (the main form will be a good choice), and then use Invoke to marshal to request user interaction with the user interface thread. Barebones example:

 class MainForm { // all other members here public bool AskForConfirmation() { var confirmationForm = new ConfirmationForm(); return confirmationForm.ShowDialog() == DialogResult.Yes; } } 

And the background worker will do this:

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

¹ Technically, you cannot display the user interface from a stream that is not an STA. If you create a workflow yourself, you can make it an STA anyway, but if it comes from a thread pool, this is not possible.

+8
source

I usually create a method to execute a delegate in a user interface thread:

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

With this code you can change the code:

 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... } 
+4
source

IMO answers that say you have to start a thread to handle this are wrong. You need to flip the window back to the main dispatcher thread.

In 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 in 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

Do not install UIDispatcher on anything, then you can do:

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

DRYing for 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 this helps.

+2
source

You must open the dialog box before starting the background worker. And in the progresschanged event, you can update the dialog box.

0
source

All Articles