Home > OS >  ViewModel change from Singleton class is not updating visual controls in WPF C#
ViewModel change from Singleton class is not updating visual controls in WPF C#

Time:09-16

I am deserializing multiple ViewModels but when they change, the visual controls (i.e. TextBlock) in the View are not changing. After trying to root cause the problem, the actual deseralization isn't the problem, but it is my motivation for why there is a ViewModel in another ViewModel.

The key difference is that it isn't just a property in the VM that is changing, the entire ViewModel is changing (as a result of deserialization).

The following code is a simplified example to show the problem.

MainWindow.xaml - Bound TextBlock and button

<StackPanel Orientation="Vertical">
        <TextBlock x:Name="Test" FontSize="22" Text="{Binding ExampleText}"/>
        <Button x:Name="ButtonTest" FontSize="22" Width="100" Content="Load new VM" Click="LoadNewVM"/>
</StackPanel>

MainWindow Code Behind

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            //create a new instance of the ExampleViewModel and load into Singleton class MainWindowViewModel
            MainWindowViewModel.Instance.ExampleViewModel = new ExampleViewModel();

            //set the data context to this Singleton instance which does support NotifyPropertyChanged
            this.DataContext = MainWindowViewModel.Instance.ExampleViewModel;
        }


        //button click event, loads a new ViewModel into the Singleton class MainWindowViewModel with different text
        private void LoadNewVM(object sender, RoutedEventArgs e)
        {
            ExampleViewModel test = new ExampleViewModel();
            test.ExampleText = "New Text";
            MainWindowViewModel.Instance.ExampleViewModel = test;
        }
    }

MainWindowViewModel - In my attempts to ensure I wasn't getting ViewModels mixed up, I made this a Singleton. Still doesn't work. Note that this ViewModel includes the ExampleViewModel instance.

 class MainWindowViewModel: INotifyPropertyChanged
    {

        #region ExampleViewModel
        private ExampleViewModel exampleViewModel;

        
        public ExampleViewModel ExampleViewModel
        {
            get { return this.exampleViewModel; }
            set
            {
                if (this.exampleViewModel != value)
                {
                    this.exampleViewModel = value;
                    NotifyPropertyChanged();
                }
            }
        }
        #endregion 

        #region Make class singleton
        private static readonly MainWindowViewModel instance = new MainWindowViewModel();

        //tell c# compiler not to mark type as beforefieldinit
        static MainWindowViewModel()
        {
        }

        private MainWindowViewModel()
        {
        }

        public static MainWindowViewModel Instance
        {
            get { return instance; }
        }
        #endregion



        #region PropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] string PropertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
        }
        #endregion

    }

ExampleViewModel - I left out NotifyPropertyChanged for brevity but it is in the class.

 class ExampleViewModel : INotifyPropertyChanged
    {

        public ExampleViewModel()
        {
            ExampleText = "Initial Text";
        }


        private string exampleText;
        public string ExampleText
        {
            get { return exampleText; }
            set
            {
                if (exampleText != value)
                {
                    exampleText = value;
                    NotifyPropertyChanged();
                    MessageBox.Show("Example Text is changing to "   exampleText);
                }
            }
        }
     }

When I run this, the TextBlock initially displays "Initial Text" as expected. Then when I press the button, it calls the LoadNewVM method which changes the text to "New Text". However, the visual control does not change. I even put a MessageBox in the Property setter for ExampleText and it does show that the text is changing to "New Text". Yet the TextBlock in the View isn't changing. Any help would be appreciated.

CodePudding user response:

You are not creating a binding to the DataContext property, but assigning an object reference from the MainWindowViewModel.Instance.ExampleViewModel property. You then change the value of the MainWindowViewModel.Instance.ExampleViewModel property, but since there is no binding, the DataContext cannot "find out" about it.

Below is the code to help you understand the essence of the problem.

            //set the data context to this Singleton instance which does support NotifyPropertyChanged
            this.DataContext = MainWindowViewModel.Instance /*.ExampleViewModel */;
<StackPanel Orientation="Vertical" DataContext="{Binding ExampleViewModel}">
        <TextBlock x:Name="Test" FontSize="22" Text="{Binding ExampleText}"/>
        <Button x:Name="ButtonTest" FontSize="22" Width="100" Content="Load new VM" Click="LoadNewVM"/>
</StackPanel>

Value assignment can be done in XAML. It's better than overloading the Code Behind of the Window with pretty useless code.

<Window -------------
        -------------
        DataContext="{Binding ExampleViewModel, Source={x:Static local:MainWindowViewModel.Instance}}">

With this assignment, it is no longer necessary to set the StackPanel.DataContext.

  • Related