moostang
moostang

Reputation: 93

C# WPF Best Practice to share a model across viewmodels

  1. I am writing a small scientific program. It has lots of variables as input.
  2. The user has to enter all the input variables in the window (or user interface). But, I cannot fit all the input in a single window (XAML). Thus, I have created several Views where the user just press the NEXT button to enter data in the next View.
  3. All of these Views have associated ViewModels. They all inherit from a base ViewModel.
  4. So, my question is: Do I write the properties of all the variables inside the base ViewModel? Like this:
namespace ScienceProgram
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {

        #region Usual Boiler-plate stuff for BindableBase

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
        #endregion

        #region Properties (All scientific parameters and calculations )

        public double ParamA { get; set; }
        public double ParamB { get; set;  }
        // ...
        // Lots of parameters //
        // ...
        public double ParamA23 { get; set;  }


        public double TotalLength()
        {
            return ParamA + ParamB + ParamA23;                
        }

        // ...
        // Lots of other methods 
        // ...

        #endregion 

    }
}
  1. Or do I create a separate class (e.g. ScienceParameters.cs) for all input parameters and do as follows (this is what I am doing):
namespace ScienceProgram
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {

        // Global Parameter // Shared across ViewModels //
        public static ScienceParameters scienceParameters = new ScienceParameters ();

        #region Usual Boiler-plate stuff for BindableBase

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
        #endregion

    }
}
  1. I am using the latter approach. When the user enters the data in the textbox, I simply store it as:
namespace ScienceProgram
{
    public class UserInputView1ViewModel : BaseViewModel
    {
        #region User Input 
        private double _paramA;
        public double ParamA
        {
            get { return _paramA; }
            set
            {                
                _paramA = value;                
                OnPropertyChanged("ParamA");

                // Store Value // 
                scienceParameters.ParamA = value;
            }
        }
    }
}
        #endregion
  1. Which is the the right way? Or is there a better way?
  2. I have been looking for some best practices, but I keep getting directed to EventAggregator and using a singleton. I don't think I need those.
  3. I think the solution is "to pass a model into constructor of a view model.", as many have said. But I am a bit confused on how to do it. Wouldn't that create a new instance in each viewModel?
  4. Sorry, if this sounds like a silly question, but I have been looking for a straight answer for the past week, and have not yet figured out the solution.

Many thanks in advance.

Upvotes: 2

Views: 1738

Answers (1)

Tam Bui
Tam Bui

Reputation: 3048

You're close. I see areas where you are duplicating storage of data that could cause maintenance problems. Based on what you've stated, I think your approach with having a BaseViewModel is unnecessary (but if you follow through my answer here, I show you how you could make a BaseViewModel more useful than what you're using it for).

If I understand correctly, you have multiple Views because you want the user to press 'NEXT' to get to the next navigated View. Each View has one associated ViewModel. But underneath the hood, you want to collect all of the properties into one class instance.

To me, the best practice here is to learn the MVVM architecture. The key learning that comes from MVVM is understanding "separation of concerns". Mainly, your data (i.e. all of the user's input values, aka the "Model") has nothing to do with the Views and ViewModels that you present to them in the UI. Read up on this and you will get a better handle on this good practice.

Here's how I would do it.

1) Create a Model class (ScienceParameters.cs), this will hold ALL of the properties that you expect the user to enter. Example:

public class ScienceParameters
{
        public double ParamA { get; set; }
        public double ParamB { get; set;  }
        // ...
        // Lots of parameters //
        // ...
        public double ParamA23 { get; set;  }


        public double TotalLength()
        {
            return ParamA + ParamB + ParamA23;                
        }  
}

Then on each UI that you present to the user, you will show them one View and one associated ViewModel that gives them access to "see/get" or "store/set" PORTIONS of this data. Example:

public class UserInput1ViewModel: INotifyPropertyChanged
{
        public UserInput1ViewModel(ScienceParameters model)
        {
            this.Model = model;
        }

        public ScienceParameters Model { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       

        public double ParamA
        {
            get { return this.Model.ParamA; }
            set
            {   
                // Store the user's data directly into the Model, not the ViewModel!
                this.Model.ParamA = value;                
                OnPropertyChanged(nameof(this.ParamA));  // <-- avoid magic words like "ParamA" in quotes, this is bad coding and can cause maintenance issues.
            }
        }

}
public class UserInput2ViewModel: INotifyPropertyChanged
{
        public UserInput2ViewModel(ScienceParameters model)
        {
            this.Model = model;
        }

        public ScienceParameters Model { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       

        public double ParamB
        {
            get { return this.Model.ParamB; }
            set
            {   
                this.Model.ParamB = value;                
                OnPropertyChanged(nameof(this.ParamB));
            }
        }

}

Extra Credit:

If you want to remove the repetitiveness of writing the PropertyChanged junk in each ViewModel, then put it into a BaseViewModel.

public class BaseViewModel : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
}

Then remove all those PropertyChanged lines from your ViewModels and instead define each ViewModel with the base:

public class UserInput1ViewModel: BaseViewModel
{
   // ...
}

Upvotes: 2

Related Questions