0
votes

I make a simple MVVM sample. I have main window with two user control pages. The main window have two event to change the view to two user control.

This is my main window XAML

<Window
        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:MVVC_Binding"
        xmlns:views="clr-namespace:MVVC_Binding.Views"
        xmlns:viewModel="clr-namespace:MVVC_Binding.ViewModels"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" x:Class="MVVC_Binding.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate DataType="{x:Type viewModel:Page1ViewModel}">
            <views:Page1 />
        </DataTemplate>

        <DataTemplate DataType="{x:Type viewModel:Page2ViewModel}">
            <views:Page2/>
        </DataTemplate>
    </Window.Resources>
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0">
            <TextBlock Text="Page1">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseLeftButtonUp">
                        <i:InvokeCommandAction Command="{Binding NavCommand}" CommandParameter="page1"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </TextBlock>
            <TextBlock Text="Page2">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseLeftButtonUp">
                        <i:InvokeCommandAction Command="{Binding NavCommand}" CommandParameter="page2"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </TextBlock>
        </StackPanel>
        <DockPanel Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Gainsboro">
            <Grid x:Name="container" Background="Gainsboro" VerticalAlignment="Top">
                <ContentControl Content="{Binding CurrentViewModel}"/>
            </Grid>
        </DockPanel>
    </Grid>
</Window>

I am using the BindableBase class for my view model. This is my BindableBase class

namespace MVVC_Binding.Utilities
{
    public class BindableBase : INotifyPropertyChanged
    {
        /// <summary>
        /// Interface implementation
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
        {
            // Check for current set member and the new value
            // If they are the same, do nothing
            if (object.Equals(member, val)) return;

            member = val;
            // Invoke the property change event
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

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

In my main view model, just simple click event to change the view binding

private BindableBase _CurrentViewModel;

        public BindableBase CurrentViewModel
        {
            get { return _CurrentViewModel; }
            set
            {
                SetProperty(ref _CurrentViewModel, value);
            }
        }

        private void OnNav(string destination)
        {
            switch (destination)
            {
                case "page2":
                    CurrentViewModel = page2;
                    break;
                default:
                    CurrentViewModel = page1;
                    break;
            }
        }

The problem is in user control Page 2, when it is display, and the event in side of it does not change the TextBlock binding value, but the text can change during the view model constructor event.

Here is my page 2 XAML

<UserControl x:Class="MVVC_Binding.Views.Page2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MVVC_Binding.Views"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <TextBlock Name="txtPage2" Text="{Binding Page2Text}"></TextBlock>
        <TextBlock Name="btn" Text="Click Button">
            <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseLeftButtonUp">
                        <i:InvokeCommandAction Command="{Binding BtnCommand}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
        </TextBlock>
    </StackPanel>
</UserControl>

And here is the Page2ViewModel

namespace MVVC_Binding.ViewModels
{
    public class Page2ViewModel : BindableBase
    {
        public MyICommand<string> BtnCommand { get; private set; }

        public Page2ViewModel()
        {
            BtnCommand = new MyICommand<string>(OnBtnClick);
            Page2Text = "Just a test";
        }

        private void OnBtnClick(string obj)
        {
            Page2Text = "Changing by button click";
        }

        private string _page2Text;

        public string Page2Text
        {
            get { return _page2Text; }
            set
            {
                _page2Text = value;
                SetProperty(ref _page2Text, value);
            }
        }
    }
}

Can you please see what I am doing wrong? Thanks so much

2
@Aaron's answer should fix your problem, but I wanted to add a little helpful tip for your BindableBase. If you change your SetProperty to return a bool with true meaning the value was updated, and false if it wasn't, you can do some really efficient property changing if you need extra logic in your setter. I use the same pattern, and it is very helpful. - Bradley Uffner
Thanks @BradleyUffner, I change the code as mentioned by Araon's but it does not help. The value is not updated to the View. But when I placed the break, I can see the code trigger correctly when the value change in the view. And the INotify event triggered as well - cam1982
The way your PropertyChanged event is set up and fired looks a little odd to me, but I'm not sure if it's really a problem, or just different than what I am used to. Have you used this BindableBase class before with successful 2-way binding, or is this the first time you are trying to use it? - Bradley Uffner
This is first time I used it, I learned the class from PluralSight course, so I tried to apply it. But the weird thing is if I change the text from the Viewmodel constructor, it will update correctly. It only fail from the Command Event - cam1982
That's because the constructor runs before the binding, the property value is already in place when it is evaluated, and it just picks up what is already there at the time of the binding, it doesn't rely on the PropertyChanged event to notify it of the new value. Try changing your BindableBase so that it looks like this and let me know if it makes any difference. If that works, I'll type up an answer for you. - Bradley Uffner

2 Answers

2
votes

If I understand correctly, you're asking why the code in this function doesn't seem to have an effect on the view:

private void OnBtnClick(string obj)
{
    _page2Text = "Changing by button click";
}

The problem is that you are changing the underlying _page2Text member, but in order for WPF to detect this change, you must use the Page2Text property, like this:

private void OnBtnClick(string obj)
{
    Page2Text = "Changing by button click";
}

The specific part of your code that is indicating the property change to WPF is the OnPropertyChanged method in your BindableBase class.

1
votes

Thanks everyone, I manage to solve it by updating the class BindableBase. The updated

namespace SilentUpdate.Utilities
{
    public class BindableBase : INotifyPropertyChanged
    {
        /// <summary>
        /// Interface implementation
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
        {
            // Check for current set member and the new value
            // If they are the same, do nothing
            // if (object.Equals(member, val)) return;

            member = val;
            // Invoke the property change event
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void OnPropertyChanged(string propName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }
}

So I comment out the check for the Object.Equals and force it to run for every value