0

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?

U. Bulle
  • 1,075
  • 9
  • 20

3 Answers3

1

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;
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Thanks for the tips. I'm new to wpf and mvmm concept, and it's giving me cancer already. You think setting datacontext from mainwindow would fix the problem? I'm unsure because the connection between `TextInMainWindowViewModel` and `UserControl1` works already fine. Problematic side is between usercontrol and its viewmodel: The problem occurs when I change the property in `UserControl1ViewModel`, because `TextInMainWindowViewModel` does not know about the update. I've updated the last lines of my question to make it more clear. – U. Bulle Oct 22 '15 at 15:12
  • @U.Bulle I almost never set my DataContext in code behind, except for setting the initial application DataContext. Everything else is done in bindings, usually by inheriting the DataContext although sometimes I will do a DataContext binding as well. If you're new to WPF, I'd recommend reading through [this answer](http://stackoverflow.com/a/15684569/302677), and in particular the post [What is this "DataContext" you speak of?](https://rachel53461.wordpress.com/2012/07/14/what-is-this-datacontext-you-speak-of/) – Rachel Oct 22 '15 at 15:15
  • Ideally you'd want your `UserControl1ViewModel` object on your `MainWindowViewModel`, like I have in my answer here, and if `MainWindowViewModel` needs to know when `TextInUserControl1` changes, it should subscribe to the `UserControl1ViewModel.PropertyChanged` event. – Rachel Oct 22 '15 at 15:16
  • Internet is a small place, I've stumbled upon your website more than once when I was preparing myself for WPF and I loved the way you present information: simple and clear. What an honor for me to welcome you in my question :) I've read your material and second post, I understand now your point. – U. Bulle Oct 22 '15 at 15:36
  • I've got two questions. _A little background info:_ I actually need this design for a captcha box user control. I want to bind to IsValidated bool and see in my login window if captcha entered is validated or not to move further. _Question 1_ Doesn't setting up datacontext from mainwindow decrease the reusability of the control? _Question 2_ Is my design any good? I mean should I just skip the usercontrols viewmodel (even though it has logics in it) and put all the code in usercontrol? Or should I use a static variable to check if captcha is validated (my worst case scenario)? Any tips? – U. Bulle Oct 22 '15 at 15:37
  • @U.Bulle I like the idea of having a separate UserControl for handling the captcha, as it's a component that would likely be used in other places. I would either make all properties the UC needs be DependencyProperties to be bound in the XAML, with no UserControl ViewModel at all, or put all properties in the UC VM and have no DPs at all. For a captcha, I would probably make a DP for IsValidated, and hardcode the random characters and logic to test if entered values are equal into the code-behind the UC because it is view-specific logic that is not related to the VM at all. – Rachel Oct 22 '15 at 15:55
  • As for your other question, yes setting the DataContext from the code behind any Window or UserControl decreases its reusability, however in many cases a Window or UC is only meant to be used with a specific DataContext. I typically will manually set the DataContext of the main application once in code-behind, and everything else gets it's DataContext via inheritance or bindings. So if MainWindow is your top level component, then it makes sense to set it's DataContext in code behind. – Rachel Oct 22 '15 at 15:59
  • (Also, glad my blog posts helped you! :D) – Rachel Oct 22 '15 at 15:59
  • Thanks for crystal claryfing everything in my mind. You've been friendly and helpful. I'm sure you help thousands of others on internet even though you don't get many feedbacks. I couldn't sleep good because of thinking what I'm doing wrong last week :) I was really stuck and none of my teachers (i'm getting .net education) know anything about WPF then baam hero of WPF on internet helped me. I wish I had some points in this website to spare on you, or could find donation section on your website to buy you a coffee. I at least send you some good karma sir, thanks for all, have a nice weekend! – U. Bulle Oct 22 '15 at 16:21
  • I'm glad I was able to help you! Good luck to you in your project :) – Rachel Oct 22 '15 at 20:32
0

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

Dominik S
  • 176
  • 4
  • 18
  • Thanks. I've just added it in `set{}`. But I never want to change the property from `MainWindowViewModel` anyway, I just want to be able to read it from `UserControl1ViewModel`, and unfortunately `RaisePropertyChanged("TextInUserControlViewModel");` in `UserControl1ViewModel` doesn't update UserControl/MainWindow – U. Bulle Oct 22 '15 at 15:14
0

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);
        }
    }
}
U. Bulle
  • 1,075
  • 9
  • 20