Equivalent of PostMessage in C# to synchronize with the main thread with MVVM?

前端 未结 3 584
青春惊慌失措
青春惊慌失措 2020-12-11 17:38

I must be retarded with searching, because here\'s another seemingly common problem that I haven\'t been able to solve.

Here\'s my problem -- I am using WPF and MVVM

相关标签:
3条回答
  • 2020-12-11 18:06

    I've got another approach that seems to work, and I just wanted to throw it out there to get some comments (if anyone is even reading this question anymore!).

    I started to use the MVVM Light Toolkit's Messenger class, and it seems to work really well for me. For example, let's take the ProgressBar as an example. I registered two messages with my ViewModel for setting the progress value and progress maximum. Then in my model, as it sets up the tasks and overall process, it sends these messages. When the VM receives the messages, it just updates databound values, and my GUI updates automatically! It's super duper easy, but I was wondering what you all thought about this approach. Is anyone else doing this without incident?

    0 讨论(0)
  • 2020-12-11 18:09

    I think that your ViewModel really shouldn't know anything about the View, including whether or not it's a WPF UI, or whether or not that UI even has the concept of a Dispatcher thread, so the red flag should fly as soon as you start writing code in your ViewModel that attempts to CheckAccess() or InvokeRequired in order to marshal some code to the UI thread. Instead I'd have the model raise an event that the View can listen for and update itself accordingly, or have the ViewModel expose a property (eg. bool FileIsLoading) that the View simply binds to in order to detect and display what the model is doing asynchronously, and it's the ViewModel's responsibility to ensure that the value of that property is accurate.

    For example:

    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
    }
    
    0 讨论(0)
  • 2020-12-11 18:21

    If you want to schedule some work from a background thread to the UI thread in WPF, use the DispatcherObject. Here's a nice article on how to Build More Responsive Apps with the Dispatcher.

    Update: Note that if you use an event to send notifications from the Model to the ViewModel, you still need to switch to the UI thread somewhere. Whether that switch should be in the Model or the ViewModel is a good design discussion, but it's orthogonal to your question.

    The event will be raised on the corresponding Dispatcher thread. Since you need to get to the UI thread, you need to use a Dispatcher that is created on the UI thread. The easiest way to get one is to use the DispatcherObject.Dispatcher property on one of the UI elements. The alternative is to create one in your Model or ViewModel. If you are a design purist, I would suggest you create the Dispatcher in your Model and dispatch the call back to the UI thread before you raise the event to which the ViewModel is listening. This way all the thread switching and management is contained in your Model and the ViewModel works as a single-threaded on the UI thread only.

    0 讨论(0)
提交回复
热议问题