0

This is a continuation of a question here: Trying to setup a custom DependencyObject. Clearly missing something. It's not practical to edit the original question; changes are too great. So I'm starting a fresh question.

I'm trying to setup bindings between custom DependencyObjects in my UWP app. The relevant code is below. I am seeing calls to ActualWidthPropertyChanged, but they are not triggering any call to WidthPropertyChanged. What am I missing?

class WindowsElement: DependencyObject
{
    public WindowsElement()
    {
    }
    public double Width
    {
        get
        {
          return (double)GetValue(WidthProperty);
        }
        set
        {
          SetValue(WidthProperty, value);
        }
    }

    private static void WidthPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
      WindowsElement element = (WindowsElement)o;
      double width = (double)e.NewValue;
      CommonDebug.LogLine("WPC", element, o, width);
      element.Width = width;
    }

   private static void ActualWidthPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
     {
       WindowsElement element = (WindowsElement)o;
       double width = (double)e.NewValue;
       CommonDebug.LogLine("AWPC", o, e, width, element.Width);
       element.ActualWidth = width;
     }
     public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(
        "Width",
        typeof(double),
        typeof(WindowsElement),
       new PropertyMetadata((double)0, WidthPropertyChanged));

      public double ActualWidth {
        get
          {
            return (double)GetValue(ActualWidthProperty);
          }
        set
            {
              SetValue(ActualWidthProperty, value);
            }
        }


    public static readonly DependencyProperty ActualWidthProperty =  
      DependencyProperty.Register(
        "ActualWidth",
        typeof(double),
        typeof(WindowsElement),
        new PropertyMetadata((double)0, ActualWidthPropertyChanged));


    public static void MessWithBindings()
    {
        WindowsElement we1 = new WindowsElement();
        WindowsElement we2 = new WindowsElement();
        var b = new Binding
          {
            Source = we2,
            Path = new PropertyPath("ActualWidth")
          };

        BindingOperations.SetBinding(we1, WindowsElement.WidthProperty, b);
        we2.ActualWidth = 13;
        CommonDebug.LogLine(we1, we1.Width,  we1.ActualWidth, we2, we2.Width, we2.ActualWidth);
    }
}
Community
  • 1
  • 1
William Jockusch
  • 26,513
  • 49
  • 182
  • 323

2 Answers2

1

I am seeing calls to ActualWidthPropertyChanged, but they are not triggering any call to WidthPropertyChanged. What am I missing?

To solve this question, you would need to implement the INotifyPropertyChanged interface on the source object so that the source can report changes.

Please see the following code:

class WindowsElement : DependencyObject, INotifyPropertyChanged
{
    public WindowsElement()
    {
    }

    public double Width
    {
        get
        {
            return (double)GetValue(WidthProperty);
        }
        set
        {
            SetValue(WidthProperty, value);
        }
    }

    private static void WidthPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        WindowsElement element = (WindowsElement)o;
        double width = (double)e.NewValue;
        CommonDebug.LogLine("WPC", element, o, width);
        //element.Width = width;
        element.RaisedPropertyChanged("Width");
    }

    private static void ActualWidthPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        WindowsElement element = (WindowsElement)o;
        double width = (double)e.NewValue;
        CommonDebug.LogLine("AWPC", o, e, width, element.Width);
        //element.ActualWidth = width;
        element.RaisedPropertyChanged("ActualWidth");
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisedPropertyChanged(string PropertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
    }

    public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(
        "Width",
        typeof(double),
        typeof(WindowsElement),
        new PropertyMetadata((double)0, WidthPropertyChanged));

    public double ActualWidth
    {
        get
        {
            return (double)GetValue(ActualWidthProperty);
        }
        set
        {
            SetValue(ActualWidthProperty, value);
        }
    }

    public static readonly DependencyProperty ActualWidthProperty = DependencyProperty.Register(
        "ActualWidth",
        typeof(double),
        typeof(WindowsElement),
        new PropertyMetadata((double)0, ActualWidthPropertyChanged));

    public static void MessWithBindings()
    {
        WindowsElement we1 = new WindowsElement();
        WindowsElement we2 = new WindowsElement();
        var b = new Binding
        {
            Source = we2,
            Path = new PropertyPath("ActualWidth")
        };

        BindingOperations.SetBinding(we1, WindowsElement.WidthProperty, b);
        we2.ActualWidth = 13;
        CommonDebug.LogLine(we1, we1.Width, we1.ActualWidth, we2, we2.Width, we2.ActualWidth);
    }
}
Jay Zuo
  • 15,653
  • 2
  • 25
  • 49
Xie Steven
  • 8,544
  • 1
  • 9
  • 23
  • See [**Custom dependency properties**](https://msdn.microsoft.com/en-us/windows/uwp/xaml-platform/custom-dependency-properties), section **Implementing the wrapper**: *In all but exceptional circumstances, your wrapper implementations should perform only the GetValue and SetValue operations. Otherwise, you'll get different behavior when your property is set via XAML versus when it is set via code. For efficiency, the XAML parser bypasses wrappers when setting dependency properties; and talks to the backing store via SetValue*. – Clemens Dec 06 '16 at 10:55
  • So (if necessary at all) raising the PropertyChanged event should be done in the PropertyChangedCallback. – Clemens Dec 06 '16 at 10:55
  • 1
    @Clemens You’re right. Calling PropertyChanged in wrapper was not a good practice. I have updated my reply. This issue should be a special case. It would need to implement the INotifyPropertyChanged interface. – Xie Steven Dec 07 '16 at 02:56
0

Not sure why in UWP a one-way Binding from one dependency property to another doesn't automatically update the target property (as it does in WPF).

However, you could simply revert the direction of the Binding and make it two-way:

var b = new Binding
{
    Source = we1,
    Path = new PropertyPath("Width"),
    Mode = BindingMode.TwoWay
};

BindingOperations.SetBinding(we2, WindowsElement.ActualWidthProperty, b);
we2.ActualWidth = 13;
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Still no callback to WidthPropertyChanged . . . – William Jockusch Dec 06 '16 at 11:54
  • It works for me. And you must not call `element.Width = width;` and `element.ActualWidth = width;` in the PropertyChangedCallbacks. The property has already changed, no need to set it again. – Clemens Dec 06 '16 at 11:55