#388 – Layout Containers Don’t Provide Scrolling Support

None of the WPF panel controls (containers) automatically provide scrollbars.  If their child controls do not fit in the available space, the child controls are clipped.

In the example below, we have a vertical StackPanel with a series of Label controls.  Notice that the first label doesn’t fit the width of the window and is clipped on the right.  The last label also does not fit and is clipped at the bottom of the window.

#387 – Set Background Color to See How Layout Works

It’s sometimes hard to understand how a container is laying out its children.  You can use the Background property of each control, or of the panels, to get a better idea of where things are.

Suppose that we have a GUI that includes a handful of controls and two nested panels.

    <StackPanel Orientation="Vertical">
        <Label Content="Bob's Your Uncle" HorizontalAlignment="Right"/>
        <StackPanel Orientation="Horizontal">
            <Label Content="Paul"/>
            <Button Content="Ringo" Margin="10"/>
            <TextBox Text="George" VerticalContentAlignment="Bottom"/>
        </StackPanel>
        <TextBox Text="Herman was here.."/>
    </StackPanel>

The GUI would look like this:

To better see how things are being layed out, we can set the Background to a different color for each element.

    <StackPanel Orientation="Vertical" Background="Pink">

        <Label Content="Bob's Your Uncle" HorizontalAlignment="Right" Background="Lavender"/>

        <StackPanel Orientation="Horizontal" Background="LightBlue">
            <Label Content="Paul" Background="Red"/>
            <Button Content="Ringo" Margin="10" Background="Green"/>
            <TextBox Text="George" VerticalContentAlignment="Bottom" Background="Blue"/>
        </StackPanel>

        <TextBox Text="Herman was here.." Background="Orange"/>
    </StackPanel>

#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)

#385 – You Can Nest Panel Controls

An application is typically composed of a top-level panel control that contains other panels, creating a hierarchy of panels that defines the layout of your GUI.

Here’s an example, with the top-level container being a three-row Grid that contains a StackPanel in the 1st row, a 2×2 Grid in the 2nd row, and another StackPanel in the 3rd row.

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right">
            <Label Content="Enter your name:"/>
            <TextBox Width="150"/>
        </StackPanel>

        <!-- 2x2 grid for main app content -->
        <Grid Grid.Row="1" >
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Content="Something here" Grid.Row="0" Grid.Column="0"/>
            <Label Content="Other stuff here" Grid.Row="1" Grid.Column="1"/>
        </Grid>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Content="OK" Margin="10,5"/>
            <Button Content="Cancel" Margin="10,5"/>
        </StackPanel>
    </Grid>

#384 – The Benefits of Flow-Based Layout

WPF provides several benefits by making flow-based layout a default.

Resolution Independence

This is not really a benefit of flow-based layout, but a benefit based on the fact that WPF specifies positions and sizes in device independent units, rather than in pixels.  If the current DPI setting matches the native DPI of the device, then the actual size of the GUI elements will be as requested.

Controls Automatically Resized or Repositioned

Flow-based layout containers automatically reposition/resize child controls as the container is resized.

Controls Size to Fit Their Content

In WPF, you typically allow a control to size to fit it’s content.  This removes the need for tweaking size and position of elements if the content changes.

    <StackPanel>
        <Button Content="Bob" HorizontalAlignment="Center"/>
        <Button Content="John Jacob Jingleheimer Smith" HorizontalAlignment="Center"/>
    </StackPanel>

Easier Localization

When controls size to fit their content, you don’t need to tweak layout for each localized language.

#383 – The Problems with Coordinate-Based Layout

WPF uses flow-based layout by default (e.g. StackPanel and Grid containers), with coordinate-based layout as an option (e.g. Canvas).

At first glance, coordinate-based layout seems easier.  You can position and size every control exactly how you want it to look.  Flow-based layout can be a little frustrating, as you try to get a container to organize its child controls the way that you want.

But coordinate-based layout has some limitations:

  • Controls look different, depending on the resolution of the display device (e.g. get smaller on higher DPI devices)
  • If you allow resizing a window or any area within a GUI (e.g. with a splitter) you have to do a lot of work to resize or reposition the controls as the container changes size
  • Controls don’t automatically resize or re-position to fit their content
  • Controls don’t automatically resize or re-position to accommodate non-English text

#382 – Persisting RichTextBox Contents as XAML

You can use the Save method of a TextRange object to save the contents of a RichTextBox control.

In the example below, I enter some formatted text into a RichTextBox and then click the Save button.

Here’s the code that does the save behavior.

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            TextRange allText = new TextRange(rtfMain.Document.ContentStart, rtfMain.Document.ContentEnd);

            FileStream stream = new FileStream(@"D:\AboutMyDog.xaml", FileMode.Create);

            allText.Save(stream, DataFormats.Xaml);

            if (stream != null)
                stream.Close();
        }

The resulting XAML file consists of an outer Section element that in turn contains multiple Paragraph elements.

    <Paragraph>
        <Run FontStyle="Italic">Kirby</Run>
        <Run> is a </Run>
        <Run FontWeight="Bold">Border Collie</Run>
        <Run>.</Run>
    </Paragraph>
    <Paragraph>
        <Run FontStyle="Italic">Jack</Run>
        <Run> is a </Run>
        <Run FontWeight="Bold">Jack Russell Terrier</Run>
        <Run>.</Run>
    </Paragraph>

#381 – Loading .rtf Files Into a RichTextBox

The RichTextBox control supports loading not only Text (.txt) files, but also Rich text (.rtf)  files.

You can load a file using the Load method of the RichTextBox’s Selection property.

        <RichTextBox x:Name="rtfMain" />
            rtfMain.Selection.Load(new FileStream(@"D:\Spade.rtf", FileMode.Open), DataFormats.Rtf);

#380 – The Frame Control Can Host Web Content

You can use the Frame control to host web content by setting its Source property to a valid URL.  Note that the control can also display standard web navigation controls.

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Frame Grid.Column="0" Source="http://en.wikipedia.org/wiki/Snoopy"
               NavigationUIVisibility="Visible"/>

        <Frame Grid.Column="1" Source="http://acepilots.com/wwi/ger_richthofen.html"
               NavigationUIVisibility="Visible"/>
    </Grid>

#379 – Using a Tooltip As a Magnifier

You can use a tooltip as a magnifier, so that when you hover over a control, it shows an enlarged view of the control.

We define a tooltip to contain a Rectangle that is exactly twice the size of the associated control (using data binding and a value converter).  We then fill the Rectangle with a VisualBrush and bind the Visual property of the VisualBrush to the element that is hosting the tooltip.

    <Window.Resources>
        <loc:DoubleIntConverter x:Key="doubleIntConverter"/>

        <ToolTip x:Key="reflectingTooltip" DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}"
                 Placement="Center">
            <Rectangle Width="{Binding ActualWidth, Converter={StaticResource doubleIntConverter}}"
                       Height="{Binding ActualHeight, Converter={StaticResource doubleIntConverter}}">
                <Rectangle.Fill>
                    <VisualBrush Visual="{Binding}"/>
                </Rectangle.Fill>
            </Rectangle>
        </ToolTip>
    </Window.Resources>

Using the tooltip:

        <Label Content="Can you read me now?" ToolTip="{StaticResource reflectingTooltip}"
               HorizontalAlignment="Center"/>

The implementation of the value converter is left as an exercise.