#1,191 – Custom Element with a Single Child Element

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 AddVisualChildAddLogicalChild 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:

1191-001

Advertisement

#1,182 – Using RenderSize Properties in Custom Elements

When writing rendering code for a custom element that derives from FrameworkElement, you can use the ActualWidth and ActualHeight properties to know how to render the element.  These properties indicate the desired final size of the element, after all layout calculations have been done.

    public class MyFrameworkElement : FrameworkElement
    {
        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));
        }
    }

If a custom control derives from UIElement, it won’t have access to the ActualWidth and ActualHeight properties, but can instead use RenderSize.Width and RenderSize.Height.  (You can also use these properties from within an element that derives from FrameworkElement, since FrameworkElement inhertis from UIElement).

    public class MyUIElement : UIElement
    {
        protected override void OnRender(DrawingContext dc)
        {
            dc.DrawLine(new Pen(Brushes.Blue, 2.0),
                new Point(0.0, 0.0),
                new Point(RenderSize.Width, RenderSize.Height));
            dc.DrawLine(new Pen(Brushes.Green, 2.0),
                new Point(RenderSize.Width, 0.0),
                new Point(0.0, RenderSize.Height));
        }
    }

#1,181 – Custom Element Indicates Desired Size in MeasureOverride

By default, a custom element deriving from FrameworkElement has no desired size.  Most elements should override MeasureOverride as a way of indicating what their desired size should be.  This would typically be based on the control’s content, but also might be some minimum size.

Below, we have a custom element that indicates that it has a minimum size of 20 x 20.

    public class MyFrameworkElement : FrameworkElement
    {
        protected override Size MeasureOverride(Size availableSize)
        {
            return new Size(20.0, 20.0);
        }

        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 still specify a desired size when consuming the element, overriding the default size.

    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <loc:MyFrameworkElement HorizontalAlignment="Center"
                                VerticalAlignment="Top"/>
        <loc:MyFrameworkElement Grid.Row="1" Margin="20,10"/>
    </Grid>

1181-001

#1,180 – By Default, FrameworkElement Doesn’t Have Desired Size

If you create a custom control that derives from FrameworkElement, the control won’t by default have any particular desired size.  You can see this by setting alignment properties on a simple custom control without also setting explicit Width and Height.

    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <loc:MyFrameworkElement HorizontalAlignment="Center"
                                VerticalAlignment="Top"/>
        <loc:MyFrameworkElement Grid.Row="1" Margin="20,10"/>
    </Grid>

You can see in the designer that the first instance of MyFrameworkElement is not visible, because it has no size.

1180-001

A custom control will typically define its desired size based on its content.  In the example above, the custom control hasn’t specified any desired size, size the control has a size of (0,0) and disappears.

 

#1,178 – Custom Element Based on FrameworkElement

You can create custom user interface elements that derive from FrameworkElement.  Below is a simple example that renders itself as a graphical “X”.  We override the OnRender method (inherited from UIElement) and use the DrawingContext object to create visual content.

    public class MyElement : FrameworkElement
    {
        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));
        }
    }

Using the new element looks like this:

    <Grid>
        <loc:MyElement/>
    </Grid>

Here’s what it looks like at run-time:
1178-001

If we host the Grid as a top-level element in a Window, our element is sized to fit the Grid, which sizes to fit the Window.  Because we render the element using ActualWidth and ActualHeight, it changes size as we resize the window.

1178-002

 

#1,177 – UIElement vs. FrameworkElement vs. Control

If you want to implement a custom element in WPF, either to display something in a user interface or to get input from a user, you’ll typically derive your custom element from one of the following classes: UIElementFrameworkElement, or Control.

The inheritance chain for these three classes is:

1177-001

When creating a custom control, you’ll want to choose as a base class whichever one of these classes has only the features that you want.  The core functionality for these three classes is:

  • UIElement  (Layout + Input + Focus + Events)
    • Layout behavior (parent/child relationship, measure/arrange passes)
    • Responding to user input (input events, command bindings)
    • Managing focus
    • Raising and responding to routed events
  • FrameworkElement adds
    • Alignment-related and Margin properties
    • Animation support
    • Data binding
    • Data templates
    • Styles
    • Defaults Focusable to false
  • Control adds
    • Control templates
    • Background, Foreground
    • Font-related properties
    • Border-related properties
    • Defaults Focusable to true

#616 – FrameworkElement.Initialized and Loaded Event Order

All elements that inherit (directly or indirectly) from FrameworkElement will fire both Initialized and Loaded events when the control is being loaded.  For example, both an Initialized and Loaded event will for each control in your application when the application is starting up.

The Initialized events for all controls fire starting with the elements at the bottom of the logical tree.  When handling the Initialize event for a particular element, you can assume that any elements below the element in question have already been initialized.

The Loaded event, on the other hand, fires from the top of the logical tree and then continues down the tree.  The Loaded events begin firing only after the layout system has figured out the position and size of every element in the tree.  So in a handler for a Loaded event, you can assume that all elements in the logical tree are fully configured.

#615 – Standard Object Lifetime Events for FrameworkElement Objects

All objects in WPF that derive from FrameworkElement or FrameworkContentElement have three common events that fire as part of the element’s lifecycle.  Because these events are inherited from FrameworkElement and FrameworkContentElement, they are available for all controls, layout panels, Window objects and Page objects.

The three main object lifetime events are:

  • Initialized – Fires when an object has been created and all of its properties have been set.  Properties relating to layout have not yet been set
  • Loaded – Fires after the layout system has calculated all properties related to layout.  Data binding has occurred at this point, so controls should have their final property values.
  • Unloaded – Fires when element is removed from logical tree.  Will not fire if application is shutting down.

#426 – Layout Panels Can Also Have Margins

You typically set margins on elements contained within a layout panel to create space between different elements and to create space between an element and the edge of the containing panel.

Here’s a Window containing a Grid (beige background), which contains some controls.  No margins have been set.

We can then specify a Margin value for the child elements within the Grid.  (Here, we set all margins to 5).  This creates space between each control and the cell in the Grid that it’s contained in.

We can also specify a Margin value for the Grid itself.  Doing so will create space between the Grid and the edges of its container, the Window.  In the example below, we specify a Margin of 10 for the Grid.  The background of the Window is set to blue, so that we can see the edges of the Grid.

 

 

#386 – Layout = Panels + FrameworkElements + Alignment/Margins/Padding

Layout in WPF is the process by which the location and size of all user interface elements is determined.

A user interface is composed of an outer Window or Page which contains a hierarchy of user interface elements.  The hierarchy can contain individual user interface elements or Panels, which in turn contain a collection of child FrameworkElements.

Panel is an abstract class that serves as a parent for specific layout panels, including Canvas, DockPanel, Grid, StackPanel and WrapPanel.

A panel will contain a collection of child FrameworkElement instances.  These can be individual controls that derive from FrameworkElement, directly or indirectly.  Because Panel is itself a child of the FrameworkElement class, a panel can contain other panels.

FrameworkElement child elements are position within a parent using properties related to alignment, margins and paddingThese properties include:

  • HorizontalAlignment, VerticalAlignment  and Margin  (from FrameworkElement)
  • HorizontalContentAlignment, VerticalContentAlignment and Padding  (from Control)