0
votes

I want to be able to change a property in my main window from my user controls view model.

This is the connection

  • MainWindow
    • I bind my property from its view model to my usercontrol
  • MainWindowViewModel
    • My property lies here, it does get updated when user control property changes
  • UserControl1
    • its dependency property that's binded to Main Window View Model returns a value from UserControlViewModel
  • UserControl1ViewModel
    • The logic that changes the property (which is supposed to update MainWindowViewModel) lies here.

I can do the binding between all of them, but the problem is when I update my property from the bottom layer (UserControlViewModel), it does not update my property neither in UserControl or in my MainWindowViewModel.

Here is all my code (I have also uploaded the project on my google drive)

MainWindow.xaml

<Window x:Class="WpfApplicationViewToViewModel.MainWindow"
        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:WpfApplicationViewToViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="367" Width="624">
    <StackPanel>
        <local:UserControl1 TextInUserControl="{Binding DataContext.TextInMainWindowViewModel,
            Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
        </local:UserControl1>


        <Button Content="Test MainWindow VM" Command="{Binding CommandTestMWVM}" ></Button>
        <Separator></Separator>

    </StackPanel>
</Window>

MainVindow.xaml.cs

using System.Windows;

namespace WpfApplicationViewToViewModel
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }


    }
}

MainWindowViewModel.cs

using System;
using System.Windows;
using System.Windows.Input;

namespace WpfApplicationViewToViewModel
{
    class MainWindowViewModel : ViewModelBase
    {
        public string TextInMainWindowViewModel
        {
            get
            {
                return _textInMainWindowViewModel;
            }
            set
            {
                _textInMainWindowViewModel = value;
                RaisePropertyChanged("TextInMainWindowViewModel");
            }
        }
        private string _textInMainWindowViewModel { get; set; }

        //test button
        public MainWindowViewModel()
        {
            _commandTestMWVM = new RelayCommand(new Action<object>(TestMWVM));
        }

        #region [Command] CommandTestMWVM
        public ICommand CommandTestMWVM
        {
            get { return _commandTestMWVM; }
        }
        private ICommand _commandTestMWVM;
        private void TestMWVM(object obj)
        {
            TextInMainWindowViewModel = TextInMainWindowViewModel + "MWVM";
            MessageBox.Show("TextInMainWindowModel " + TextInMainWindowViewModel);
        }
        #endregion
    }
}

UserControl1.xaml (includes just two buttons for testing purposes)

<UserControl x:Class="WpfApplicationViewToViewModel.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:WpfApplicationViewToViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <Button Content="Test UC" Click="Button_Click"></Button>
        <Button Content="Test UCVM" Command="{Binding CommandTestUCVM}" ></Button>
    </StackPanel>
</UserControl>

UserControl1.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplicationViewToViewModel
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        private  UserControl1ViewModel VM = new UserControl1ViewModel();

        public UserControl1()
        {
            InitializeComponent();
            this.DataContext = VM;

            //http://stackoverflow.com/questions/15132538/twoway-bind-views-dependencyproperty-to-viewmodels-property
            //does not work because breaks binding somewhere
            //string propertyInViewModel = "TextInUserControlViewModel";
            //var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
            //this.SetBinding(TextInUserControlProperty, bindingViewMode);

        }

        //dependency property declaration
        public static DependencyProperty TextInUserControlProperty =
            DependencyProperty.Register("TextInUserControl",
                typeof(string),
                typeof(UserControl1)

                );


        public string TextInUserControl
        {
            get {

                return (DataContext as UserControl1ViewModel).TextInUserControlViewModel;
            }
            set
            {
                (DataContext as UserControl1ViewModel).TextInUserControlViewModel = value;
                this.SetValue(TextInUserControlProperty, value);
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            TextInUserControl = TextInUserControl + "UC";
            MessageBox.Show("TextInUserControl : " + TextInUserControl);
        }
    }
}

UserControl1ViewModel.cs

using System;
using System.Windows;
using System.Windows.Input;

namespace WpfApplicationViewToViewModel
{
    class UserControl1ViewModel : ViewModelBase
    {

        private string _textInViewModel;
        public string TextInUserControlViewModel
        {
            get { return _textInViewModel; }
            set {
                _textInViewModel = value;
                RaisePropertyChanged("TextInUserControlViewModel");
            } }

