Cedage Cedage - 2 months ago 11
C# Question

MVVM unable to display default usercontrol on main page

I'm creating an app with WPF and trying to use the MVVM pattern.

I have a main Window set up with some default controls that are used across the application.

Within this main Window, I have a ContentControl set up to display different child views, depending on which navigation button is pressed.

For the most part this works fine. The actual navigation functions properly so I am not too concerned with that code. Main Window is displayed and when I press a button, the desired ContentControl is displayed with the proper information from the Database.

The problem that I am having is trying to get a default ContentControl to be displayed. When MainWindow first loads, I want the "Master" ContentControl to be displayed without having to press a button.

Here is what I have right now:

MainWindowVM:

public class MainWindowVM : ViewModelBase
{
public MainWindowVM()
{
NavCommand = new NavigationCommand<string>(OnNav);
}

private MasterViewVM masterViewVM = new MasterViewVM();
private Room1ViewVM room1ViewVM = new Room1ViewVM();
private Room2ViewVM room2ViewVM = new Room2ViewVM();

private ObservableObject currentViewModel;
public ObservableObject CurrentViewModel
{
get { return currentViewModel; }
set { SetProperty(ref currentViewModel, value); }
}

public NavigationCommand<string> NavCommand { get; private set; }

private void OnNav(string destination)
{
switch (destination)
{
case "master":
CurrentViewModel = masterViewVM;
break;
case "room1":
CurrentViewModel = room1ViewVM;
break;
case "room2":
CurrentViewModel = room2ViewVM;
break;
}
}


Main Window XAML:

<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:MasterViewVM}">
<views:MasterView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:Room1ViewVM}" >
<views:Room1View />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:Room2ViewVM}" >
<views:Room2View />
</DataTemplate>
</Window.Resources>

<Grid>
<StackPanel Name="stackPage" Orientation="Vertical" Grid.Column="1">

<StackPanel Name="stackNavButtons"
Orientation="Horizontal"
Grid.Column="1" Grid.ColumnSpan="4"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
>
<Button Name="btnMaster"
Content="Master"
Height="50"
Width="75"
Command="{Binding NavCommand}"
CommandParameter="master"
/>
<Button Name="btnRoom1"
Content="Room 1"
Height="50"
Width="75"
Command="{Binding NavCommand}"
CommandParameter="room1"
/>
<Button Name="btnRoom2"
Content="Room 2"
Height="50"
Width="75"
Command="{Binding NavCommand}"
CommandParameter="room2"
/>
</StackPanel>

<ContentControl Content="{Binding CurrentViewModel}" />

</StackPanel>
</Grid>


I have tried adding these lines (individually) to the MainWindowVM constructor.


  • CurrentViewModel = masterViewVM;

  • CurrentViewModel = new MasterViewVM();

  • OnNav("master");



The various methods I have tried end up resulting in a System.NullReferenceException pointing back to the ObservableObject (referenced by ViewModelBase) helper class. Specifically it points to:


PropertyChanged(this, new PropertyChangedEventArgs(propertyName));


and states "Object reference not set to an instance of an object".

I suspect that the problem is that when I am loading the MainWindow, the data for the Master is not populated yet. I assume that is what is giving me the null reference.

Here is the code that opens the MainWindow (from a button on the Home page):

public ICommand OpenMainCommand
{
get { return new RelayCommand(p => OpenMainWindow()); }
}

private void OpenMainWindow()
{
MainWindow mainWin = new MainWindow();
MainWindowVM mainVM = new MainWindowVM();
mainWin.DataContext = mainVM;
mainWin.Show();
}


I have tried adding the following to the above method in an attempt to get the data loaded prior to opening the main window (and attempting to display the Master).

MasterView masterView = new MasterView();
MasterViewVM masterVM = new MasterViewVM();
masterView.DataContext = masterVM;


The final thing I tried was to rebuild the program completely following along with the guidance provided in Rachel Lim's blog about Navigation in MVVM.
I'm not going to include the modified code here because I ended up with the same results.

I was able to get Rachel's sample app to work properly. However, it didn't have a connection to a database. This further suggests that my issue lies with when and/or how my data is loaded into the Master view.

All I am trying to do now is just get the Master to display as the initial ContentControl when MainWindow is displayed.

EDIT: Changing the case of "currentViewModel" (upper case to lower case) per ibebbs's suggestion solved the issue.
Now the constructor reads:

public MainWindowVM()
{
NavCommand = new NavigationCommand<string>(OnNav);
currentViewModel = masterViewVM;
}


Since this was in question also, here is the code for my ObservableObject:

public class ObservableObject : INotifyPropertyChanged
{

protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
if (object.Equals(member, val)) return;

member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

public event PropertyChangedEventHandler PropertyChanged;

protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}


I modified the last part (also per ibebbs) to read:
(NOTE: My issue was solved without this modification.)

protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}


I'm not sure if this will handle nulls better or not, but it seems like it might.

Answer

I'm afraid there isn't enough information in the question to answer with any certainty (i.e. Are you using ObservableObject from the Microsoft.VisualStudio.PlatformUI library? Where is ViewModelBase defined?) but my guess would be that ViewModelBase doesn't check for null prior to calling the PropertyChanged event which will be invoked by the SetProperty(ref currentViewModel, value) call.

To resolve the issue, I would:

a) Ensure ViewModelBase checks for null prior to calling the PropertyChanged event which is typically done as follows (taken from ObservableObject code):

protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
  PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
  if (propertyChanged == null)
    return;
  propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

b) Assign the initial view model to the field not the property which will not cause a property changed event to be fired. The constructor should therefore be:

public MainWindowVM()
{
    NavCommand = new NavigationCommand<string>(OnNav);
    currentViewModel = masterViewVM; // Note lower case 'c' on currentViewModel
}

Hope it helps.

Comments