YH Kim
YH Kim

Reputation: 31

WPF Data Binding between ViewModel in Window and ViewModel in UserControl

I have a window and an usercontrol used within this window.

And I have done the following:

  1. Bound the window property to window's viewmodel property.
  2. Bound the usercontrol property to usercontrol's viewmodel property.
  3. Bound the usercontrol's property to window's viewmodel property.

In my opinion, I think if I set window property by specific value, usercontrol's viewmodel's property will be set to the same value.

But usercontrol's viewmodel's property was not set to the same value.

Here is whole codes.

MainWindow.xaml.cs

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

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Window1 w = new Window1();
            w.Mode = RegisterMode.Update;
            w.Show();
        }
    }

Window1.xaml

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMTest2" x:Name="window" x:Class="MVVMTest2.Window1"
        Title="Window1" Height="300" Width="300">
    <Window.DataContext>
        <local:WindowViewModel></local:WindowViewModel>
    </Window.DataContext>
    <Grid>
        <local:UserControl1 x:Name="uc1" HorizontalAlignment="Left" Height="100" Margin="77,116,0,0" VerticalAlignment="Top" Width="100" Mode="{Binding DataContext.Mode, ElementName=window, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <Button Content="Button" HorizontalAlignment="Left" Margin="156,43,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
    </Grid>
</Window>

