#1,169 – Custom Panel, part I (Measure and Arrange)

You can create a class that derives from Panel in order to create a panel control with custom behavior.  You typically override the following two methods in your custom panel:

  • MeasureOverride – You call Measure method on each child element to determine how much space they need, then return the total space needed
  • ArrangeOverride – You call Arrange method for each child element to position them

Below is a very simple example–a panel that does nothing in either its MeasureOverride or ArrangeOverride methods.  We’ll build on this class in future posts.

    public class MyPanel : Panel
    {
        protected override Size MeasureOverride(Size availableSize)
        {
            Size measuredSize = base.MeasureOverride(availableSize);
            return measuredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            Size arrangedSize = base.ArrangeOverride(finalSize);
            return arrangedSize;
        }
    }

If we add children to this panel, they will not be displayed. Below is debug output at run-time. MeasureOverride returns (0,0) because we haven’t calculated any sizes.
1169-001

#1,168 – Layout in Action, part V

When a panel is laying out its children, it will respect (make use of) an explicit size specified for a child control.

In the example below, the MyLabel control specifies a width of 100.  This makes it wider than it would normally be to fit its content.

    <StackPanel Margin="5" Background="Honeydew">
        <loc:MyLabel Content="Billy" HorizontalAlignment="Center" Width="100"
                     Background="Thistle" />
    </StackPanel>

1168-001
At run-time, the measure/arrange process is:

  • StackPanel reads the label’s desired width of 100, using this value as the “constrained” width (target width)
  • StackPanel calls Measure on the label, with constrained width of 100 and height of Infinity
  • Label returns a size from MeasureOverride with width and height equal to what’s required for its content (31.4 wide x 26 high)
  • StackPanel calls Arrange on MyLabel, passing in a size that includes the explicit width (100 x 26)

1168-002

#1,167 – Layout in Action, part IV

The previous example showed how layout works when a child element needs less space than what it is available.  Below, we walk through the case when the child needs more space than what’s available.

Assume that we have a Label with more content than will fit into a containing StackPanel (which sizes to fit a containing window).  At run-time, the measure/arrange process is:

  • StackPanel calls Measure on the label, passing in its own width as a constraint width and infinity as a constraint height.
  • Label can’t fit into specified constraint width, so it returns a size from MeasureOverride with width equal to constraint width (it takes what it can get) and height to fit its desired height
  • StackPanel gives the label this requested height and width
  • StackPanel calls Arrange on MyLabel, which uses specified size to render label

1167-001

1167-002

#1,166 – Layout in Action, part III

Layout in WPF dictates how layout panels (containers) arrange their child elements.  In the example below, we build on an earlier example to show how the measure and arrange phases work when host a Label in control in a StackPanel and then stretch the label.

Suppose the we have the XAML shown below. Note that the HorizontalAlignment of the Label is set to Stretch.

    <StackPanel Margin="5" Background="Honeydew">
        <loc:MyLabel Content="Billy" Background="Thistle"
                     HorizontalAlignment="Stretch" />
    </StackPanel>

At run-time this looks like:
1166-001
At run-time, the measure/arrange process is:

  • StackPanel calls Measure on MyLabel
  • Base Label class calculates how much space it needs (31.4 x 26) and MyLabel returns this value from MeasureOverride
  • StackPanel decides to give the label the requested height, but stretches out its width
  • StackPanel calls Arrange on MyLabel, passing in size of 164 x 26
  • MyLabel.ArrangeOverride receives this value, passing on to base Label class
  • Base Label class uses this size to know how to render label

Here’s some run-time output showing these values:

1166-002

#1,165 – Layout in Action, part II

Layout in WPF dictates how layout panels (containers) arrange their child elements.  Below is a simple example of a StackPanel containing a custom Label that we’ve instrumented so that we can see when its Measure and Arrange methods are called.  (MyPanel derives from Label).

Suppose we use the label as follows:

    <StackPanel Margin="5" Background="Honeydew">
        <loc:MyLabel Content="Billy" Background="Thistle"
                     HorizontalAlignment="Center" />
    </StackPanel>

The label looks like this at run-time:
1165-001

 

At run-time, the measure/arrange process is:

  • StackPanel calls Measure on MyLabel
  • Base Label class calculates how much space it needs (31.4 x 26) and MyLabel returns this value from MeasureOverride
  • StackPanel decides to give the label the space that it needs/wants
  • StackPanel calls Arrange on MyLabel, passing in size of 31.4 x 26
  • MyLabel.ArrangeOverride receives this value, passing on to base Label class
  • Base Label class uses this size to know how to render label

Here’s some run-time output showing these values:

1165-002

#1,162 – Layout in Action, part I

Layout in WPF dictates how layout panels (containers) arrange their child elements.  Layout consists of two phases:

  • Measure – Container asks each child what its desired size is
    • Container calls Measure on each child element
    • In MeasureOverride, child element determines how much size it wants (typically by calling Measure on each of its own child elements)
    • Child element returns its desired size, to fit its child elements
  • Arrange – Container figures out how to arrange its children and decides on final position and size of each child
    • Container calls Arrange on each child element, passing in a size
    • In ArrangeOverride, child element is told how big it should be
    • Child element in turn tells each of its visual children how big they are going to be, by calling their Arrange methods
    • Child element returns final total arranged size to container

#1,065 – ViewBox Child Must Have Explicit Size

Whatever content you set as the content wrapped by a ViewBox, that content needs to be able to determine its own size.  The ViewBox needs to know what size to make the content at a scale of 1.0.

If the ViewBox wraps a panel containing elements that can size based on their own content, like buttons, everything works fine.

1065

However, if the ViewBox wraps a Canvas, you need to give the Canvas an explicit size so that the ViewBox knows what size to start with.  If you don’t do this, the ViewBox won’t be able to scale the content within the Canvas.

1065-001

#1,064 – Limiting ViewBox to Scale in Just One Direction

When you use a ViewBox to scale some content, by default it scales the content either larger or smaller than the default size of the content.

You can change this behavior by setting the StretchDirection of the ViewBox to one of the following values:

  • Both (the default) – allow scaling both up and down, relative to the default size of the content
  • UpOnly – Only allow scaling larger than default size of content
  • DownOnly – Only allow scaling smaller than default size of content

If you make the ViewBox a size at which scaling of the content is not allowed, the content will be clipped or whitespace will be added.

        <Viewbox StretchDirection="UpOnly">
            <Canvas Background="Bisque" Width="200" Height="100">
                <Line X1="5" Y1="5" X2="195" Y2="95"
                        Stroke="Black"/>
                <Label Canvas.Left="80" Canvas.Top="5" Content="Howdy"/>
                <Ellipse Height="30" Width="50" Stroke="Blue" StrokeThickness="2"
                            Canvas.Left="140" Canvas.Top="5"/>
            </Canvas>
        </Viewbox>

1064-001

#1,063 – ViewBox Stretching Options

By default, content scaled by using a ViewBox will preserve its aspect ratio as it is being scaled.  Content is scaled until its size fills its container in one dimension.  White borders are added in the other dimension.

This default behavior corresponds to setting the Stretch property of the ViewBox to Uniform.  We can see this behavior below as we scale a Canvas.

1063-001

1063-002

If we set Stretch to Fill, the content always fills the available area and the aspect ratio is not preserved.  That is, content is stretched more in one direction than in another.

1063-003

Setting the Stretch property to UniformToFill preserves the aspect ratio, but content stretches until it fills the container in both dimensions.  If the aspect ratio of the container is different than that of the content, content is clipped.

1063-004

Finally, setting Stretch to None disables all scaling.

1063-005

 

#1,062 – Scaling a Canvas Using a ViewBox

ViewBox is typically used to scale a panel containing other elements.  One common use of a ViewBox is to scale the contents of a Canvas panel.

We might include several elements within a Canvas that has an explicit size.

1062-001

If we re-size the window, however, the canvas stays the same size.

1062-002

We could have had the Canvas stretch to fill the remaining area, but its elements would still be the same size.

We can get the elements within the Canvas to scale by wrapping the Canvas in a ViewBox.

    <DockPanel>
        <Label DockPanel.Dock="Top" Background="LightGray"
               Content="Stuff at top of window here"
               VerticalAlignment="Top"/>
        <Label DockPanel.Dock="Bottom" Background="AliceBlue"
               Content="Bottom stuff down here"
               VerticalAlignment="Bottom"/>
        <Viewbox>
            <Canvas Background="Bisque" Width="200" Height="100">
                <Line X1="5" Y1="5" X2="195" Y2="95"
                        Stroke="Black"/>
                <Label Canvas.Left="80" Canvas.Top="5" Content="Howdy"/>
                <Ellipse Height="30" Width="50" Stroke="Blue" StrokeThickness="2"
                            Canvas.Left="140" Canvas.Top="5"/>
            </Canvas>
        </Viewbox>
    </DockPanel>

Now when we resize the window, everything within the Canvas is scaled.
1062-003