Resolved Where to create a bind view models

MattNorman

Member
Joined
May 22, 2021
Messages
13
Programming Experience
1-3
I have made the decision to move to WPF and stop trying to make windows forms do what I want because it's all I know.

I have watched several videos on the MVVM model and how to set this up however they all seems to do things a little different and I have gotten quite confused along the way.

In my current example I have the following:

1. In App.xaml code behind I first check if this is the first time the app has been launched and if settings needs to be configured
2. I then create an instance of a window (AutoSizeWindow) that I have and set its content to a new instance of my SetupTypeView.
3. Once the user clicks the continue button, it then creates a new instance of my SystemSettingsView and sets that to the windows content.
3. In my view's code behind I have a variable called 'systemSettings' and after the Initialize in the constructor, I set the variable to a new instance of my 'SystemSettingsViewModel' and then set the views DataContext to the viewmodel instance
4. The system settings view model checks some provided parameters in the constructor and based on these will either leave it's instance of my SystemSettingsDataModel with default values, or load existing settings.
5. When it is doing the loading of settings and calling the OnPropertyChanged event, I get a null reference exception. It's unclear what is set to null however as both the passed string is not null.

I may be doing something completely wrong here but it's difficult to follow with so many different implementations in different videos.

I would appreciate any assistance.

