#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,179 – Alignment and Margin Properties for a Custom FrameworkElement

When you create a custom control that derives from FrameworkElement, your control has access to all standard FrameworkElement behavior.  This includes the use of the Margin property, as well as the HorizontalAlignment and VerticalAlignment properties.

Below, we use margin and alignment in positioning a custom framework element within a Grid.

    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        
        <loc:MyFrameworkElement HorizontalAlignment="Center"
                                VerticalAlignment="Top"
                                Height="40" Width="100"/>
        <loc:MyFrameworkElement Grid.Row="1" Margin="20,10"/>
    </Grid>

(Showing grid lines for clarity).

1179-001

#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

#1,163 – Make an Image Clickable with a Control Template

You can allow clicking on an Image by using it as the control template for a Button.

In the example below, we define a button that consists of an image and a border that lights up when you hover over the image.

	<StackPanel>
		<Button Click="Button_Click"
                Margin="10"
			    HorizontalAlignment="Center"
			    ToolTip="Click on Fred">
			<Button.Template>
				<ControlTemplate>
					<Border x:Name="theBorder" 
                            BorderBrush="Transparent"
                            BorderThickness="2">
						<Image Source="Astaire.png" Height="45"/>
					</Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="BorderBrush" TargetName="theBorder" 
                                    Value="LightSkyBlue"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
				</ControlTemplate>
			</Button.Template>
		</Button>
	</StackPanel>

Below, we can see the clickable Image in action.
1163-001

1163-002

1163-003

#1,145 – Using RenderSize in Custom Shape

When drawing a geometry in a custom Shape element, you could draw using hard-coded coordinates.  It’s more common, however, to use the RenderSize property of the UIElement to render the object so that the geometry scales based on the size of the control.

Below, we create a custom shape that draws a diagonal line from the upper left corner of the control to the lower right.

    public class MyShape : Shape
    {
        protected override Geometry DefiningGeometry
        {
            get
            {
                double maxWidth = RenderSize.Width;
                double maxHeight = RenderSize.Height;

                StreamGeometry geom = new StreamGeometry();
                using (StreamGeometryContext ctx = geom.Open())
                {
                    ctx.BeginFigure(
                        new Point(0.0, 0.0),
                        false,
                        false);
                    ctx.LineTo(
                        new Point(maxWidth, maxHeight),
                        true,
                        false);
                }

                return geom;
            }
        }
    }

We can use the shape in XAML as follows:

    <StackPanel>
        <loc:MyShape Stroke="Black" StrokeThickness="1"
                     Height="50" Width="50"
                     HorizontalAlignment="Center"/>
    </StackPanel>

Now when we change the size of the underlying control, the geometry adjusts as well.

1145-001

1145-002

1145-003

#1,144 – Geometry in Custom Shape Doesn’t Automatically Scale

If you define a custom Shape by creating some Geometry, the resulting geometry will not automatically scale when shape’s size is changed.

Suppose that we have the following custom shape.

    public class MyShape : Shape
    {
        protected override Geometry DefiningGeometry
        {
            get
            {
                StreamGeometry geom = new StreamGeometry();
                using (StreamGeometryContext ctx = geom.Open())
                {
                    ctx.BeginFigure(
                        new Point(0.0, 0.0),
                        false,
                        false);
                    ctx.LineTo(
                        new Point(50.0, 50.0),
                        true,
                        false);
                }

                return geom;
            }
        }
    }

Placing this control in a StackPanel, it’s size is just large enough to accommodate the geometry.

1144-001

If we explicitly make the shape larger, the underlying geometry stays the same size.

1144-002

Follow

Get every new post delivered to your Inbox.

Join 374 other followers