Window1.xaml.cs

    public partial class Window1 : Window
    {
        public RegisterMode Mode
        {
            get { return (RegisterMode)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Mode.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ModeProperty =
            DependencyProperty.Register("Mode", typeof(RegisterMode), typeof(Window1), new PropertyMetadata(RegisterMode.None));

        public Window1()
        {
            InitializeComponent();

            Binding modeBinding = new Binding();
            modeBinding.Source = this.DataContext;
            modeBinding.Path = new PropertyPath("Mode");
            modeBinding.Mode = BindingMode.TwoWay;
            modeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            this.SetBinding(Window1.ModeProperty, modeBinding);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show((uc1.DataContext as UCViewModel).Mode.ToString());
        }
    }

WindowViewModel.cs

    public class WindowViewModel : INotifyPropertyChanged
    {
        RegisterMode mode = RegisterMode.None;
        public event PropertyChangedEventHandler PropertyChanged;

        public RegisterMode Mode
        {
            get { return this.mode; }
            set
            {
                this.mode = value;
                RaisePropertyChanged("Mode");
            }
        }

        void RaisePropertyChanged(string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

UserControl1.xaml

<UserControl x:Class="MVVMTest2.UserControl1"
             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:MVVMTest2"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.DataContext>
        <local:UCViewModel></local:UCViewModel>
    </UserControl.DataContext>
    <Grid>

    </Grid>
</UserControl>

UserControl1.xaml.cs

    public partial class UserControl1 : UserControl
    {


        public RegisterMode Mode
        {
            get { return (RegisterMode)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Mode.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ModeProperty =
            DependencyProperty.Register("Mode", typeof(RegisterMode), typeof(UserControl1), new PropertyMetadata(RegisterMode.None));


        public UserControl1()
        {
            InitializeComponent();

            Binding modeBinding = new Binding();
            modeBinding.Source = this.DataContext;
            modeBinding.Path = new PropertyPath("Mode");
            modeBinding.Mode = BindingMode.TwoWay;
            modeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            this.SetBinding(UserControl1.ModeProperty, modeBinding);
        }
    }

UCViewModel.cs

    public class UCViewModel : INotifyPropertyChanged
    {
        RegisterMode mode = RegisterMode.None;
        public event PropertyChangedEventHandler PropertyChanged;

        public RegisterMode Mode
        {
            get { return this.mode; }
            set
            {
                this.mode = value;
                RaisePropertyChanged("Mode");
            }
        }

        void RaisePropertyChanged(string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));                
            }
        }
    }

What point am I doing wrong? Please let me know about it.

Upvotes: 2

Views: 2206

Answers (1)

janonimus
janonimus

Reputation: 2897

Try this solution using composition between your ViewModels.

Window1.xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMTest2" x:Name="window" x:Class="MVVMTest2.Window1"
        Title="Window1" Height="300" Width="300"
        Mode={Binding UcViewModel.Mode}> <!-- Added this binding -->

    <!-- MOVED THIS TO THE CODE-BEHIND
    <Window.DataContext>
        <local:WindowViewModel></local:WindowViewModel>
    </Window.DataContext>
    -->

    <Grid>
        <local:UserControl1 x:Name="uc1" HorizontalAlignment="Left" Height="100" Margin="77,116,0,0" VerticalAlignment="Top" Width="100" 
                            Mode="{Binding UcViewModel.Mode}"/>
        <Button Content="Button" HorizontalAlignment="Left" Margin="156,43,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
    </Grid>
</Window>

Window1.xaml.cs

public partial class Window1 : Window
{
    public RegisterMode Mode
    {
        get { return (RegisterMode)GetValue(ModeProperty); }
        set { SetValue(ModeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Mode.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ModeProperty =
        DependencyProperty.Register("Mode", typeof(RegisterMode), typeof(Window1), new PropertyMetadata(RegisterMode.None));

    public Window1()
    {
        InitializeComponent();

        /* THIS IS NOT NEEDED
        Binding modeBinding = new Binding();
        modeBinding.Source = this.DataContext;
        modeBinding.Path = new PropertyPath("Mode");
        modeBinding.Mode = BindingMode.TwoWay;
        modeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        this.SetBinding(Window1.ModeProperty, modeBinding)
        */

        // Use the following instead:
        this.DataContext = new WindowViewModel();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show((uc1.DataContext as UCViewModel).Mode.ToString());
    }
}

UserControl1.xaml

<UserControl x:Class="MVVMTest2.UserControl1"
             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:MVVMTest2"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

<!-- THIS PART IS NOT NEEDED
     <UserControl.DataContext>
        <local:UCViewModel></local:UCViewModel>
    </UserControl.DataContext>
 -->
    <Grid>

    </Grid>
</UserControl>

UserControl1.xaml.cs

public partial class UserControl1 : UserControl
{

    public RegisterMode Mode
    {
        get { return (RegisterMode)GetValue(ModeProperty); }
        set { SetValue(ModeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Mode.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ModeProperty =
        DependencyProperty.Register("Mode", typeof(RegisterMode), typeof(UserControl1), new PropertyMetadata(RegisterMode.None));


    public UserControl1()
    {
        InitializeComponent();

        /* THIS IS NOT NEEDED
        Binding modeBinding = new Binding();
        modeBinding.Source = this.DataContext;
        modeBinding.Path = new PropertyPath("Mode");
        modeBinding.Mode = BindingMode.TwoWay;
        modeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        this.SetBinding(UserControl1.ModeProperty, modeBinding);
        */
    }
}

WindowViewModel.cs

public class WindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    /* Remove this:
    RegisterMode mode = RegisterMode.None;
    public RegisterMode Mode
    {
        get { return this.mode; }
        set
        {
            this.mode = value;
            RaisePropertyChanged("Mode");
        }
    }
    */

    // Use composition instead
    UCViewModel _ucViewModel = null;
    public UCViewModel UcViewModel
    {
        get 
        { 
            if (_ucViewModel == null)
            {
                _ucViewModel = new UCViewModel();
            }
            return _ucViewModel; 
        }
    }

    void RaisePropertyChanged(string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

And one more thing, you might want to rename the ViewModel property Mode to another name like RegMode. The reason is that it's confusing in the XAML code during binding.

From this: {Binding Mode, Mode=TwoWay} Or, this: {Binding Path=Mode, Mode=TwoWay} To this less confusing one: {Binding RegMode, Mode=TwoWay}

Upvotes: 3

Related Questions