C
C#•3y ago
AfterLife

WPF MVVM PropertyChangedEventHandler always null - Not updating Binding [Answered]

I have a MainWindow which loads a view (UserControl) in a ContentControl as Content per Binding to an Property in my MainViewModel. The initial Load works fine but changes made afterwards don't get published to the MainView. OnPropertyChanged is being called- and INotifyPropertyChanged is implemented via an "ObservableObject" Wrapper-Class. The PropertyChangedEventHandler is always null the View never attaches to it. My MainWindow
<Window x:Class="_505_GUI_Battleships.MainWindow"
[...]>

<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>

<Grid Margin="0 2 0 0">
[...]
<ContentControl Grid.Row="1" Content="{Binding Path=CurrentView, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
<Window x:Class="_505_GUI_Battleships.MainWindow"
[...]>

<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>

<Grid Margin="0 2 0 0">
[...]
<ContentControl Grid.Row="1" Content="{Binding Path=CurrentView, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
My ViewModel
public class MainViewModel : ObservableObject
{
private readonly MainModel _mainModel;

internal readonly DummyViewModel? DummyVm;
internal readonly StartViewModel? StartVm;

private object _currentView = null!;

public object CurrentView
{
get => _currentView;
set => Update(ref _currentView, value);
}

public ICommand StartViewModelCommand => new RelayCommand(_ => _mainModel.ChangeView(ViewChanged, StartVm));
[...]

public MainViewModel()
{
_mainModel = new MainModel();

StartVm = new StartViewModel();
DummyVm = new DummyViewModel();

CurrentView = StartVm;
}

private void ViewChanged(object view)
{
CurrentView = view;
}
}
public class MainViewModel : ObservableObject
{
private readonly MainModel _mainModel;

internal readonly DummyViewModel? DummyVm;
internal readonly StartViewModel? StartVm;

private object _currentView = null!;

public object CurrentView
{
get => _currentView;
set => Update(ref _currentView, value);
}

public ICommand StartViewModelCommand => new RelayCommand(_ => _mainModel.ChangeView(ViewChanged, StartVm));
[...]

public MainViewModel()
{
_mainModel = new MainModel();

StartVm = new StartViewModel();
DummyVm = new DummyViewModel();

CurrentView = StartVm;
}

private void ViewChanged(object view)
{
CurrentView = view;
}
}
public sealed class MainModel
{
public void ChangeView(Action<object> action, object? param)
{
if ( param != null )
action(param);
}
}
public sealed class MainModel
{
public void ChangeView(Action<object> action, object? param)
{
if ( param != null )
action(param);
}
}
21 Replies
AfterLife
AfterLifeOP•3y ago
RelayCommand Class
public class RelayCommand : ICommand
{
private readonly Action<object?> _action;

public RelayCommand(Action<object?> action)
{
_action = action;
}

public void Execute(object? parameter)
{
_action(parameter);
}

public bool CanExecute(object? parameter)
{
return true;
}

public event EventHandler? CanExecuteChanged
{
add { }
remove { }
}
}
public class RelayCommand : ICommand
{
private readonly Action<object?> _action;

public RelayCommand(Action<object?> action)
{
_action = action;
}

public void Execute(object? parameter)
{
_action(parameter);
}

public bool CanExecute(object? parameter)
{
return true;
}

public event EventHandler? CanExecuteChanged
{
add { }
remove { }
}
}
ObservableObject Class
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;

protected void Update<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return;
field = value;
OnPropertyChanged(propertyName);
}

private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;

protected void Update<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return;
field = value;
OnPropertyChanged(propertyName);
}

