Below is an example of a simple custom element that derives from FrameworkElement and includes a single child element (UIElement), set using the Child dependency property.
Here’s the full code for the custom element. We do the following:
- Override both Measure and Layout
- Override VisualChildrenCount and GetVisualChild to respond indicating that we have a single child
- Override Render to render the graphical portion of the control
- Use AddVisualChild / AddLogicalChild to indicate that the child element belongs to the parent element
public class MyElement : FrameworkElement
{
private static FrameworkPropertyMetadata childMetadata =
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsParentArrange,
new PropertyChangedCallback(OnChildChanged));
public static void OnChildChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyElement thisElement = obj as MyElement;
if (thisElement == null)
throw new Exception("Child property must be attached to MyElement");
// Remove old one
Visual oldChild = e.OldValue as Visual;
if (oldChild != null)
{
thisElement.RemoveVisualChild(oldChild);
thisElement.RemoveLogicalChild(oldChild);
}
// Attach new one
Visual newChild = e.NewValue as Visual;
if (newChild != null)
{
thisElement.AddVisualChild(newChild);
thisElement.AddLogicalChild(newChild);
}
}
public static readonly DependencyProperty ChildProperty =
DependencyProperty.RegisterAttached("Child", typeof(UIElement),
typeof(MyElement), childMetadata);
public static void SetChild(DependencyObject depObj, UIElement value)
{
depObj.SetValue(ChildProperty, value);
}
protected override int VisualChildrenCount
{
get
{
UIElement childElement = (UIElement)GetValue(ChildProperty);
return childElement != null ? 1 : 0;
}
}
protected override Visual GetVisualChild(int index)
{
// (ignoring index)
return (UIElement)GetValue(ChildProperty);
}
protected override Size MeasureOverride(Size availableSize)
{
UIElement childElement = (UIElement)GetValue(ChildProperty);
if (childElement != null)
childElement.Measure(availableSize);
// "X" and child both use all of the available space
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
UIElement childElement = (UIElement)GetValue(ChildProperty);
if (childElement != null)
childElement.Arrange(new Rect(new Point(0.0, 0.0), finalSize));
return finalSize;
}
// Render a big "X"
protected override void OnRender(DrawingContext dc)
{
dc.DrawLine(new Pen(Brushes.Blue, 2.0),
new Point(0.0, 0.0),
new Point(ActualWidth, ActualHeight));
dc.DrawLine(new Pen(Brushes.Green, 2.0),
new Point(ActualWidth, 0.0),
new Point(0.0, ActualHeight));
}
}
We can now use this element as follows:
<Grid>
<loc:MyElement>
<loc:MyElement.Child>
<Label Content="I'm the child" HorizontalAlignment="Center"/>
</loc:MyElement.Child>
</loc:MyElement>
</Grid>
When rendered, it looks like this:
