0
votes

I just started with the MVVM design pattern and built my own small project based on the example shown in this youtube video https://www.youtube.com/watch?v=EpGvqVtSYjs, source is found here http://danderson.io/posts/mvvm-session-01-02-demo-source-code-downloads/. In addition to the example I created a new thread in the Model which updates some of the Model properties every second. It worked well; the text boxes in my View were updated every second like expected. However, the example implemented INotifyPropertyChanged on the Model and bindings were made from the View to the properties of the Model. If I understood the MVVM pattern corretly, the latter described approach is against the MVVM pattern, the INotifyPropertyChanged should be implemented on the ViewModel instead and the View should bind to the properties which are in the ViewModel. And that is exactly what I tried on my small project, but I have no idea how the ViewModel should know whether the Properties of the Model are updated to throw the PropertyChanged event to the View. The code of the Model, ViewModel and the View binding code is pasted below. Any idea's on my struggle a very much welcome, thanks in advance.

The Model class:

public class Customer
{
    private Thread _thread;

    /// <summary>
    /// Initializes a new instance of the Customer class.
    /// </summary>
    public Customer() {
        _thread = new Thread(Temporal);
        _thread.IsBackground = true;
        _thread.Start();
    }

    private string _Name;
    /// <summary>
    /// Gets or sets the Customer's name.
    /// </summary>
    public String Name {
        get {
            return _Name;
        }
        set {
            _Name = value;
        }
    }

    public void Temporal()
    {
        int i = 0;

        while (true)
        {
            Name = "Customer #" + i.ToString();
            i++;
            System.Threading.Thread.Sleep(1000);                
        }
    }
}

The ViewModel class:

internal class CustomerViewModel : INotifyPropertyChanged
{
    /// <summary>
    /// Initializes a new instance of the CustomerViewModel class.
    /// </summary>
    public CustomerViewModel() {
        _Customer = new Customer();
    }        

    private Customer _Customer;

    public string Name
    {
        get { return _Customer.Name }
        set
        {
            _Customer.Name = value;
            OnPropertyChanged("Name");
        }
    }           

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion
}

For the binding in my View I use the following:

Text="{Binding CustomerViewModel.Name, UpdateSourceTrigger=PropertyChanged}"
1
It doesn't matter where you implement INotifyPropertyChanged. It's valid in both the model and the view model. That said, better use a DispatcherTimer for a repeated action, instead of spawning a Thread. Finally, setting UpdateSourceTrigger=PropertyChanged has no effect on your Binding. It only controls how a TwoWay or OneWayToSource Binding updates its source property. It is a common misunderstanding that it has something to do with the PropertyChanged event of the INotifyPropertyChanged interface.Clemens
I smell an issue where you're putting the binding. Ditto to what @Clemens said, but shouldn't your binding be: Text="{Binding Name}", rather than preceding it with CustomerViewModel. The property name you're binding to is relative to the context you're using, so you don't need to put the view model's name.Geoff James
I would consider updating your question so it is a little clearer to see what you're actually trying to achieve. From what I can tell you're looking to update the Customer's name every second. If this is the case, I would look at 1 of 2 ways: doing the update to the Customer from the ViewModel (and notify the change); or using some kind of messaging/eventing pattern to do the update from the Customer, still, and notify the ViewModel to update that way. A further helpful convention is to make private members' names camelCase (personal preference to precede with the _. Hope this helps.Geoff James
Thank you all for your feedback. It really helped me forward.smehrlapf

1 Answers

0
votes

You have many issues in your code.

Issue 1

Your code will not compile because your CustomerViewModel is not using an instance of Customer but it is using the Customer class. Look at the code below and read my comments inline:

public string Name
{
    get { return Customer.Name } // <-- Should be _Customer.Name
    set
    {
        Customer.Name = value; // <-- Should be _Customer.Name
        OnPropertyChanged("Name");
    }
}      

Issue 2

When your Customer instance changes by your thread, your CustomerViewModel is totally unaware of this change. There is no rule written in stone that your Model cannot have PropertyChanged. Therefore, either use a custom event or PropertyChanged in your Customer class. Like this:

public class Customer : INotifyPropertyChanged
{
    // other code...
    private string _Name;
    public String Name
    {
        get
        {
            return _Name;
        }
        set
        {
            _Name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    //... other code
}

So now your Customer class has the ability to inform subscribers when on of it's properties change.

Issue 3

Although your CustomerViewModel has code about PropertyChanged, it does not implement INotifyPropertyChanged so whoever uses it, for example your view, has no idea it is observable. Therefore, if your CustomerViewModel changes, the view will not know. Thus it will not update.

Therefore, change the code in CustomerViewModel to implement INotifyPropertyChanged and also subscribe to the Customer.PropertyChanged and then trigger the view to update itself.

internal class CustomerViewModel : INotifyPropertyChanged
{
    public CustomerViewModel()
    {
        _Customer = new Customer();
        // Subscribe to the PropertyChanged event
        _Customer.PropertyChanged += _Customer_PropertyChanged;
    }

    private void _Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // Check if Name changed, if yes, trigger the event so subscribers (view) can update.
        if (e.PropertyName == nameof(Customer.Name))
        {
            OnPropertyChanged(nameof(Name));
        }
    }

    private Customer _Customer;

    public string Name
    {
        get { return _Customer.Name; }
        set
        {
            _Customer.Name = value;
            OnPropertyChanged("Name");
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion
} 

And finally here is the XAML:

<TextBox Text="{Binding Path=Name}">