private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
By setting the
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
in code instead of xaml the EventHandler isn't null anymore. But the Content is still not updated even thou the Value of the Binded Property changed
canton7
canton7•3y ago
UpdateSourceTrigger is wrong -- that's about when the ContentControl writes back to the CurrentView property (the "source"), which it should never do. I don't think that's your issue though
But the Content is still not updated even thou the Value of the Binded Property changed
Are you sure the binding isn't updating? Note that you're binding a ViewModel, but the ContentControl only knows how to display UI elements (i.e. views), so it isn't going to show anything when you give it a ViewModel to display
AfterLife
AfterLifeOP•3y ago
Almost forgot, ViewModels and Views are linked via App.xaml DataTemplates
<Application x:Class="_505_GUI_Battleships.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:_505_GUI_Battleships.MVVM.View"
xmlns:viewModel="clr-namespace:_505_GUI_Battleships.MVVM.ViewModel"
xmlns:mainView="clr-namespace:_505_GUI_Battleships"
StartupUri="MainWindow.xaml">

<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Style/MenuButtonStyle.xaml" />
<ResourceDictionary Source="Style/TaskButtonStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type viewModel:MainViewModel}">
<mainView:MainWindow />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:StartViewModel}">
<view:StartView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:DummyViewModel}">
<view:DummyView />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
<Application x:Class="_505_GUI_Battleships.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:_505_GUI_Battleships.MVVM.View"
xmlns:viewModel="clr-namespace:_505_GUI_Battleships.MVVM.ViewModel"
xmlns:mainView="clr-namespace:_505_GUI_Battleships"
StartupUri="MainWindow.xaml">

<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Style/MenuButtonStyle.xaml" />
<ResourceDictionary Source="Style/TaskButtonStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type viewModel:MainViewModel}">
<mainView:MainWindow />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:StartViewModel}">
<view:StartView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:DummyViewModel}">
<view:DummyView />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
I can only see the Value change in the Property
canton7
canton7•3y ago
Where's that? Ah, you said App.xaml I still stand by my question -- do you know whether the binding's getting updated? It might be the ViewModel->View stuff that's breaking You can turn up the binding debug level, put a converter on it, etc Also, https://github.com/canton7/Stylet 🙂
AfterLife
AfterLifeOP•3y ago
I thought i know but the issues i have gets me questioning myself right now. I'm confused about the Fact that the ContentControl loads the StartVm Viewmodel as the View I've assigned in the App.xaml correctly. Same happens when i use DummyVm as the initial Value in MainViewModel's Constructor. Only after changing the Value the issue persists, which is why i can't wrap my head around it. I can verify that the Value of the Binded Property Changed to the Correct Value; and the PropertyChanged event is being fired with "something" attached to it. If the initial Load works with the viewmodels as the values for "CurrentView"; shouldn't the same work via an Command updating the Binding? Thank you, I'll take a look later today
canton7
canton7•3y ago
I'm confused about the Fact that the ContentControl loads the StartVm Viewmodel as the View I've assigned in the App.xaml correctly.
Good point.
If the initial Load works with the viewmodels as the values for "CurrentView"; shouldn't the same work via an Command updating the Binding?
Yeah, it should do Next step it to turn up the binding debug level I think Debug -> Options -> Debugging -> Output Window -> Data Binding, set to something high
canton7
canton7•3y ago
canton7
canton7•3y ago
(then check the Output window)
AfterLife
AfterLifeOP•3y ago
The Clicked event get's through and the Value is changed.. Except of the initial loading i don't get any logs about Bindings doing anything. LogLevel is set to All
AfterLife
AfterLifeOP•3y ago
Alternatively tried using the PresentationTraceSources.TraceLevel=High - Still the same no single Log message
<ContentControl Grid.Row="1" Content="{Binding CurrentView, PresentationTraceSources.TraceLevel=High }" />
<ContentControl Grid.Row="1" Content="{Binding CurrentView, PresentationTraceSources.TraceLevel=High }" />
AfterLife
AfterLifeOP•3y ago
The Changed Is being executed
AfterLife
AfterLifeOP•3y ago
Issue resolved.. I was just dumb trying to update a MainViewModel instance with another one..
canton7
canton7•3y ago
Cool! What do you meian, "with another one"?
AfterLife
AfterLifeOP•3y ago
Sorry for the late reply, The issue was in my StartView that has a DataContext set to MainViewModel.. I've ended up creating a "middle-man" class like that
using System;
using System.Collections.Generic;
using _505_GUI_Battleships.MVVM.ViewModel;