App.xaml.cs
C#:
private void AppStart(object sender, StartupEventArgs e) { //Set default theme colors. ThemeManager.ChangeAccentColor(Colors.DarkRed); //Check if first time setup. If so, run through setup process and then go back to start to re-process user info. if (RegistryManager.GetRegistrySetting("FirstTimeSetup") == "true" || RegistryManager.GetRegistrySetting("FirstTimeSetup") == string.Empty) { //Display setup type view. AutoSizeWindow wdw = new AutoSizeWindow(); wdw.cntMain.Content = new SetupTypeView(); wdw.ShowDialog(); } else { //Validate user etc //Load main window and view } }"]private void AppStart(object sender, StartupEventArgs e)
{
    //Set default theme colors.
    ThemeManager.ChangeAccentColor(Colors.DarkRed);

    //Check if first time setup. If so, run through setup process and then go back to start to re-process user info.
    if (RegistryManager.GetRegistrySetting("FirstTimeSetup") == "true" || RegistryManager.GetRegistrySetting("FirstTimeSetup") == string.Empty)
    {
        //Display setup type view.
        AutoSizeWindow wdw = new AutoSizeWindow();
        wdw.cntMain.Content = new SetupTypeView();
        wdw.ShowDialog();         
    }
    else
    {
        //Validate user etc
        //Load main window and view
    }     
}

SetupTypeView.xaml.cs
C#:
private void btnContinue_Click(object sender, RoutedEventArgs e)
{
    if (!string.IsNullOrEmpty(cbSetupType.Text))
    {
        if (cbSetupType.Text == "Server")
        {
            AutoSizeWindow win = new AutoSizeWindow();
            SystemSettingsView view = new SystemSettingsView(SettingsFormType.Server, FormMode.Setup);
            SystemSettingsViewModel model = new SystemSettingsViewModel(FormMode.Setup, SettingsFormType.Server);
            view.DataContext = model;
            win.cntMain.Content = view;
            win.Show();
            Window parentWin = Window.GetWindow(this);
            parentWin.Close();
        }
        else if (cbSetupType.Text == "User")
        {
            AutoSizeWindow win = new AutoSizeWindow();
            SystemSettingsView view = new SystemSettingsView(SettingsFormType.User, FormMode.Setup);       
            win.cntMain.Content = view;
            win.Show();
            Window parentWin = Window.GetWindow(this);
            parentWin.Close();
        }
    }
    else
    {
        CustomMessageBox mb = new CustomMessageBox("System Setup", "Please choose a setup type before continuing");
        mb.ShowDialog();
    }
}

SystemSettingsView - Constructor
C#:
 public SystemSettingsView(SettingsFormType FormType, FormMode FormMode)
 {
     InitializeComponent();
     vm = new SystemSettingsViewModel(FormMode, FormType);
     this.DataContext = vm;
 }

SystemSettingsViewModel
C#:
public class SystemSettingsViewModel : ObservableObject
    {
        #region Properties

        private SystemSettingsModel systemSettings = new SystemSettingsModel();

        public SystemSettingsModel SystemSettings
        {
            get { return systemSettings; }
            set { systemSettings = value; OnPropertyChanged("systemSettings"); }
        }

        private FormMode formMode;
        private SettingsFormType formType;

        #endregion

        #region Constructor

        public SystemSettingsViewModel(FormMode FormMode, SettingsFormType FormType)
        {
            formMode = FormMode;
            formType = FormType;

            if (formMode == FormMode.Setup) { SystemSettings = DataManagerSystemSettings.GetSystemSettings(); }
        }

        #endregion
    }

ObservableObject
C#:
public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
 
Last edited by a moderator:

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
3,122
Location
Chesapeake, VA
Programming Experience
10+
I'm still trying to understand the problem and the code, but as an aside, if you are handling a button click event (e.g. btnContinue_Click()) you are not doing MVVM anymore and have fallen back to WinForms/Win32 API style programming.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
3,122
Location
Chesapeake, VA
Programming Experience
10+
Property change notifications are case sensitive. You are passing in "systemSettings" instead of the bound property name "SystemSettings".
 

MattNorman

Member
Joined
May 22, 2021
Messages
13
Programming Experience
1-3
I'm still trying to understand the problem and the code, but as an aside, if you are handling a button click event (e.g. btnContinue_Click()) you are not doing MVVM anymore and have fallen back to WinForms/Win32 API style programming.
Yes sorry I should have mentioned that I had started setting this up as if it were a win forms app but am switching it up top use the mvvm model.
 

MattNorman

Member
Joined
May 22, 2021
Messages
13
Programming Experience
1-3
Property change notifications are case sensitive. You are passing in "systemSettings" instead of the bound property name "SystemSettings".
I did try both but still had a null reference exception.

I think I need to just start a new basic project and setup a basic demo app to simplify it.

Changing a few of my existing windows from full code behind the mvvm is making it more confusing and may be causing this issue.

The main thing I was confused about is where the views data binding should be done. From what I have seen most examples do this in the views constructor after initialization.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
3,122
Location
Chesapeake, VA
Programming Experience
10+
The hardcore WPF users will insist that the binding be done in the XAML for the view. Personally, I do the binding in the constructor for the view so that I readily have a dependency injection hook readily available if/when I need to unit test the view. (I rarely have to unit test a view. Almost all of my unit testing centers around the Model and View Model.)

Anyway, look at the full callstack for the null error exception. Try to determine where that null is coming from.
 

MattNorman

Member
Joined
May 22, 2021
Messages
13
Programming Experience
1-3
The hardcore WPF users will insist that the binding be done in the XAML for the view. Personally, I do the binding in the constructor for the view so that I readily have a dependency injection hook readily available if/when I need to unit test the view. (I rarely have to unit test a view. Almost all of my unit testing centers around the Model and View Model.)

Anyway, look at the full callstack for the null error exception. Try to determine where that null is coming from.
Managed to get this figured out in the end.

It was just an issue with my OnPropertyChanged event not handling null values where properties hadn't been instantiated.

In either case I have started a fresh project using MVVM as trying to change it in place was too confusing.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
3,122
Location
Chesapeake, VA
Programming Experience
10+
Oh! You mean that this:
C#:
protected void OnPropertyChanged(string name)
{
    PropertyChanged(this, new PropertyChangedEventArgs(name));
}

should have been:
C#:
protected void OnPropertyChanged(string name)
{
    if (PropertyChanged != null)
        PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
}

or more tersely using expression bodied methods and the Elvis operator:
C#:
protected void OnPropertyChanged(string name)
    => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

Also as an aside, you've been keeping up with C#, this would be a better implementation:
C#:
protected void OnPropertyChanged([CallerMemberName] string name = null)
    => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

so that you can change this:
C#:
set { systemSettings = value; OnPropertyChanged("systemSettings"); }
to:
C#:
set { systemSettings = value; OnPropertyChanged(); }
and not have to worry about the case sensitive thing. The property that is bound is going to use the property name when calling OnPropertyChanged().
 

MattNorman

Member
Joined
May 22, 2021
Messages
13
Programming Experience
1-3
Oh! You mean that this:
C#:
protected void OnPropertyChanged(string name)
{
    PropertyChanged(this, new PropertyChangedEventArgs(name));
}

should have been:
C#:
protected void OnPropertyChanged(string name)
{
    if (PropertyChanged != null)
        PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
}

or more tersely using expression bodied methods and the Elvis operator:
C#:
protected void OnPropertyChanged(string name)
    => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

Also as an aside, you've been keeping up with C#, this would be a better implementation:
C#:
protected void OnPropertyChanged([CallerMemberName] string name = null)
    => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

so that you can change this:
C#:
set { systemSettings = value; OnPropertyChanged("systemSettings"); }
to:
C#:
set { systemSettings = value; OnPropertyChanged(); }
and not have to worry about the case sensitive thing. The property that is bound is going to use the property name when calling OnPropertyChanged().
Thanks for the info
 
Top Bottom