Editting a member within a class does not invoke PropertyChangedCallback of DependencyProperty

46 Views Asked by At

I’m developing a WPF desktop application, utilizing MVVM framework. I have an enum and a class defined as follows:

public enum eTypes { A, B };
public class MyClass
{
    public eTypes MemberEnum { get; set; }
}

In my Model:

public class Model : INotifyPropertyChanged
{
    public MyClass Stiffness { get; }   // This must be read-only, since it's actually linked to a dictionary that's expected not to be changed

    private int _num;
    public int Num
    { 
        get => _num;
        set
        {
                _num = value;
                Stiffness.MemberEnum = A; // Changed
                OnPropertyChanged(nameof(Stiffness));
        }
    }

    public void OnPropertyChanged([CallerMemberName]string propertyName = null, bool includeEntirePropChanged = true)
    {
        PropertyChanged?. Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

Then, there is an custom UserControl at the View:

public partial class PropertyGrid : UserControl
{
    public static readonly DependencyProperty RowListProperty =
     DependencyProperty.Register(
                       "PropertyList",
            typeof(MyClass), typeof(PropertyGrid),
            new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnPropsListChanged))

    private static void OnPropsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    { // Desired operations }

    public MyClass PropertyList
    {
        get
        {
            return (GetValue(RowListProperty) as IRFCProperty);
        }
        set
        {
            SetValue(RowListProperty, value);
        }
    }
}

And finally, the .xaml code for Views that uses the UserControl (DataContext correctly set to instance of Model)

<PropertyGrid PropertyList="{Binding Stiffness, UpdateSourceTrigger=PropertyChanged}" />

The desired behaviour would be: when the user changes Model’s Num => Stiffness’s MemberEnum is changed => OnPropertyChanged(nameof(Stiffness)) => Stiffness is binded with PropertyList in the View, which is backed by RowListProperty => OnPropsListChanged should be invoked, which updates the view.

However, OnPropsListChanged never gets called, because the DependencyProperty’s “effective value changed” will indicate that the Stiffness object is the same (even though we changed one of its member variables). The only work around for me now is to create a Clone for the object that’s returned from Stiffness’s get, then call the OnPropertyChanged(nameof(Stiffness)), which then works. However, this is very much prone to error (the provided code is made simple to illustrate my point).

I tried adding FrameworkPropertyMetadataOptions.AffectsRender flag to the declaration of RowListProperty, but it doens’t work, as well as FrameworkPropertyMetadataOptions.BindsTwoWayByDefault (because Stiffness is read-only)

Is there a way to have an option to edit the ‘effective value’ check of DependencyObject’s SetValue?

1

There are 1 best solutions below

0
AudioBubble On

You are only changing a property of MyClass but not the Stiffness property value/instance itself.
In other words, because there is no assignment to the PropertyList property the property changed callback is not invoked.

What you should do is to observe the new PropertyList value for property changes:

public static readonly DependencyProperty RowListProperty = DependencyProperty.Register(
  "PropertyList",
  typeof(IRFCProperty), 
  typeof(PropertyGrid),
  new FrameworkPropertyMetadata(default, OnPropsListChanged));

private static void OnPropsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ 
  var propertyGrid = d as PropertyGrid;
  propertyGrid.OnPropsListChanged(e.OldValue as MyClass, e.NewValue as MyClass);
}

protected virtual void void OnPropsListChanged(MyClass oldValue, MyClass newValue)
{ 
  if (oldValue is INotifyPropertyChanged oldPropertyChangedSource)
  {
    oldPropertyChangedSource.PropertyChanegd -= OnPropertyListValuePropertyChanged;
  }

  if (newValue is INotifyPropertyChanged newPropertyChangedSource)
  {
    newPropertyChangedSource.PropertyChanegd += OnPropertyListValuePropertyChanged;
  }
}

private void OnPropertyListValuePropertyChanged(object sender, PropertyChangedEventArgs e)
{
  var myClass = sender as MyClass;

  // If you are interested in particular properties,
  // you can filter the relevant property names
  switch (e.PropertyName)
  {
    case nameof(MyClass.MemberEnum): 
      OnPropertyListMemberEnumChanged(myClass.MemberEnum); break;
  }
}

private void OnPropertyListMemberEnumChanged(eTypes eType)
{
  // Desired operations
}