I would like to ask a question regarding the UI update of a WPF application based on changes applied to MVVM objects stored in a ObservableCollection
. But first, let me explain my intuition.
I have the following files created to support my Project Solution. In total there are 5 files, so I present their code for you to replicate the issue. Copy-paste the code below in a new Solution project (WPF - .NET Core) and see for yourself my issue.
File 1: App.xaml
<Application x:Class="WpfAppTestingScenarios.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAppTestingScenarios"
StartupUri="Window1.xaml">
<Application.Resources>
</Application.Resources>
</Application>
File 2: Window1.xaml
<Window x:Class="WpfAppTestingScenarios.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppTestingScenarios"
d:DataContext="{d:DesignInstance Type=local:LoginScreenViewModel}"
mc:Ignorable="d"
Title="Window1"
Height="450"
Width="800">
<Grid>
<Button Content="Click me"
Command="{Binding Path=LoginCommand}"
Height="20"
Width="110"/>
</Grid>
</Window>
File 3: MainWindow.xaml
<Window x:Class="WpfAppTestingScenarios.MainWindow"
x:Name="MainWindowName"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppTestingScenarios"
d:DataContext="{d:DesignInstance Type=local:MainWindowViewModel}"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="640"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="225"/>
<RowDefinition Height="210"/>
</Grid.RowDefinitions>
<StackPanel
x:Name="StackPanel1"
Visibility="{Binding StackPanelVisibility1}"
Grid.Row="0"
Grid.Column="1">
<TextBlock Text="Hello World 1"/>
</StackPanel>
<Button
IsEnabled="{Binding Path=EnableViewButton1, UpdateSourceTrigger=PropertyChanged, FallbackValue=false}"
Content="View"
Width="80"
Height="25"
FontSize="10"
FontWeight="Light"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Grid.Row="0"
Grid.Column="1">
</Button>
<StackPanel
x:Name="StackPanel2"
Visibility="{Binding StackPanelVisibility2}"
Grid.Row="1"
Grid.Column="1">
<TextBlock Text="Hello World 2"/>
</StackPanel>
<Button
IsEnabled="{Binding Path=EnableViewButton2}"
Content="View"
Width="80"
Height="25"
FontSize="10"
FontWeight="Light"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Grid.Row="1"
Grid.Column="1">
</Button>
</Grid>
</Window>
File 4: Window1.xaml.cs
using Prism.Commands;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace WpfAppTestingScenarios
{
public class LoginScreenViewModel : INotifyPropertyChanged
{
public ICommand LoginCommand
{
get { return new DelegateCommand<object>(FuncLoginCommand); }
}
public void FuncLoginCommand(object parameters)
{
MainWindow WindMain = new MainWindow();
WindMain.Show();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new LoginScreenViewModel();
}
}
}
File 5: MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace WpfAppTestingScenarios
{
public class MyCustomClass : INotifyPropertyChanged
{
public string Key { get; set; }
private object _value;
public object Value
{
get { return _value; }
//set { _value = value; NotifyPropertyChanged($"{Value}"); }
//change to
set { _value = value; NotifyPropertyChanged(nameof(Value)); } //still no luck
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
//1. - StackPanelVisibility 1
private Visibility _StackPanelVisibility1;
public Visibility StackPanelVisibility1
{
get
{
return _StackPanelVisibility1;
}
set
{
_StackPanelVisibility1 = value;
OnPropertyChanged("StackPanelVisibility1");
}
}
//2. - StackPanelVisibility 2
private Visibility _StackPanelVisibility2;
public Visibility StackPanelVisibility2
{
get
{
return _StackPanelVisibility2;
}
set
{
_StackPanelVisibility2 = value;
OnPropertyChanged("StackPanelVisibility2");
}
}
//3. - EnableViewButoon 1
private bool _EnableViewButton1;
public bool EnableViewButton1
{
get
{
return _EnableViewButton1;
}
set
{
_EnableViewButton1 = value;
OnPropertyChanged("EnableViewButton1");
}
}
//4. - EnableViewButoon 2
private bool _EnableViewButton2;
public bool EnableViewButton2
{
get
{
return _EnableViewButton2;
}
set
{
_EnableViewButton2 = value;
OnPropertyChanged("EnableViewButton2");
}
}
private void CustomFunction(ObservableCollection<MyCustomClass> UICollection)
{
if ((Visibility)UICollection[0].Value == Visibility.Hidden)
UICollection[0].Value = Visibility.Visible;
if ((bool)UICollection[1].Value == false)
UICollection[1].Value = true;
}
public MainWindowViewModel()
{
ObservableCollection<MyCustomClass> dict = new ObservableCollection<MyCustomClass>
{
new MyCustomClass { Key = "StackPanelVisibility", Value = StackPanelVisibility1 },
new MyCustomClass { Key = "EnableViewButton", Value = EnableViewButton1 }
};
CustomFunction(dict);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
So even though everything was set successfully and with no errors, when I run the application and I click the button Click me
the UI and thus the objects of the MainWindow
are not updated.
Initially, I tried this logic with Dictionaries. But then I read that Dictionary
cannot update the UI of a WPF application so I changed it to an ObservableCollection
. However, both approaches didn't work for me.
Edit
Based on this answer, I created the following code
public class MyCustomClass : INotifyPropertyChanged
{
public string Key { get; set; }
private object _value;
public object Value
{
get { return _value; }
//set { _value = value; NotifyPropertyChanged($"{Value}"); }
//change to
set { _value = value; NotifyPropertyChanged(nameof(Value)); } //still no luck
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
But still, I don't observe any UI change.
My end result would be to enable the two buttons in the MainWindow like in the screen below (when I click the button Click me
)