Get Notified when child is added to a Custom Panel via XAML

As I was creating the next control for my WPFSpark project – the FluidWrapPanel, a thought occurred to me. How will the FluidWrapPanel be notified when controls are added as its children via XAML?

As of now, FluidWrapPanel had an API called AddChild which would take a UIElement as a parameter. The reason behind this was that the FluidWrapPanel needs to perform some calculations before adding it to the InternalChildren. The UIElementCollection does not have a CollectionChanged to which we can subscribe to. Search over the internet yielded partial solutions to the problem. This MSDN link provided the initial starting point for my solution.

First, I defined an interface INotifiableParent which must be implemented by the Custom Panel requiring UIElementCollection change notification.

namespace WPFSpark
{
    public interface INotifiableParent
    {
        int AddChild(UIElement child);
        void RemoveChild(UIElement child);
    }
}

Next, I derived a class NotifiableUIElementCollection from the UIElementCollection class and provided a constructor, overriden methods (Add and Remove) and private member variable of type INotifiableParent.

namespace WPFSpark
{
    public class NotifiableUIElementCollection : UIElementCollection
    {
        private INotifiableParent parent;

        public NotifiableUIElementCollection(UIElement visualParent, FrameworkElement logicalParent)
            : base(visualParent, logicalParent)
        {
            parent = (INotifiableParent)logicalParent;
        }

        public override int Add(System.Windows.UIElement element)
        {
            if (parent != null)
                return parent.AddChild(element);

            return -1;
        }

        public override void Remove(UIElement element)
        {
            if (parent != null)
                parent.RemoveChild(element);
        }
    }
}

The NotifiableUIElementCollection class does not store the children within itself. Instead it delegates the work to the INotifiableParent member.

Now, the following must be done in the Custom Panel code –

  • Add a member variable of type NotifiableUIElementCollection (this member must be initialized in the constructor!)
  • Add A read-only property of type NotifiableUIElementCollection which returns the private member.
  • The ContentPropertyAttribute must be added to the CustomPanel class with the read-only Property name as the argument.
  • Implement the INotifiableParent interface.

The ContentPropertyAttribute indicates which property of a type is the XAML content property.

Important: In the implementation of the AddChild method, you must add the child to the Children property of the CustomPanel otherwise it will not be rendered on the panel.

Here in the sample panel below, I am expecting that TextBoxes will be added to it via XAML and I am assigning a value to the Text property of the TextBlock.

namespace CustomPanelSample
{
    [ContentProperty("NotifiableChildren")]
    class CustomPanel : Canvas, INotifiableParent
    {
        int count = 1;

        private NotifiableUIElementCollection notifiableChildren;

        public NotifiableUIElementCollection NotifiableChildren
        {
            get
            {
                return notifiableChildren;
            }
        }

        public CustomPanel()
        {
	    // Initialize the NotifiableUIElementCollection
            notifiableChildren = new NotifiableUIElementCollection(this, this);
        }

        #region INotifiableParent Members

        public int AddChild(System.Windows.UIElement child)
        {
            // Add your custom code here
            TextBlock tb = child as TextBlock;

            if (tb != null)
            {
                tb.Text = count++.ToString();
            }

	    // Add the child to the InternalChildren
            return this.Children.Add(child);
        }

        public void RemoveChild(System.Windows.UIElement child)
        {
            this.Children.Remove(child);
        }

        #endregion
    }
}

Here is how the CustomPanel will be used

<Window x:Class="CustomPanelSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="350"
        Width="525"
        xmlns:my="clr-namespace:CustomPanelSample">
    <Grid>
        <my:CustomPanel HorizontalAlignment="Stretch"
                        Margin="15"
                        x:Name="customPanel1"
                        VerticalAlignment="Stretch">
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="10"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="50"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="90"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="130"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="170"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="210"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
            <TextBlock Width="100"
                       Height="30"
                       Canvas.Left="200"
                       Canvas.Top="250"
                       Foreground="Black"
                       Background="Orchid"
                       FontSize="20"
                       TextAlignment="Center"></TextBlock>
        </my:CustomPanel>
    </Grid>
</Window>

Upon executing this, the following output is shown.

In the NotifiableUIElementCollection, you can also implement the remaining overridable methods available in the UIElementCollection. In that case you must also expand the INotifiableParent interface to accommodate those methods.

Hope this solution comes handy!

Happy coding! 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s