1
votes

I'm trying to follow the MVVM design paradigm with C# and XAML. I'm running into trouble with a nested user control. I'm trying to bind an element on the nested user control to one of the values in the ViewModel (which is bound to the View via the DataContext property). The same ViewModel is used for both the outer and nested user controls.

It partially works as is, but changes only go one-way from the ViewModel to the nested user control. I need the changes made in the nested user control to propagate back to the ViewModel.

Starting with the XAML for the main View, I have:

<UserControl>

   <!-- ... -->

   <UserControl.DataContext>
      <local:MyViewModel x:Name="myViewModel" />
   </UserControl.DataContext>

   <!-- ... -->

   <local:NestedUserControl
      x:Name="nestedUserControl"
      CustomNestedValue="{Binding Path=CustomValue, ElementName=myViewModel, Mode=TwoWay}" />

</UserControl>

In the C# code for the ViewModel:

// Constructor
public MyViewModel()
{
   CustomValue = true;
}

private bool _customValue;
public bool CustomValue
{
   get { return _customValue; }
   set
   {
       if (_customValue != value)
       {
          _customValue = value;
          RaisePropertyChanged ("CustomValue");
       }
   }
}

And in the code behind of the NestedUserControl, I have:

public static readonly DependencyProperty CustomNestedValueProperty =
   DependencyProperty.Register (
      "CustomNestedValue",
      typeof (bool),
      typeof (NestedUserControl),
      new FrameworkPropertyMetatdata
      {
         BindsTwoWayByDefault = true,
         PropertyChangedCallback = 
            new PropertyChangedCallback (CustomNestedValueChangedCallback)
      });

public bool CustomNestedValue
{
   get { return (bool) GetValue (CustomNestedValueProperty); }
   set { SetValue (CustomNestedValueProperty, value); }
}

protected static void CustomNestedValueChangedCallback (
   DependencyObject Source,
   DependencyPropertyChangedEventArgs e)
{
   bool value = (bool) e.NewValue;
   NestedUserControl control = source as NestedUserControl;
   control.OnCustomValueChange (value);
}

public void OnCustomValueChange (bool value)
{
   RaisePropertyChanged ("CustomNestedValue");

   // Do other stuff ...
}

// This function is where the nested user control gets direct
// interactions from the user which cause the dependency
// property to change.  When this event occurs, the change needs
// to be communicated back up to the view model.
private void _onPreviewMouseDown (object sender, MouseButtonEventArgs e)
{
   CustomNestedValue = !CustomNestedValue;
}

[Note: Not only do I set the binding mode to TwoWay when setting the binding in XAML, but I attempted to make this the default behavior of the DependencyProperty in the code above. No luck.]

Both the code behind for the nested user control and the ViewModel code contain the below PropertyChangedEventHandler event/response, which is necessary for the INotifyPropertyChanged interface. From what I understand, this is how bindings between XAML elements and the ViewModel are kept in sync.

public event PropertyChangedEventHandler PropertyChanged;

protected void RaisePropertyChanged(string propertyName)
{
   try
   {
      if (this.PropertyChanged != null)
         this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
   catch (Exception e)
   {
      //  ...
   }
}

When I run the code, whenever the RaisePropertyChanged function is called for the NestedUserControl, the PropertyChanged event is always null. This is only a problem for the nested usercontrol, and not the outer one. Shouldn't this event be automatically set via the binding mechanism?

I've been struggling with this for several days now to no avail. Any help would be much appreciated. Thanks!

1

1 Answers

2
votes

Binding to a DependencyObject operates without using the INotifyPropertyChanged interface. In fact, if you set a breakpoint in the getter or setter of the CustomNestedValue property of the NestedUserControl, you'll find it will never hit when binding in XAML. In essence, the INotifyPropertyChanged is a way of achieving binding without descending from DependencyObject.

When the MyViewModel.CustomValue is bound to the NestedUserControl, the binding code calls (in pseudo code):

NestedUserControl.SetBinding(binding, NestedUserControl.CustomNestedValueProperty)

The INotifyPropertyChanged.PropertyChanged event is never registered and will remain null. However, this doesn't necessarily answer why the value isn't going back to the ViewModel.

Regardless, you could remove a few moving pieces and go with

public static readonly DependencyProperty CustomNestedValueProperty =
    DependencyProperty.Register("CustomNestedValue",
                                typeof (bool),
                                typeof (NestedUserControl),
                                null);

public bool CustomNestedValue
{
    get { return (bool) GetValue (CustomNestedValueProperty); }
    set { SetValue (CustomNestedValueProperty, value); }
}

That's how most of my DependencyProperties are written and they do support TwoWay binding.