Hisham Maudarbocus
Hisham Maudarbocus

Reputation: 620

Property setter not working in XAML

There is a custom control that extends from Canvas and can only accept instances of the class Shape as its children. Consider the code below:

public class SvgGroup : Canvas
{

    // ...

    public Brush Fill
    {
        // Retuns the fill brush value of all the shape children, if they are all the same. Otherwise, the default value of Brush is returned
        get
        {
            Brush rtn = default(Brush);
            for (int i = 0; i < ShapeChildren.Count; i++)
            {
                Shape shape = ShapeChildren[i];
                if (i == 0) // First loop
                {
                    rtn = shape.Fill;
                }
                else if (rtn != shape.Fill) // Children shapes have different Fill value
                {
                    return default(Brush);
                }
            }

            return rtn;
        }

        // Sets the fill brush value of all the shape children
        set
        {
            foreach (Shape shape in ShapeChildren)
            {
                shape.Fill = value;
            }
        }
    }

    // ...
}

The problem is when setting the Fill property in XAML, nothing happens. However setting the Fill in code-behind works.

I was thinking of dependency properties, but the implementation in this scenario could be quite tricky.

Upvotes: 0

Views: 745

Answers (2)

Clemens
Clemens

Reputation: 128136

You could declare an Attached Property with Property Value Inheritance behavior.

When the property is set on any parent element, its value is inherited by all child elements. There is a PropertyChanged callback that checks if the element is a Shape and eventually applies the inherited Brush to the Shape's Fill property.

public static class ChildFillEx
{
    public static readonly DependencyProperty ChildFillProperty =
        DependencyProperty.RegisterAttached(
            "ChildFill", typeof(Brush), typeof(ChildFillEx),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.Inherits,
                ChildFillPropertyChanged));

    public static Brush GetChildFill(DependencyObject obj)
    {
        return (Brush)obj.GetValue(ChildFillProperty);
    }

    public static void SetChildFill(DependencyObject obj, Brush value)
    {
        obj.SetValue(ChildFillProperty, value);
    }

    private static void ChildFillPropertyChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var shape = obj as Shape;
        if (shape != null)
        {
            shape.Fill = (Brush)e.NewValue;
        }
    }
}

You would use it like this:

<Canvas local:ChildFillEx.ChildFill="Red">
    <Rectangle Width="100" Height="100" />
</Canvas>

Upvotes: 0

rmojab63
rmojab63

Reputation: 3629

I think you should define two dependency properties and you should keep one of them updated:

public class SvgGroup : Canvas
{

    public Brush Fill
    {
        get { return (Brush)GetValue(FillProperty); }
        set { SetValue(FillProperty, value); }
    }
    public static readonly DependencyProperty FillProperty
   = DependencyProperty.Register(
         "Fill",
         typeof(Brush),
         typeof(SvgGroup), 
         new FrameworkPropertyMetadata(Brushes.Red, OnFillPropertyChanged)
     );

    private static void OnFillPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        SvgGroup svg = (SvgGroup)d;
        if (e.NewValue != null && !e.NewValue.Equals(e.OldValue))
        {
            foreach (Shape shape in d.ShapeChildren)
            {
                shape.Fill = (Brush)e.NewValue;
            }
            d.OnShapeBrushChanged(); // Note that you should call this method in some other places too.
        }
    }

    public Brush FillDifferentBrush
    {
        get { return (Brush)GetValue(IsFillDifferentProperty); }
    }

    public static readonly DependencyProperty FillDifferentProperty
        = DependencyProperty.Register(
              "FillDifferentBrush",
              typeof(Brush),
              typeof(SvgGroup),
              new PropertyMetadata(null)
          );




    void OnShapeBrushChanged()
    {
        Brush rtn = default(Brush);
        for (int i = 0; i < ShapeChildren.Count; i++)
        {
            Shape shape = ShapeChildren[i];
            if (i == 0) // First loop
            {
                rtn = shape.Fill;
            }
            else if (rtn != shape.Fill) // Children shapes have different Fill value
            {
                SetValue(FillDifferentProperty, default(Brush));
            }
            else
                SetValue(FillDifferentProperty, rtn);
        }
    }

}

You should call OnShapeBrushChanged() properly (for example when you add new Shapes or when you change their Brush individually, or when you call Fill property) to keep it updated (Something like HasItems property of an ItemsControl).

Upvotes: 1

Related Questions