Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly update the Model after the ViewModel has changed?

Suppose we have a Model (class Model) with the following property.

public string InputFileName
{
    get { return m_InputFileName; }
    set
    {
        m_InputFileName = value;
        RaiseNotifyPropertyChanged("InputFileName");
    }
}

The above model implements the INotifyPropertyChanged interface, so we have also the following method and the following event. The RaiseNotifyPropertyChanged method below is used to update the ViewModel.

#region INotifyPropertyChanged Implementation

private void RaiseNotifyPropertyChanged(string property)
{
    var handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(property));
    }
}

public event PropertyChangedEventHandler PropertyChanged;

#endregion

The following are the main sections of the class that implements the ViewModel.

public class ViewModel : INotifyPropertyChanged
{
    #region Members

    private Model m_Model;

    private string m_InputFileStr;

    private readonly ICommand m_SubmitCommand;

    #endregion

    #region Constructors

    public ViewModel()
    {
        m_Model = new Model();
        m_Model.PropertyChanged += new PropertyChangedEventHandler(this.Model_PropertyChanged);

        m_InputFileStr = string.Empty;

        // ...
        // initialize m_SubmitCommand
    }

    #endregion

    // ...

    #region Properties

    public string InputFileStr
    {
        get { return m_InputFileStr; }
        set
        {
            if (value == m_InputFileStr) return;
            m_InputFileStr = value;
            OnPropertyChanged("InputFileStr");
            m_SubmitCommand.RaiseCanExecuteChanged();
        }
    }

    #endregion

    #region INotifyPropertyChanged Implementation

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion

    // This method is called when the model changes, so the Model notified the ViewModel.
    private void Model_PropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "InputFileName")
        {
            InputFileStr = m_Model.InputFileName;
        }
        else if (args.PropertyName == "OutputFileName")
        {
            OutputFileStr = m_Model.OutputFileName;
        }
        else if (args.PropertyName == "ReportText")
        {
            ReportTextStr = m_Model.ReportText;
        }
    }
}

The following are the main sections of the class that implements the View:

MainWindow.xaml

<TextBox Name="inputfileTextBox"
         Text="{Binding Path=InputFileStr, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>

<Button Name="submitButton"
        Content="Submit"
        Command="{Binding SubmitCommand}"/>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new ViewModel();
    }
}

The above implementation works correctly:

  • the View and the ViewModel correctly update each other;
  • the Model correctly updates the ViewModel.

With the aim of enabling the ViewModel to update the Model, I thought I would add the following call inside the set property InputFileStr of ViewModel:

m_Model.InputFileName = value;

However, this solution of updating the Model causes an obvious unintended effect:

  1. The user modified the View.
  2. The ViewModel is automatically modified.
  3. The ViewModel updates the Model (m_Model.InputFileName = value;).
  4. The Model is updated...
  5. ... so it notifies the ViewModel about the changes

Is the above behavior a correct behavior? I expect that if the ViewModel updates the Model, then the Model does not have to re-notify the ViewModel about the same change... As an alternative solution I thought I'd add an Update method to the Model: this method should update the Model without using the Model Properties.

public void Update(string inputFileName)   // this method does not notifies the ViewModel
{
    m_InputFileName = inputFileName;
}

Is this alternative solution a correct solution or are there better solutions?

like image 991
enzom83 Avatar asked Mar 19 '15 00:03

enzom83


People also ask

How the model changes could be notified to ViewModel in MVVM?

If you want your Models to alert the ViewModels of changes, they should implement INotifyPropertyChanged, and the ViewModels should subscribe to receive PropertyChange notifications. But typically this is only needed if more than one object will be making changes to the Model's data, which is not usually the case.

Should ViewModel implement INotifyPropertyChanged?

You should always have the Model implement INotifyPropertyChanged and this is just a mistake which would be corrected if this were developed from a code example to an application.


1 Answers

Depending on what your model is, you will usually just invoke a "Save" method or similar. Most models (say, a database) don't need/want to have every change given to them in real-time.

So in general, the flow would be:

  1. User invokes "save" operation
  2. View model receives this as a command
  3. View model invokes "save" operation on the model with the new data

If your DTO objects are shared between the model and view model, you don't even need to worry about synchronization. Otherwise, this is a good time to sync them.

On a similar note, using PropertyChanged in a model class is usually a bad idea. For starters, its no fun at all to listen to. Instead, if the model receives new data, raise a more semantically clear event to the VM with the new data.

tldr; Basically, don't worry so much about keeping your model and view model in sync. Very often, the model won't be keeping a copy of the current state at all! Even when it is, just update it when the view model is ready to "commit" changes, and notify the View Model of external changes to the model via normal events.

like image 181
BradleyDotNET Avatar answered Nov 28 '22 09:11

BradleyDotNET