0

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)

Image of my desired result

DXser1
  • 25
  • 5
  • MyCustomClass should implement INotifyPropertyChanged and fire the PropertyChanged event for its properties. That is not magically done by the ObservableCollection. – Clemens Dec 10 '20 at 10:10
  • @Clemens I already do the ```INotifyPropertyChanged ``` inside MVVM ViewModel. Please check my edit. – DXser1 Dec 10 '20 at 10:13
  • As said, when you write `UICollection[0].Value = true` and expect the UI to update, MyCustomClass must fire a change notification for its Value property. – Clemens Dec 10 '20 at 10:19
  • @Clemens I still don't get it. the ```PropertyChanged``` should be invoked inside ```MyCustomClass``` or inside ```CustomFunction```?..Sorry to bother with that but I try t understand it. – DXser1 Dec 10 '20 at 10:48
  • @Clemens please check my update when you have time to spare – DXser1 Dec 10 '20 at 11:00
  • 1
    It should be `NotifyPropertyChanged(nameof(Value))` in the Value setter in MyCustomClass. – Clemens Dec 10 '20 at 11:18
  • @Clemens still no luck. The UI does not update. I will search more about this issue. – DXser1 Dec 10 '20 at 11:37
  • You are not using the suggested code by Clemens. Instead of `NotifyPropertyChanged($"{Value}");` use `NotifyPropertyChanged(nameof(Value));` – XAMlMAX Dec 10 '20 at 12:04
  • @XAMlMAX i did that in my code...Let me update the code also here. Please check my edit, – DXser1 Dec 10 '20 at 12:05
  • I can't see where you call your LoginViewModel. I can see MainWindowViewModel only. And button is enabled is usually controlled by a Command Binding, why are you using a property? – XAMlMAX Dec 10 '20 at 12:37
  • @XAMlMAX I have added the call of the ```LoginScreenVIewModel()``` inside the ```LoginScreen : Window``` class. Now the reason why I bind ```IsEnabled``` to a property is that I want to manipulate specific objects of the UI based on some if statements. – DXser1 Dec 10 '20 at 12:43
  • There is no code that calls login window. Use styles then instead of properties, also using names in xaml can lead to memory leak. Ideally there should be no names on anything. – XAMlMAX Dec 10 '20 at 13:00
  • @XAMlMAX you mean that ```x:Name="PreviewButton1"``` I guess. I will remove it as you suggest. Thanks for the note. Now about the styles. I am not so sure for the moment because I am experimenting with MVVM design so I will probably keep it as it is. To call the login window through ```public LoginScreen(){InitializeComponent(); DataContext = new LoginScreenViewModel();``` isn't it enough? In any case, though the Objects that I want to affect exist in the MainWindow, not in the LoginScreen window } – DXser1 Dec 10 '20 at 13:07

0 Answers0