Search code examples
c#wpfcustom-controlsrenderinguielement

How do I make a custom UIElement-derived class that contains (and displays) other UIElements as children?


Let's say I want to make a class that inherits directly from UIElement and is able to contain one or more [externally added] UIElements as children - like Panels and other container controls. It's obviously easy to have the class house a collection of UIElements in some form or other, but how do I get them to be displayed / rendered along with my class?

I assume they must be added to the visual tree as children of my own UIElement in some way (or, possibly, render them manually à la going via VisualTreeHelper.GetDrawing and do it using OnRender's DrawingContext? But that seems clumsy).

I do not want to know that I can - or should - inherit from more ready-made controls, like FrameworkElement, Panel, ContentControl etc (if anything, I want to know how they are implementing the displaying / rendering of externally added child elements, where applicable).

I have my reasons for wanting to be as high up in the hierarchy as possible, so please don't give me any lectures on why it is a good thing to be XAML / WPF Framework 'compliant' etc.


Solution

  • The following class provides the absolute minimum in terms of layout and rendering of child elements:

    public class UIElementContainer : UIElement
    {
        private readonly UIElementCollection children;
    
        public UIElementContainer()
        {
            children = new UIElementCollection(this, null);
        }
    
        public void AddChild(UIElement element)
        {
            children.Add(element);
        }
    
        public void RemoveChild(UIElement element)
        {
            children.Remove(element);
        }
    
        protected override int VisualChildrenCount
        {
            get { return children.Count; }
        }
    
        protected override Visual GetVisualChild(int index)
        {
            return children[index];
        }
    
        protected override Size MeasureCore(Size availableSize)
        {
            foreach (UIElement element in children)
            {
                element.Measure(availableSize);
            }
    
            return new Size();
        }
    
        protected override void ArrangeCore(Rect finalRect)
        {
            foreach (UIElement element in children)
            {
                element.Arrange(finalRect);
            }
        }
    }
    

    It is not required to have a UIElementCollection. An alternative implementation could look like this:

    public class UIElementContainer : UIElement
    {
        private readonly List<UIElement> children = new List<UIElement>();
    
        public void AddChild(UIElement element)
        {
            children.Add(element);
            AddVisualChild(element);
        }
    
        public void RemoveChild(UIElement element)
        {
            if (children.Remove(element))
            {
                RemoveVisualChild(element);
            }
        }
    
        // plus the four overrides
    }