1
  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.

moostang
  • 73
  • 7
  • You are using a static scienceParameters. That would not be needed, if you assure that you pass the same Viewmodel instance to all 3 wizard pages (maybe that is meant by "Singleton"). Let the XAML of each dialog decide what properties of the Viewmodel instance is filled in. Side note: I am *not* an expert in MVVM, this is a comment, not an answer. But it seems your 3 wizard pages actually form ONE input dialog, with one submit in the end., you may as well use ONE instance of your Viewmodel ? – Goodies May 31 '20 at 22:59
  • HI @Goodies, Thank you very much for your reply. I am sorry for the late response. I did not notice this comment. As a matter of fact, your suggestion of using a single viewModel that uses 3 different wizard page is really awesome. That is what I am implementing today. I am learning more and more about MVVM pattern each day, and I foolishly thought that "each view needs its own viewMode". But, now as I read your suggestion, I think it will help me build a much cleaner program by using a single viewModel for all wizard pages. This is great ! I never thought of that ! Thanks, once again! – moostang Jun 21 '20 at 16:19
  • Success moostang.. as for terminology, maybe you could even regard "a wizard" consisting of a number of pages as a single view ! Only XAML does not support that, it is single window-oriented. ASP.NET has a similar constraint, its .cshtml file only defines one page of HTML. But the only reason a wizard is using more than 1 page (=window) is clarity for the user. From the MVVM program's perspectIve, a wizard is a single dialog with a single outcome: all parameters the user has specified underways. – Goodies Jun 21 '20 at 21:59

1 Answers1

2

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
{
   // ...
}
Tam Bui
  • 2,940
  • 2
  • 18
  • 27
  • Hi Tam, Thank you very much and thanks you for an awesome answer . This is exactly what I was looking for. Yes, I have been reading about MVVM, but not enough!. You have just elevated my understanding of MVVM. I thought MVVM pattern involved NOT writing those code-behinds, and the usual "button_click" stuff of the old Windows Form. I have been using ICommand, RelayCommand and binding my actions. But, here you have expanded my knowledge on MVVM. That is so great ! I am going to follow your suggestion and yes, I should spend more time on learning more !. Thank you very much Tam. – moostang Jun 01 '20 at 17:49
  • Not writing code-behinds (if possible) is part of the benefits of MVVM. And yes, if you can avoid it, use ICommand and RelayCommands and data bindings. I didn't want to get into it because it clutters the message of what you were trying to achieve. – Tam Bui Jun 01 '20 at 17:51
  • 1
    and thank you for showing me how to pass the model in the constructor. ``` public UserInput2ViewModel(ScienceParameters model) { this.Model = model; } ``` – moostang Jun 01 '20 at 17:51
  • Thanks again Tam. Don't worry about the data bindings. I have some basic handle on that after stumbling through Icommands and RelayCommands through several attempts. – moostang Jun 01 '20 at 17:56
  • Hi @tam-bui, I have a quick question. As a follow-up to this post, what should you do to clear the ScienceParameters class?, i.e. if I want to start a new calculation. Many many thanks in advance. – moostang Nov 23 '20 at 18:17
  • 1
    If you want the user to be able to clear the Model and start over, present to them a "Clear" button on the UI that is bound to a RelayCommand (e.g. call it ClearCommand) in your BaseViewModel (that way, they can clear it from any of your Views). Inside the ClearCommand, simply reset your Model. You can either clear each property one-by-one, or just set `this.Model = new ScienceParameters();` Don't forget to call the `PropertyChanged` though on your public ViewModel properties, or else those fields will be stale in the UI and still show the old data. – Tam Bui Nov 23 '20 at 20:00
  • Appending: According to [this post](https://stackoverflow.com/questions/4651466/good-way-to-refresh-databinding-on-all-properties-of-a-viewmodel-when-model-chan), you can simply call `OnPropertyChanged(String.Empty)`, and it will refresh all the ViewModel properties. Call this after you clear your Model, and your UI should refresh. – Tam Bui Nov 23 '20 at 20:21
  • Awesome! @Tam. I found my mistake. I forgot to call the `PropertyChanged` on the public ViewModel properties, just like you said. Thanks, for pointing that out. Also, thanks for sharing that post in the later comment. Its really helpful. – moostang Nov 23 '20 at 20:39