Вызов ShowDialog в BackgroundWorker

У меня есть приложение WinForms, в котором мой фоновый работник выполняет задачу синхронизации, добавляет новые файлы, удаляет старые и т. Д.

В своем фоновом рабочем коде я хочу показать пользовательскую форму, сообщая ему, что будет удалено и что будет добавлено, если он продолжит, с помощью кнопок YES / NO, чтобы получить свой отзыв.

Мне было интересно, можно ли делать что-то подобное в методе doWork фонового работника? Если нет, то как мне это сделать?

Пожалуйста, порекомендуйте.

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

Ответы на вопрос(4)

прежде чем запускать фонового работника. А в событии progressloaded вы можете обновить диалоговое окно.

Решение Вопроса

BackgroundWorker нить не STA (это происходит от управляемый пул потоков).

Суть в том, что вы не можете отобразить пользовательский интерфейс из рабочего потока¹, поэтому вы должны обойти это. Вы должны передать ссылку на элемент пользовательского интерфейса вашего приложения (основная форма была бы хорошим выбором), а затем использоватьInvoke направить запрос на взаимодействие с пользователем в вашем потоке пользовательского интерфейса. Пример скелета:

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, но если это происходит из пула потоков, такой возможности нет.

 Ahmed08 мая 2012 г., 14:38
Кстати, кто-то сказал мне в другом месте, что безопасно вызывать MessageBox.show (..) на BackgroundWorker, возможно, потому что метод statis .. это также небезопасно ()?
 Jon08 мая 2012 г., 14:42
@ Ахмед: Кто-то не знает, о чем говорит. Здесь есть связанный вопрос, см. / Stackoverflow.com вопросы / 559252 / .... Вывод: тыможе используй это, но не потому что этоstatic или какой-то другой чепухи. Это потому, что он специально настраивает свой собственный насос сообщений.
 Ahmed08 мая 2012 г., 14:29
Спасибо, но как мне этого добиться? ..
 Ahmed08 мая 2012 г., 14:52
Спасибо, Джон, ты настоящий Гуру :) Пожалуйста, проверьте этот пост, на который я ссылался, о MessageBox, где было обосновано, что он статический, и не был указан насос сообщений. / Stackoverflow.com вопросы / 10283881 / ...
 Ahmed08 мая 2012 г., 14:36
Спасибо Джон, я попробую и посмотрим, сработает ли это для меня .. Твой ответ имеет смысл ..

го интерфейса:

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

что вы должны запустить поток, чтобы справиться с этим. Вам нужно переместить окно обратно в главный поток диспетчера.

В 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) );
}
DRYing это для 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));
    }

Надеюсь, это поможет

 Jon08 мая 2012 г., 14:35
Где ответы, в которых говорится, что нужно запустить еще одну ветку?
 Henrik08 мая 2012 г., 14:37
Все через интернет. У меня только сегодня была эта проблема. :)
 Ahmed08 мая 2012 г., 14:33
Я не использую WPF, а довольно простые формы окон ...
 Ahmed08 мая 2012 г., 14:37
Я никогда не видел такого ответа в ответ на мой вопрос .. может быть, кто-то создал и удалил его

Ваш ответ на вопрос