Link to home
Start Free TrialLog in
Avatar of deleyd
deleydFlag for United States of America

asked on

WPF MVVM System.Threading.Timer in Model. How does View get notified?

I have a project where I want to use a System.Threading.Timer in the Model to make it "go".

I figure the Model should run by itself and not be concerned about the View. So I don't want to use DispatchTimer in the Model.

It sounds nice to have a Model that can utilize true parallelism. There's a lot of computation to be done, and the computer has a lot of CPU cores.

It looks like a PITA to deal with locking variables when updating them so everything is thread safe, but the effort may be worth it if the result runs significantly faster.

I'm wondering though, how will notifying the View of updates work? Will it work at all if the NotifyPropertyChanged() is initiated from the Model via the System.Threading.Timer Tick event?

Eventually somehow the ViewModel needs to notify the View that a display variable has been updated, and the View needs to be running on the UI thread, which I assume is the DispatchThread.

In WinForms we'd do this by testing with InvokeRequired and then calling BeginInvoke if we're not on the UI thread.

What is the equivalent for WPF using the MVVM paradigm? Where does it make the jump to the UI thread?

I envision the Model having a Property which gets updated by System.Threading.Timer, and the Property calls NotifyPropertyChanged(), and the ViewModel subscribes to this event, so the VIewModel event handler is being called from the Model's  System.Threading.Timer Tick event handler, on whatever thread that's on. The ViewModel has it's own corresponding Property for the View to look at. The ViewModel property's getter just fetches the value from the Model. The ViewModel's PropertyChangedEventHandler just calls it's own NotifyPropertyChanged() to notify the VIew.

Let me write some sample code here of what I'm thinking (the following isn't complete, it's just a sketch):
    class Model
    {
        private int myInt;
        public int MyInt
        {
            get
            {
                return myInt;
            }
            set
            {
                if (myInt != value)
                {
                    myInt = value;
                    NotifyPropertyChanged();
                }
            }
        }

        var timer = new System.Threading.Timer();
        public Model()
        {
           set up timer...
        }
        private void TimerTIck()
        {
                update MyInt using locks
        }
    }

    class MainWindowViewModel
    {
        private Model model;

        public string MyIntText
        {
            get
            {
                return model.myInt.ToString;
            }
            set
            {
                if (myIntText != value)
                {
                    myIntText = value;
                    NotifyPropertyChanged();
                }
            }
        }

        private void ModelPropertyChangedEventHandler(object sender, PropertyChangedEventArgs e)
        {
            switch(e.PropertyName)
            {
                case nameof(model.MyInt):
                    NotifyPropertyChanged("MyIntText");
                    break;
            }
        }

        public MainWindowViewModel()
        {
            this.model = new Model();
            this.DataContext = model;
        }
    }

Open in new window

<Window x:Class="WpfApplication3.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>
        <TextBox Text="{Binding MyIntText}" />
    </Grid>
</Window>

Open in new window

I'm starting to worry about what happens when the Model is driven by  System.Threading.Timer. Somewhere along the line the PropertyChanged event needs to jump to the UI thread, and I don't see that happening anywhere.

What's missing?

(I'm also a bit wary of the case statement that jumps from MyInt to MyIntText.)

(I saw an example of System.Threading.Timer that made it look quite complicated to use:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/threading/thread-timers#thread-timer-example)
ASKER CERTIFIED SOLUTION
Avatar of Wayne Bradney
Wayne Bradney
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial