0

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.

2 Answers2

1

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).

rmojab63
  • 3,513
  • 1
  • 15
  • 28
  • is there a way to "listen" for changes in the brush of the shape children? – Hisham Maudarbocus Feb 16 '17 at 08:09
  • I am not sure. However, don't let user to directly change the brush of a specific Shape. Create a public method for this purpose. Other than that, you should check brushes in just two cases: when a new Item is added/removed and when Fill property is set. – rmojab63 Feb 16 '17 at 08:13
  • What's the purpose of `IsFillDifferentBrush`? It seems entirely redundant. Neither do you explain its usage, not do you actually use it in your code sample. Besides that, you don't adhere to dependency property naming conventions. When the property is `IsFillDifferentBrush`, the DependencyProperty field should be named `IsFillDifferentBrushProperty`, and the name passed to register must be `"IsFillDifferentBrush"`. – Clemens Feb 16 '17 at 08:41
  • @Clemens its a typo. Its a Brush rather than a bool (as it is set in ``OnShapeBrushChanged`` methhod). I ll edit. – rmojab63 Feb 16 '17 at 08:44
  • You never need two properties for OP's use case. What you actually need is an event or something alike to react on added children. – Clemens Feb 16 '17 at 08:45
  • The Fill property in the question has a get accessor. I defined it because of that! – rmojab63 Feb 16 '17 at 08:46
0

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>
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Hi, a relevant question: Do you agree with [this](http://stackoverflow.com/a/8802620/5615980)? thanks – rmojab63 Feb 16 '17 at 18:01
  • Not at all. Think about attached behaviors for instance. However, a dependency property with value inheritance should generally be declared as attached property. See the *Making a Custom Property Inheritable* section here: https://msdn.microsoft.com/en-us/library/ms753197(v=vs.110).aspx – Clemens Feb 16 '17 at 18:28
  • Indeed, that's an interesting concept, will try it to see if it can apply in my situation – Hisham Maudarbocus Feb 17 '17 at 05:21