Эквивалент PostMessage в C # для синхронизации с основным потоком с MVVM? - PullRequest
7 голосов
/ 09 марта 2010

Я должен быть задержан с поиском, потому что вот еще одна, казалось бы, распространенная проблема, которую мне не удалось решить.

Вот моя проблема - я использую WPF и MVVM, и у меня есть машина состояний, которая выполняется в модели. Если возникает ошибка, мне нужно передать информацию до ViewModel для отображения ошибки. Эта часть, кажется, работает хорошо. Когда пользователь щелкает требуемое поведение, код в модели продолжается и смотрит на объект, с которым взаимодействует пользователь, чтобы определить, что делать дальше.

Проблема заключается в том, что модели необходимо перезагрузить файл, который обновляет графический интерфейс с помощью содержимого указанного файла. Поскольку модель выполняется в потоке, вы можете представить, что я собираюсь спросить дальше - как, черт возьми, вы правильно синхронизируете с графическим интерфейсом? В MFC я бы использовал SendMessage или PostMessage для обновления GUI.

Я прочитал статьи для WinForms , в которых предлагается использовать InvokeRequired для автоматического вызова BeginInvoke при необходимости. На самом деле я не знал, что BeginInvoke выполнит то, что я хотел, поэтому я был рад этому научиться.

Как я могу вызвать BeginInvoke из моей модели? Применим ли этот метод даже к WPF? Я реализовал делегат и затем вызвал Invoke, но я получаю ту же ошибку, которая говорит мне, что коллекция не может быть изменена из этого потока. Я также попробовал BeginInvoke, но я предполагаю, что это тоже не сработает, потому что он все равно будет запускаться из другого потока.

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

РЕДАКТИРОВАТЬ - Я, вероятно, также должен добавить, что я ищу что-то кроме решения на основе таймера или BackgroundWorker, если только это не единственный способ решить эту проблему в WPF / MVVM , Кроме того, мне интересно, есть ли в каком-либо из наборов инструментов MVVM средства для такого рода вещей ...

Ответы [ 3 ]

8 голосов
/ 09 марта 2010

Если вы хотите запланировать некоторую работу из фонового потока в поток пользовательского интерфейса в WPF, используйте DispatcherObject. Вот хорошая статья о том, как создавать более отзывчивые приложения с помощью диспетчера .

Обновление: Обратите внимание, что если вы используете событие для отправки уведомлений из Модели в ViewModel, вам все равно нужно где-то переключиться на поток пользовательского интерфейса. Должен ли этот переключатель быть в Model или ViewModel - хорошее обсуждение дизайна, но оно ортогонально вашему вопросу.

Событие будет инициировано в соответствующем потоке Dispatcher. Поскольку вам нужно попасть в поток пользовательского интерфейса, вам нужно использовать Dispatcher, который создается в потоке пользовательского интерфейса. Самый простой способ получить его - использовать свойство DispatcherObject.Dispatcher для одного из элементов пользовательского интерфейса. Альтернативой является создание в вашей модели или модели представления. Если вы являетесь чистым дизайнером, я бы предложил вам создать Dispatcher в вашей модели и отправить обратный вызов в поток пользовательского интерфейса, прежде чем вызывать событие, которое прослушивает ViewModel. Таким образом, все переключение потоков и управление ими содержатся в вашей модели, и ViewModel работает как однопоточный только в потоке пользовательского интерфейса.

4 голосов
/ 09 марта 2010

Я думаю, что ваша ViewModel действительно не должна ничего знать о View, в том числе о том, является ли он интерфейсом WPF или же этот интерфейс имеет концепцию потока Dispatcher, поэтому красный флаг должен вылететь, как только когда вы начинаете писать код в вашей ViewModel, которая пытается CheckAccess () или InvokeRequired, чтобы маршалировать некоторый код в поток пользовательского интерфейса. Вместо этого я хотел бы, чтобы модель вызвала событие, которое View может прослушивать и обновляла сама, или чтобы ViewModel выставлял свойство (например, bool FileIsLoading), с которым просто привязывается View, чтобы обнаружить и отобразить, что представляет собой модель. выполняется асинхронно, и ViewModel несет ответственность за обеспечение точности значения этого свойства.

Например:

public partial class MainWindow : Window {
    private ViewModel _model = new ViewModel();

    public MainWindow() {
        InitializeComponent();
        DataContext = _model;
    }

    private void Button_Click(object sender, RoutedEventArgs e) {
        _model.Run();
    }
}


<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Click="Button_Click"
                Content="Run"
                IsEnabled="{Binding IsIdle}" />
    </Grid>
</Window>



public class ViewModel : INotifyPropertyChanged {

    private bool _isIdle = true;

    public bool IsIdle {
        get { return _isIdle; }
        set {
            _isIdle = value;
            OnPropertyChanged("IsIdle");
        }
    }

    public void Run() {
        ThreadPool.QueueUserWorkItem((state) => {
            IsIdle = false;
            Thread.Sleep(10000);
            IsIdle = true;
        });
    }

    #region INotifyPropertyChanged Implementation

    protected void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
        if (propertyChanged != null) {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}
1 голос
/ 26 мая 2010

У меня есть другой подход, который, кажется, работает, и я просто хотел его использовать, чтобы получить некоторые комментарии (если кто-то даже больше читает этот вопрос!)

Я начал использовать класс Messenger MVVM Light Toolkit, и, похоже, он мне очень подходит. Например, давайте возьмем ProgressBar в качестве примера. Я зарегистрировал два сообщения в моей ViewModel для установки значения прогресса и максимума прогресса. Затем в моей модели, когда он устанавливает задачи и общий процесс, он отправляет эти сообщения. Когда виртуальная машина получает сообщения, она просто обновляет значения базы данных, и мой графический интерфейс обновляется автоматически! Это супер просто, но мне было интересно, что вы все думаете об этом подходе. Кто-нибудь еще делает это без инцидентов?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...