1

In WPF I have been trying to figure out how to keep a views dependency property and one of it's view model's properties in sync for a while now without any luck. I have done a fair amount research into the subject but none of the suggested solutions are working for me and I was hoping someone could help me find what I am missing.

I attempted many of the things suggested in this post, Twoway-bind view's DependencyProperty to viewmodel's property?, because of all the things I read it looked to be the most promising, but was never able to get the results I was looking for.

I have written a simple program to demonstrate that issue I am having. In it I set the the property IntValue in MainWindowViewModel to 2 and then Bind it to a dependency property created in the UserControl IncrementIntView. Then when I push the button in IncrementIntView it increases the value of IntValue by one. This all works fine inside the UserControl IncrementIntView but I can't figure out how to send the updated IntValue back to MainWindowViewModel, it stays set to 2.

IncrementIntView.xaml.cs

public partial class IncrementIntView : UserControl
{
    public int IntValue
    {
        get { return (int)GetValue(IntValueProperty); }
        set { SetValue(IntValueProperty, value); }
    }

    public static readonly DependencyProperty IntValueProperty =
        DependencyProperty.Register("IntValue", typeof(int), typeof(IncrementIntView),
                                        new PropertyMetadata(-1, new PropertyChangedCallback(IntValueChanged)));

    private static void IntValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        IncrementIntView detailGroup = dependencyObject as IncrementIntView;
        if (e.NewValue != null)
        {
            detailGroup.ViewModel.IntValue = (int)e.NewValue;
        }
    }

    public IncrementIntView()
    {
        InitializeComponent();
    }
}

IncrementIntViewModel.cs

public class IncrementIntViewModel : ViewModelBase
{
    private int intValue;
    public int IntValue
    {
        get { return intValue; }
        set { SetProperty(ref intValue, value); }
    }

    public IncrementIntViewModel()
    {
        incrementIntCommand = new Command(IncrementInt);
    }

    private Command incrementIntCommand;
    public Command IncrementIntCommand { get { return incrementIntCommand; } }
    public void IncrementInt()
    {
        IntValue++;
    }
}

IncrementIntView.xaml

<UserControl.DataContext>
    <local:IncrementIntViewModel x:Name="ViewModel" />
</UserControl.DataContext>

<Grid>
    <StackPanel Orientation="Horizontal">
        <Label Content="{Binding IntValue}" />
        <Button Content="Increment" Command="{Binding IncrementIntCommand}" Width="75" />
    </StackPanel>
</Grid>

MainWindow.xaml.cs

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

MainWindowViewModel.cs

public class MainWindowViewModel : ViewModelBase
{
    private int intValue = 2;
    public int IntValue
    {
        get { return intValue; }
        set { SetProperty(ref intValue, value); }
    }
}

MainWindow.xaml

<Window.DataContext>
    <local:MainWindowViewModel x:Name="ViewModel"/>
</Window.DataContext>

<Grid>
    <StackPanel Margin="10">
        <local:IncrementIntView IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
        <Label Content="{Binding IntValue}" />
    </StackPanel>
</Grid>
  • Note that `ElementName=ViewModel` is entirely redundant. Since the MainWindowViewModel instance is already assigned to the Window's DataContext, you don't need to explicitly specify the Binding source. The Binding should just be `IntValue="{Binding IntValue, Mode=TwoWay}"`, because `UpdateSourceTrigger=PropertyChanged` is also the default. – Clemens Aug 29 '17 at 05:56

1 Answers1

0

I can see that your code is passing the IntValue from MainWindowViewModel's property to IncrementIntView's dependency property to IncrementIntViewModel's property.

The increment button is updating the IncrementIntViewModel's IntValue property. Unfortunately, whatever happens in the IncrementIntViewModel is not being reflected back to the IncrementIntView's IntValue dependency property. The TwoWay Mode is not between IncrementIntView's dependency property and IncrementIntViewModel's property, but it is between MainWindowViewModel's property to IncrementIntView's dependency property.

The easy solution: Bind the MainWindow's Label to IncrementIntViewModel's IntValue property without bothering the View's property.

<local:IncrementIntView x:Name="iiv" IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding DataContext.IntValue, ElementName=iiv}" />
<!--You need to specify DataContext.IntValue, because you have same name for both view's dependency property and viewmodel's property-->

Here you can see that MainWindowViewModel's IntValue is not that important, because it just passes the value to IncrementIntViewModel once and never have the value updated ever.

The other solution: You need to trigger value change back to MainViewModel's property.

First thing first, there is no connection between MainViewModel and IncrementIntViewModel. One solution is to make the MainViewModel to be singleton, so that when increment is done inside the IncrementIntViewModel, you want to update the MainViewModel's property as well.

In MainViewModel.cs

public static MainWindowViewModel SingletonInstance { get; set; }
public MainWindowViewModel()
{
    if (SingletonInstance == null)
    {
        SingletonInstance = this;
    }
}

In IncrementIntViewModel.cs

public void IncrementInt()
{
    IntValue++;
    MainWindowViewModel.SingletonInstance.IntValue = IntValue;
}

The other other solution: Similar to the above solution, but we don't need to make Singleton instance of MainWindowViewModel, because MainWindow is singleton to begin with.

In IncrementIntViewModel.cs

public void IncrementInt()
{
    IntValue++;
    ((MainWindowViewModel)App.Current.MainWindow.DataContext).IntValue = IntValue;
}

If your intention is to update the IntValue from IncrementViewModel's property to IncrementView's dependency property, then you might ask why you need to do this, because MVVM is supposed to separate between V and VM. V should be looking to VM, but not the other way around.

kurakura88
  • 2,185
  • 2
  • 12
  • 18
  • your other and other other solutions both work, but as you say this not valid for MVVM. I am going to take a step back and reevaluate my approach. Thanks for the help. – ThatBaldGuy13 Aug 29 '17 at 04:54