namespace _505_GUI_Battleships.Core;

public sealed class ChangeViewModel
{
public static event EventHandler<object>? ViewChanged;

public enum ViewType
{
Dummy = 0,
Start = 1
}

private static readonly Dictionary<ViewType, Type> Views = new()
{
{ ViewType.Dummy, typeof(DummyViewModel) },
{ ViewType.Start, typeof(StartViewModel) }
};

public static void ChangeView(ViewType viewType)
{
var view = Activator.CreateInstance(Views[viewType]);
if ( view == null ) return;
OnViewChanged(view);
}

private static void OnViewChanged(object e)
{
ViewChanged?.Invoke(null, e);
}
}
using System;
using System.Collections.Generic;
using _505_GUI_Battleships.MVVM.ViewModel;

namespace _505_GUI_Battleships.Core;

public sealed class ChangeViewModel
{
public static event EventHandler<object>? ViewChanged;

public enum ViewType
{
Dummy = 0,
Start = 1
}

private static readonly Dictionary<ViewType, Type> Views = new()
{
{ ViewType.Dummy, typeof(DummyViewModel) },
{ ViewType.Start, typeof(StartViewModel) }
};

public static void ChangeView(ViewType viewType)
{
var view = Activator.CreateInstance(Views[viewType]);
if ( view == null ) return;
OnViewChanged(view);
}

private static void OnViewChanged(object e)
{
ViewChanged?.Invoke(null, e);
}
}
The MainViewModel Subscribes like that
public MainViewModel()
{
ChangeViewModel.ViewChanged += (_, view) =>
{
CurrentView = view;
};
}
public MainViewModel()
{
ChangeViewModel.ViewChanged += (_, view) =>
{
CurrentView = view;
};
}
And the StartViewModel Executes a Command like that
public static ICommand DummyViewModelCommand => new RelayCommand(_ => ChangeViewModel.ChangeView(ChangeViewModel.ViewType.Dummy));
public static ICommand DummyViewModelCommand => new RelayCommand(_ => ChangeViewModel.ChangeView(ChangeViewModel.ViewType.Dummy));
Beforehand the StartView wrote to a different instance of MainViewModel instead of StartViewModel
canton7
canton7•3y ago
This is getting quite painful, haha. I'm not sure I follow why StartView knows anything about MainViewModel at all
AfterLife
AfterLifeOP•3y ago
It doesn't anymore.. That was my mistake.. Because I've had a flaw in thoughts in how to design my MainViewModel. Now that i correctly use MVVM everything works out fine!
canton7
canton7•3y ago
A decent mvvm framework will remove this clutter, and handle the mapping of ViewModels -> views for you
AfterLife
AfterLifeOP•3y ago
Yes indeed, the issue with that is that i don't know the restrictions of this project yet. This is going to be a Project for my Collage Module "Graphical User Interfaces" and the Proffesor hasn't disclosed rules about using Frameworks yet. https://github.com/xAfterLife/505-GUI
GitHub
GitHub - xAfterLife/505-GUI: Re-Creating Battle-Ships in C# using t...
Re-Creating Battle-Ships in C# using the WPF Framework and MVVM - GitHub - xAfterLife/505-GUI: Re-Creating Battle-Ships in C# using the WPF Framework and MVVM
canton7
canton7•3y ago
Aah, fair enough
AfterLife
AfterLifeOP•3y ago
Never the less thank you for your help! Have a wonderfull day/evening 🙂
Accord
Accord•3y ago
✅ This post has been marked as answered!

Did you find this page helpful?