6

I'm trying to learn the basics of creating a custom panel in a WinRT XAML app. I have defined an attached dependency property and it's working as expected except i can't figure out how to get the property's callback for a child element to trigger the arrange or measure of the container.

What's the proper way to for a child to let it's container know that arrange and measure should be called again? In my WPF 4 unleashed book they use the FrameworkPropertyMetadataOptions.AffectsParentArrange but that doesn't seem to be available in WinRT.

public class SimpleCanvas : Panel
{
    #region Variables
    #region Left Property
    public static double GetLeft(UIElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        object value = element.GetValue(LeftProperty);
        Type valueType = value.GetType();
        return Convert.ToDouble(value);
    } 

    public static void SetLeft(UIElement element, double value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        element.SetValue(LeftProperty, value);
    }

    public static readonly DependencyProperty LeftProperty =
        DependencyProperty.RegisterAttached("Left", typeof(double), typeof(SimpleCanvas),
        new PropertyMetadata(0, OnLeftPropertyChanged));

    public static void OnLeftPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = (UIElement)source;
        // This doesn't cause ArrangeOverride below to be called
        element.InvalidateArrange();
    }
    #endregion

    #region Top Property
    public static double GetTop(UIElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        object value = element.GetValue(TopProperty);
        return (value == null) ? 0 : (double)value;
    }

    public static void SetTop(UIElement element, double value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        element.SetValue(TopProperty, value);
    }

    public static readonly DependencyProperty TopProperty =
        DependencyProperty.RegisterAttached("Top", typeof(double), typeof(SimpleCanvas),
        new PropertyMetadata(0, OnTopPropertyChanged));

    public static void OnTopPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = (UIElement)source;
        // This doesn't cause ArrangeOverride below to be called
        element.InvalidateArrange();
    }
    #endregion
    #endregion

    public SimpleCanvas()
    {

    }

    #region Methods
    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in this.Children)
        {
            child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
        }

        return new Size(0, 0);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach (UIElement child in this.Children)
        {
            double x = 0;
            double y = 0;

            double left = GetLeft(child);
            double top = GetTop(child);

            if (!double.IsNaN(left))
            {
                x = left;
            }
            if (!double.IsNaN(top))
            {
                y = top;
            }

            child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
        }

        return finalSize;
    }
    #endregion
}
Mario S
  • 11,715
  • 24
  • 39
  • 47

3 Answers3

3

I'm late to the party, but I went the same direction and faced the same issue. Here is my solution.

In your callback you call InvalidateArrange on the child element you attached you property to:

public static void OnTopPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
    UIElement element = (UIElement)source;
    // This doesn't cause ArrangeOverride below to be called
    element.InvalidateArrange();
}

But you should really invalidate the panel, by changing you code so:

public static void OnTopPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
    UIElement panel= VisualTreeHelper.GetParent(source) as UIElement;
    if(panel != null)       
        panel.InvalidateArrange();
}

And it should work (did for me).

  • I can't believe I just spent the better part of two days trying to figure this out... Thanks a lot! – Gaet Jun 29 '16 at 15:02
0

If InvalidateArrange alone doesn't work you could also try InvalidateMeasure or UpdateLayout.

Filip Skakun
  • 31,624
  • 6
  • 74
  • 100
  • 1
    Changed callback to InvalidateArrange(), InvalidateMeasure(), and UpdateLayout() but neither breakpoints in ArrangeOverride() nor MeasureOverride() are hit in the container. – Bryan Creswell Oct 20 '12 at 01:25
  • Perhaps it checks if any of the layout properties changed and if none had - it doesn't call it. You might need to change some of the layout properties then, but that could break existing bindings. – Filip Skakun Oct 21 '12 at 03:10
0

I had this problem with a child control which depended on the FontSize of the parent control. I solved the problem by traveling up the stack of parents and invalidating everything:

    static MyControl()
    {
        // replace base implementation of the dependent property 
        FontSizeProperty.OverrideMetadata(typeof(Scalar), 
            new FrameworkPropertyMetadata(SystemFonts.MessageFontSize, FrameworkPropertyMetadataOptions.Inherits, OnMeasureInvalidated));
    }

    private static void OnMeasureInvalidated(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        // recurse over parent stack
        while (true)
        {
            var parent = VisualTreeHelper.GetParent(sender) as UIElement;
            if (parent == null) return; // break on root element
            parent.InvalidateMeasure();
            sender = parent;
        }
    }
MovGP0
  • 7,267
  • 3
  • 49
  • 42