Reputation: 1105
I want to be able to change a property in my main window from my user controls view model.
This is the connection
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?
Upvotes: 0
Views: 1522
Reputation: 1105
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);
}
}
}
Upvotes: 0
Reputation: 132548
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;
Upvotes: 1
Reputation: 186
You should call RaisePropertyChanged("TextInMainWindowViewModel");
in your MainWindowViewModel
Upvotes: 0