        //test button
        public UserControl1ViewModel()
        {
            _commandTestUCVM = new RelayCommand(new Action<object>(TestUCVM));
        }

        #region [Command] CommandTestUCVM
        public ICommand CommandTestUCVM
        {
            get { return _commandTestUCVM; }
        }
        private ICommand _commandTestUCVM;
        private void TestUCVM(object obj)
        {
            TextInUserControlViewModel = TextInUserControlViewModel + "UCVM";
            MessageBox.Show("TextInUserControlViewModel : " + TextInUserControlViewModel);
        }
        #endregion
    }
}

Any help is really really appreciated because I've been trying to figure out this system (reading usercontrols viewmodel from mainwindow) for almost a week.

To make my question more clear:

TextInUserControl <=> TextInMainWindowViewModel : works succesfuly

TextInUserControl => TextInUserControlViewModel : works but when I change TextInUserControlViewModel, TextInUserControl doesn't get updated automatically.

Is there anyway I can let TextInUserControl know that TextInUserControlViewModel is changed?

3

3 Answers

1
votes

You are setting your UserControl's DataContext to a UserControl1ViewModel instance, then binding the TextInUserControl property to DataContext.TextInMainWindowViewModel, which is resulting in it looking for the property UserControl1ViewModel.DataContext.TextInMainWindowViewModel, which does not exist.

One of the first rules of working with WPF/MVVM : NEVER set this.DataContext = x; in the code behind a user-control unless you intend to never pass that control any outside value.

Instead what you probably want is to add an instance of UserControl1ViewModel onto MainWindowViewModel, and bind the UserControl.DataContext to that instance.

For example,

class MainWindowViewModel : ViewModelBase
{
    // add this property
    public UserControl1ViewModel UserControlData { ... }

    public string TextInMainWindowViewModel { ... }
    public ICommand CommandTestMWVM { ... }
}
<!-- change binding to this -->
<local:UserControl1 DataContext="{Binding UserControlData}" />

and get rid of the following in your UserControl constructor

this.DataContext = VM;
0
votes

You should call RaisePropertyChanged("TextInMainWindowViewModel"); in your MainWindowViewModel

0
votes

I've fixed the problem by using a "bridge property". I copy the solution that might help the others having the same problem:

UserControl1.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplicationViewToViewModel
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            this.DataContext = new UserControl1ViewModel(); 
            /*
            [Bridge Binding ©]
            It's not possible to bind 3 properties.
            So this bridge binding handles the communication
            */
            string propertyInViewModel = "TextInUserControlViewModel";
            var bindingViewMode = new Binding(propertyInViewModel);
            bindingViewMode.Mode = BindingMode.TwoWay;
            this.SetBinding(BridgeBetweenUCandVWProperty, bindingViewMode);


        }
        #region Bridge Property
        public static DependencyProperty BridgeBetweenUCandVWProperty =
            DependencyProperty.Register("BridgeBetweenUCandVW",
        typeof(string),
        typeof(UserControl1),
        new PropertyMetadata(BridgeBetweenUCandVWPropertyChanged)
        );

        public string BridgeBetweenUCandVW
        {
            get
            {
                return (string)GetValue(BridgeBetweenUCandVWProperty);
            }
            set
            {
                this.SetValue(BridgeBetweenUCandVWProperty, value);
            }
        }

        private static void BridgeBetweenUCandVWPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((UserControl1)d).TextInUserControl = (string)e.NewValue;
        }
        #endregion

        #region TextInUserControl Property
        public static DependencyProperty TextInUserControlProperty =
            DependencyProperty.Register("TextInUserControl",
                typeof(string),
                typeof(UserControl1),
                new PropertyMetadata(OnTextInUserControlPropertyChanged)
                );

        private static void OnTextInUserControlPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((UserControl1ViewModel)((UserControl)d).DataContext).TextInUserControlViewModel = (string)e.NewValue;
        }

        public string TextInUserControl
        {
            get {
               return (string)GetValue(TextInUserControlProperty);
            }
            set
            {
                this.SetValue(TextInUserControlProperty, value);
            }
        }
        #endregion
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            TextInUserControl += "[UC]";
            MessageBox.Show("TextInUserControl : " + TextInUserControl);
        }
    }
}