Chapter 3: Controls and XAML

 

Introduction
Chapter 1: The "Longhorn" Application Model
Chapter 2: Building a "Longhorn" Application

Chapter 3: Controls and XAML

Brent Rector
Wise Owl Consulting

December 2003

Contents

XAML Elements
XAML Panels
Controls
Resources and Styles
Graphics and Animations
Document Services
Summary

As you've seen in Chapter 2, Longhorn platform applications typically consist of an Application object and a set of user interface pages that you write in a declarative markup language called XAML.

The Application object is a singleton and persists throughout the lifetime of the application. It allows your application logic to handle top-level events and share code and state among pages. The Application object also determines whether the application is a single window application or a navigation application.

You typically write each user interface page using a dialect of XML named Extensible Application Markup Language (XAML). Each page consists of XAML elements, text nodes, and other components organized in a hierarchical tree. The hierarchical relationship of these components determines how the page renders and behaves.

You can also consider a XAML page to be a description of an object model. When the runtime creates the page, it instantiates each of the elements and nodes described in the XAML document and creates an equivalent object model in memory. You can manipulate this object model programmatically—for example, you can add and remove elements and nodes to cause the page to render and behave differently.

Fundamentally, a XAML page describes the classes that the runtime should create, the property values and event handlers for the instances of the classes, and an object model hierarchy—that is, which instance is the parent of another instance.

All XAML documents are well-formed XML documents that use a defined set of element names. Therefore, all rules regarding the formation of well-formed XML documents apply equally to XAML documents. For example, the document must contain a single root element; all element names are case-sensitive; an element definition cannot overlap another element definition but must entirely contain it, and so on. If you're not familiar with XML syntax, now is an excellent time to learn it.

XAML Elements

Each XAML page contains one or more elements that control the layout and behavior of the page. You arrange these elements hierarchically in a tree. Every element has only one parent. Elements can generally have any number of child elements. However, some element types—for example, Scrollbar—have no children; and other element types—for example, Border—can have a single child element.

Each element name corresponds to the name of a managed class. Adding an element to a XAML document causes the runtime to create an instance of the corresponding class. For example, the following markup represents a root DockPanel element that has a single child Table element. The Table element contains three child Row elements. Each Row element contains three children, and a few of them have child text nodes.

<Border xmlns="https://schemas.microsoft.com/2003/xaml" 
        Background="BlanchedAlmond">
  <DockPanel>
    <Table> 
      <Body>
        <Row>
          <Cell><Button/></Cell>
          <Cell><Text>Item</Text></Cell>
          <Cell><Text>Price</Text></Cell>
        </Row>
        <Row>
          <Cell><CheckBox Checked="true"/></Cell>
          <Cell><TextBox Height="50">Nissan 350Z</TextBox></Cell>
          <Cell><TextBox Height="50">29.95</TextBox></Cell>
        </Row>
        <Row>
          <Cell><CheckBox/></Cell>
          <Cell><TextBox Height="50">Porsche Boxster</TextBox></Cell>
          <Cell><TextBox Height="50">9.95</TextBox></Cell>
        </Row>
      </Body>
    </Table>

  </DockPanel>
</Border>

This XAML document creates an object hierarchy as shown in Figure 3-1 and the display shown in Figure 3-2.

Figure 3-1. An example XAML page object model

Click here for larger image

Figure 3-2. The display from the previous XAML (click for larger image)

You can access much of the functionality of such objects using only markup. Using only markup, you can do any of the following:

  • Describe a hierarchical set of objects that the runtime will instantiate
  • Set object properties to values known statically
  • Set object properties to values retrieved from a data source
  • Cause changed property values to be stored back into the data source
  • Repeatedly change a property's value over time
  • Bind an event handler to an object's event

However, although you can create some amazing user interfaces using only markup, you can also access an element's functionality programmatically using the XAML object model. The object model allows you to manipulate every aspect of the elements on a page. It actually provides additional capabilities that are not accessible through XAML.

Every XAML element derives from System.Windows.UIElement or System.Windows.ContentElement, and therefore all elements possess a number of common features. Elements can be grouped in the following four basic categories:

  • Controls derive from System.Windows.Control and handle user interaction.
  • Panels are specialized controls that derive from System.Windows.Panel and handle page layout and act as containers for elements.
  • Text formatting elements derive from System.Windows.TextElement and handle text formatting and document structure.
  • Shapes handle vector graphic shapes.

XAML Panels

A XAML page typically begins with a panel element. The panel is a container for a page's content and controls the positioning and rendering of that content. In fact, when you display anything using XAML, a panel is always involved, although sometimes it is implicit rather than one you describe explicitly. A panel can contain other panels, allowing you to partition the display surface into regions, each controlled by its panel.

There are six built-in Panel classes in the Longhorn platform:

  • A Canvas positions each child element explicitly using coordinates relative to the Canvas area.
  • A DockPanel puts its children in the top, bottom, left, right, or center of the panel. When you assign multiple children to the same area, a DockPanel arranges them either horizontally or vertically within that area.
  • A FlowPanel arranges its child elements according to its line-breaking and alignment properties. When content exceeds the length of a single line, the panel will break lines, wrap lines, and align content appropriately.
  • A TextPanel renders multiple lines of text in multiple text formats. You will typically use it only when you need complex text layout. For most cases, you'll use the lightweight Text element for basic text support.
  • A GridPanel is a lightweight element that arranges its child elements in rows and columns forming a grid. It is useful for creating simple tables but has limited features. You would use the Table control for complex table layout.
  • A FixedPanel positions its child elements on a fixed layout page. Elements on fixed layout pages always have the same positioning and pagination regardless of device resolution or window size.

Generally, these panels will provide sufficient functionality for most developers. However, you can also create your own panel classes that position and displays content in a specialized manner.

Canvas

The Canvas panel provides considerable flexibility regarding positioning and arranging elements on the screen. It allows you to specify the location for each child element and, when elements overlap, you can specify the order in which the canvas draws the overlapping elements by changing the order the elements appear in markup.

The following markup produces three overlapping graphics, as you see in Figure 3-1: a green rectangle with an orange border, a translucent yellow ellipse with a blue border, and some text centered in the rectangle. (The thought of never again writing a WM_PAINT handler to draw things like this brings tears to my eyes . . . tears of joy I hasten to add!) The framework draws the shapes in the order presented, so the text appears over the rectangle.

<Canvas xmlns="https://schemas.microsoft.com/2003/xaml" >
  <Rectangle 
    Fill="#33CC66"
    Width="2in"       Height="1in"
    Canvas.Top="25"          Canvas.Left="50"
    StrokeThickness="6px" Stroke="Orange" />

  <Ellipse         
    Fill="yellow"
    CenterX="1.5in"    CenterY="1.1in"
    RadiusX=".5in"     RadiusY="1in"
    StrokeThickness="4px"  Stroke="Blue" />

   <Text
    Canvas.Top="50" Canvas.Left="60" Foreground="#000000"
    FontWeight="Bold" FontFamily="Arial"
    FontStyle="Normal" FontSize="25">Hello Shapes!</Text>

</Canvas>

Figure 3-3. An example using the Canvas panel

DockPanel

The DockPanel panel arranges child elements horizontally or vertically, relative to each other. The DockPanel class examines the Dock property of each child element to determine how to align the element along the edges of the panel. You can set the Dock property to one of five values: Top, Bottom, Left, Right, or Fill.

For example, a panel aligns the first child element with its Dock property equal to Top against the top edge of the panel. The panel then aligns the next child element with its Dock property equal to Top just below the prior element. The panel similarly aligns child elements with their Dock property set to Bottom, Left, or Right. Setting the Dock property of the last child element to Fill causes it to occupy all remaining space in the DockPanel. Never follow a Dock="Fill" element with other elements because the subsequent elements will not be visible. The default value of the Dock property is Left, so when you do not set the Dock property for an element, it stacks horizontally to the left.

The Dock property is an attached property—it's defined by the DockPanel class, but you set it on a child element like this:

<child DockPanel.Dock="Top"/>

Or in code:

DockPanel.SetDock(child, Dock.Top)

The following markup uses a DockPanel and five Canvas panels to create a commonly seen user interface. The DockPanel aligns the first two canvases against the top of the DockPanel. It aligns the third canvas against the bottom edge of the DockPanel, the fourth against the left edge, and the fifth canvas fills the remaining space. You might put a menu in the top panel and a toolbar in the panel just below the menu. This decision leaves the left panel for a tree view, the bottom panel for a status bar, and the remaining panel for the detailed selected item view, as you can see in Figure 3-4.

<Border xmlns="https://schemas.microsoft.com/2003/xaml" 
Background="White">
  <DockPanel>
          <Border Width="500" DockPanel.Dock="Top" 
BorderThickness="2,2,2,2" BorderBrush="Black" Background="#87ceeb" >
              <Text>Dock = "Top"</Text>
          </Border>
          <Border Width="500" DockPanel.Dock="Top" 
BorderThickness="2,2,2,2" BorderBrush="Black" Background="#87ceeb" >
            <Text>Dock = "Top"</Text>
          </Border>
          <Border Width="500" DockPanel.Dock="Bottom" 
BorderThickness="2,2,2,2"
                  BorderBrush="Black" Background="#ffff99" >
            <Text>Dock = "Bottom"</Text>
          </Border>
          <Border Width="200" DockPanel.Dock="Left" 
BorderThickness="2,2,2,2" BorderBrush="Black" Background="#98fb98" >
            <Text>Dock = "Left"</Text>
          </Border>
          <Border Width="300" DockPanel.Dock="Fill" 
BorderThickness="2,2,2,2" BorderBrush="Black" Background="White" >
            <Text>Dock = "Fill"</Text>
          </Border>
  </DockPanel>
</Border>

Figure 3-4. An example using the DockPanel panel

FlowPanel

The FlowPanel panel provides a number of automatic layout features and allows for complex presentations of text and graphics. You define the size of the panel using its Width and Height properties. The panel then displays its child elements in a way that best uses the panel's space, wrapping and aligning the elements as necessary. The default flow direction for a FlowPanel is from left to right and top to bottom.

The following markup example demonstrates how the FlowPanel breaks and wraps content. The FlowPanel contains four one-inch square canvases. The FlowPanel attempts to display its child elements left to right and top to bottom.

<Border xmlns="https://schemas.microsoft.com/2003/xaml" Background="White">
  <FlowPanel>
      <Border Background="Red" Width="1in" Height="1in"/>
      <Border Background="Green" Width="1in" Height="1in"/>
      <Border Background="Blue" Width="1in" Height="1in"/>
      <Border Background="Yellow" Width="1in" Height="1in"/>
  </FlowPanel>
</Border>

Figure 3-3 shows the output when the FlowPanel can fit all elements on a single line. Figure 3-4 demonstrates the FlowPanel wrapping the last element to a new line. Figure 3-5 shows the worst case where the FlowPanel must place each element on its own line. Figure 3-6 shows the last element wrapping to a new line, and Figure 3-7 shows every element wrapping to a new line.

Figure 3-5. The FlowPanel breaks lines only when necessary.

Figure 3-6. The FlowPanel panel wrapping the last element to a new line

Figure 3-7. The FlowPanel panel wrapping each element to a new line

TextPanel

The TextPanel panel formats, sizes, and draws text. This panel class supports multiple lines of text as well as multiple text formats. You will typically use the TextPanel class when you need complex layout support. However, when you require only simple text display, it's better to use the Text element instead.

The following markup example demonstrates how the TextPanel breaks and wraps content. The TextPanel adjusts the number of columns and the height of each column as you resize the window.

<Border xmlns="https://schemas.microsoft.com/2003/xaml" Background="White">
  <TextPanel 
    ColumnCount="3" 
    ColumnWidth="200px" 
    ColumnGap="25px"
    ColumnRuleWidth="5px"
    ColumnRuleBrush="blue">

    <Block Background="LightGray">
      <Inline FontFamily="Arial" FontWeight="Bold"
              FontSize="16pt">Transcript of the 
           <Italic>Nicolay Draft</Italic> 
           of the Gettysburg Address.
      </Inline>
    </Block>
    §
  </TextPanel>
</Border>

Figure 3-8 shows the resulting output.

Figure 3-8. The TextPanel with multiple font characteristics, columns, and formatting

GridPanel

The GridPanel panel displays tabular data. The GridPanel supports many properties that you can use to customize the layout of the tabular data. For example, you can set the Columns and Rows properties to control the number of columns and rows in the grid. Similarly, the ColumnStyles and RowStyles properties allow you to set a collection of properties that the GridPanel applies to the rows and columns, respectively.

GridPanel arranges its children in order, starting with the upper left cell and moving to the right until the end of the row. A child can take more than one column if you set the GridPanel.ColumnSpan property on the child. Similarly, GridPanel.RowSpan allows a child to span multiple rows.

The following markup displays a Calculator user interface that looks quite similar to the Windows Calculator utility.

<Border xmlns="https://schemas.microsoft.com/2003/xaml" Background="#DEE7F7">
<DockPanel Dock="Left">
  <Border  BorderThickness="0,0,0,0">
<!-- Padding="10, 10, 10, 10"  -->
  <GridPanel Columns="7">
    <GridPanel.ColumnStyles>
      <Column Width="16%"/>
      <Column Width="4%"/>
      <Column Width="16%"/>
      <Column Width="16%"/>
      <Column Width="16%"/>
      <Column Width="16%"/>
      <Column Width="16%"/>
    </GridPanel.ColumnStyles>

    <GridPanel.RowStyles>
      <Row Height="25"/>
      <Row Height="10"/>
      <Row Height="35"/>
      <Row Height="7"/>
      <Row Height="35"/>
      <Row Height="35"/>
      <Row Height="35"/>
      <Row Height="35"/>
    </GridPanel.RowStyles>

        <Border GridPanel.ColumnSpan="7" BorderBrush="#DEE7F7" 
                BorderThickness="2,2,2,2" Background="White">
            <Text HorizontalAlignment="right"
                  ID="CalcText">0.</Text>    
        </Border>

        <Text GridPanel.ColumnSpan="7"/>

        <Border BorderThickness="0,0,0,0">
          <GridPanel>
            <Border BorderBrush="#DEE7F7" BorderThickness="2,2,2,2">
               <Text Width="16%"
                  HorizontalAlignment="center"></Text>
            </Border>
          </GridPanel>
        </Border>

        <Text Width="4%"/>
        <DockPanel GridPanel.ColumnSpan="5" Dock="Left">
          <Button Width="33.33%" Foreground="Red">Backspace</Button>
          <Button Width="33.33%" Foreground="Red">CE</Button>
          <Button Width="33.33%" Foreground="Red">C</Button>
        </DockPanel>

        <Text GridPanel.ColumnSpan="7"/>

        <Button Foreground="Red">MC</Button>
        <Text/>
        <Button Foreground="Blue">7</Button>
        <Button Foreground="Blue">8</Button>
        <Button Foreground="Blue">9</Button>
        <Button Foreground="Red">/</Button>
        <Button Foreground="Blue">sqrt</Button>

        <Button Foreground="Red">MR</Button>
        <Text/>
        <Button Foreground="Blue">4</Button>
        <Button Foreground="Blue">5</Button>
        <Button Foreground="Blue">6</Button>
        <Button Foreground="Red">*</Button>
        <Button Foreground="Blue">%</Button>

        <Button Foreground="Red">MS</Button>
        <Text/>
        <Button Foreground="Blue">1</Button>
        <Button Foreground="Blue">2</Button>
        <Button Foreground="Blue">3</Button>
        <Button Foreground="Red">-</Button>
        <Button Foreground="Blue">1/x</Button>

        <Button Foreground="Red">M+</Button>
        <Text/>
        <Button Foreground="Blue">0</Button>
        <Button Foreground="Blue">+/-</Button>
        <Button Foreground="Blue">.</Button>
        <Button Foreground="Red">+</Button>
        <Button Foreground="Red">=</Button>

  </GridPanel>
</Border>
</DockPanel>
</Border>

Figure 3-9 shows the resulting output.

Figure 3-9. The GridPanel as a calculator

FixedPanel

The FixedPanel panel allows you to specify the exact locations and sizes of every element. Elements on a FixedPanel will always display in the same location and size on all devices. I'll discuss the FixedPanel panel later in this chapter in the "Document Layout Services" section.

Controls

XAML has all the controls you've come to expect from Windows—buttons, check boxes, radio buttons, list boxes, combo boxes, menus, scroll bars, sliders, and so on. This sample demonstrates some of the common controls provided in Longhorn. You can see the results in Figure 3-10.

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
<DockPanel>
  <Menu DockPanel.Dock="Top">
    <MenuItem Header="File">
      <MenuItem Header="New" />
      <MenuItem Header="Open" />
    </MenuItem>

    <MenuItem Header="Edit">
      <MenuItem Header="Cut"/>
      <MenuItem Header="Copy"/>
      <MenuItem Header="Paste"/>
    </MenuItem>
  </Menu>

<FlowPanel>

<Button> Button </Button>
<Border Width="15"/>

<CheckBox Checked="true"> CheckBox </CheckBox>
<Border Width="15"/>

<RadioButtonList>
  <RadioButton> RadioButton 1 </RadioButton>
  <RadioButton Checked="true"> RadioButton 2 </RadioButton>
  <RadioButton> RadioButton 3 </RadioButton>
</RadioButtonList>
<Border Width="15"/>

<ListBox>
   <ListItem> ListItem 1 </ListItem> 
   <ListItem> ListItem 2 </ListItem> 
   <ListItem> ListItem 3 </ListItem> 
</ListBox>
<Border Width="15"/>

<ComboBox>
   <ListItem> ListItem 1 </ListItem> 
   <ListItem> ListItem 2 </ListItem> 
   <ListItem> ListItem 3 </ListItem> 
</ComboBox>
<Border Width="15"/>

    <DockPanel>
      <VerticalSlider DockPanel.Dock="Top"  Height="200"
             Minimum="0" Maximum="255" Value="75"
             SmallChange="1" LargeChange="16"/> 
      <Text DockPanel.Dock="Bottom">Slider</Text>
    </DockPanel>
<Border Width="15"/>

    <DockPanel>
      <VerticalScrollBar DockPanel.Dock="Top" 
             Minimum="0" Maximum="255" Value="125" Height="200"
             SmallChange="1" LargeChange="16"/> 
      <Text DockPanel.Dock="bottom">ScrollBar</Text>
    </DockPanel>
<Border Width="15"/>
   
<TextBox> TextBox </TextBox> 

</FlowPanel>
</DockPanel>
</Border>

Figure 3-10. An example of XAML controls

XAML also allows you to combine elements and controls to create rich effects. We call this combining of elements control composition, and it is one of the most powerful aspects of Longhorn. For instance, to create a button with an image, you put an Image element inside Button:

<Button> 
  <Image Source="tulip.jpg"/>
</Button>

To have both an image and text in the Button, as you can see in Figure 3-11, we use our old friend DockPanel:

<Button> 
  <DockPanel>
     <Image Source="tulip.jpg"/>
     <Text DockPanel.Dock="fill" VerticalAlignment="center"> Button 
         <Italic>with Image!</Italic>
     </Text>
  </DockPanel>
</Button>

Figure 3-11. A button with an image and text

You can put pretty much anything inside anything, including this strange example of a CheckBox inside a Button:

<Button> 
  <CheckBox Checked="true"> CheckBox </CheckBox>
</Button>

Composition is powerful enough that many of the Longhorn controls are actually defined using composition. For instance, a ScrollBar is actually two buttons and a slider, plus some event handler logic to hook them together.

XAML also includes some control "primitives," which are primarily used with control composition to build larger effects. For instance, ScrollViewer takes one child (typically a panel) and adds scroll bars to it. This example places a very large list of CheckBox elements inside a ScrollViewer, something which prior to Longhorn required a separate control such as Windows Forms' CheckedListBox:

<Border BorderThickness="1" BorderBrush="black">
  <ScrollViewer Height="100" Width="200">
    <GridPanel Columns="1">
      <CheckBox Checked="true"> CheckBox 1</CheckBox>
      <CheckBox Checked="true"> CheckBox 2</CheckBox>
      <CheckBox Checked="true"> CheckBox 3</CheckBox>
      <CheckBox Checked="true"> CheckBox </CheckBox>
      <CheckBox Checked="true"> CheckBox </CheckBox>
      <CheckBox Checked="true"> CheckBox </CheckBox>
      <CheckBox Checked="true"> CheckBox </CheckBox>
      <CheckBox Checked="true"> CheckBox </CheckBox>
    </GridPanel>
  </ScrollViewer>
</Border>

Resources and Styles

XAML provides very rich facilities for customizing the look of your application, through entities known as styles. However, before we get into this topic, we need to learn about resources. The term resources used in this context simply refers to a way of reusing commonly defined objects and values. Let's look at an example:

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <FlowPanel>
    <FlowPanel.Resources>
      <SolidColorBrush def:Name="MyColor" Color="Gold"/>
    </FlowPanel.Resources>
    <Button Background="{MyColor}"/>
    <Ellipse Fill="{MyColor}"/>
  </FlowPanel>
</Border>

This code defines a new resource named MyColor, whose type is SolidColorBrush and value is Gold. This resource is part of the FlowPanel's Resources collection. Every element has a Resources collection. You can define resources on any element you want, but most often you'll put them only on the root element—in this case, the FlowPanel.

Once you define a resource, you can then reference the resource in a property value by putting the resource name in braces, as you see here:

<Button Background="{MyColor}"/>

When the XAML processor sees {MyColor} in this example, it will first check the button's Resources collection. Because Button doesn't have a definition of MyColor (its Resources collection is empty), it will check the Button's parent—the FlowPanel.

One particularly useful kind of resource is a Style. Style is both the name of class and the name of a property that all elements have. A Style defines properties to set on an element, which then uses that designated style. This example defines a Style called MyStyle and applies that style to a button:

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <FlowPanel>
    <FlowPanel.Resources>

      <Style def:Name="MyStyle">               
           <Button Background="Red" FontSize="24"/>
      </Style>   

    </FlowPanel.Resources>
    <Button>Normal</Button>

    <Button Style="{MyStyle}">Styled</Button>

  </FlowPanel>
</Border>

You can also define a Style resource with no name—which becomes the element's default style for elements where you do not specify an explicit Style property. This example adds a default style to the preceding example:

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <FlowPanel>
    <FlowPanel.Resources>

      <Style>               
           <Button Background="Green" FontSize="15"/>
      </Style>   

      <Style def:Name="MyStyle">               
           <Button Background="Red" FontSize="24"/>
      </Style>   
    </FlowPanel.Resources>
    <Button>Normal</Button>
    <Button Style="{MyStyle}">Styled</Button>
  </FlowPanel>
</Border>

You can do a kind of style inheritance by setting the BasedOn property of the Style class. Referencing the new Style class will set all the properties that the old Style did, plus the additional properties you specify. The following example defines two styles: the first sets the Background property, and the second, based on the first, sets the FontSize property.

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <FlowPanel>
    <FlowPanel.Resources>
      <Style def:Name="Style1">
           <Button Background="Red"/>
      </Style>   

      <Style def:Name="Style2" BasedOn="{Style1}">
           <Button FontSize="24"/>
      </Style>   
    </FlowPanel.Resources>
    <Button Style="{Style1}">Style 1</Button>
    <Button Style="{Style2}">Style 2</Button>
  </FlowPanel>
</Border>

It's even possible for a Style to set properties conditionally, using a feature known as property triggers. Style has a property named VisualTriggers, which is a collection of PropertyTriggers. Each PropertyTrigger specifies a condition using the Property and Value properties, and contains a collection of Set statements. When the styled element's property matches that value, the Set statements are applied, and when the condition is no longer true, the values are unapplied, as if they had never been set in the first place. This example uses property triggers to make the button green when the mouse is over the button, and red otherwise:

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
           xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <FlowPanel>
    <FlowPanel.Resources>
      <Style def:Name="Style1">
           <Button Background="Red"/>

           <Style.VisualTriggers>
             <PropertyTrigger Property="IsMouseOver" Value="true">
               <Set PropertyPath="Background" Value="Green"/>
             </PropertyTrigger>
            </Style.VisualTriggers>
      </Style>   
    </FlowPanel.Resources>
    <Button Style="{Style1}">Style 1</Button>
  </FlowPanel>
</Border>

Many XAML controls use control composition. They combine a number of smaller controls to create a larger, more complicated control. Styles even let you change this! By specifying a different composition, you can completely redefine the look of a control while still keeping its behavior.

After you declare the style element and its properties, the <Style.VisualTree> tag specifies inside the Style which elements to compose to create the larger control. You can set child elements' properties as usual, and give to those children children of their own. You can also use property aliasing to set property values. For example, Name1="*Alias(Target=Name2)" will set the child's Name1 property to the larger control's Name2 property. The following example creates a style for Button that changes the composition to achieve a round look, as you can see in Figure 3-12. The button's Background and Content properties are aliased at appropriate points in the visual tree.

<Border Background="white"
    xmlns="https://schemas.microsoft.com/2003/xaml"
    xmlns:def="Definition">
  <FlowPanel>
    <FlowPanel.Resources>
      <Style def:Name="RoundButton">
    
         <Button FontSize="20"/>
     
         <Style.VisualTree>
             <Canvas>
                 <Rectangle ID="MainRect" 
                     RadiusX="10" RadiusY="10" 
                     Fill="*Alias(Target=Background)" 
                     Width="100%" Height="100%" />

                 <FlowPanel Width="100%" Height="100%" >
                     <ContentPresenter  
                          ContentControl.Content="*Alias(Target = Content)" 
                          Margin="15,3,15,5"/>
                 </FlowPanel>
             </Canvas>
         </Style.VisualTree>
      </Style>
    </FlowPanel.Resources>
 
    <Button Style="{RoundButton}">
        standard RoundButton
    </Button>

    <Button Background="red" Style="{RoundButton}">
        red RoundButton
    </Button>

    <Button>
        standard button
    </Button>

    <Button Background="red">
        red standard button
    </Button>
  </FlowPanel>
</Border>

Figure 3-12. A couple of RoundButton and standard buttons on a FlowPanel

Graphics and Animations

XAML provides extensive support for drawing shapes, transforming the state of an object, and animating nearly any property of an object. You use Shape elements for drawing, Transform elements to altering a property or an object, and Animation elements to change a property of an object over time.

Shapes

XAML provides a set of Shape elements for drawing, which include Ellipse, Line, Rectangle, Path, Polygon, and Polyline. A Shape has a Fill, which is the background color, and a Stroke, which is the outline color. Fill and Stroke default to transparent, so make sure you set at least one of them! The StrokeWidth property controls the thickness of the outline.

Shapes can't have child elements. You typically place shapes inside a Canvas, so the first shape in markup will be the first one drawn. This example illustrates some of the basic shapes, which you can also see in Figure 3-13:

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <Canvas Height="400" Width="400">
        <Ellipse CenterX="70" CenterY="75" 
         RadiusX="30" RadiusY="50" 
         Fill="yellow" Stroke="red" StrokeThickness="15"/>

        <Rectangle RectangleLeft="150" RectangleTop="20" 
         RectangleHeight="100" RectangleWidth="40"
         Fill="lightBlue" Stroke="green"/>

        <Line X1="20" Y1="220" X2="150" Y2="240"
         Stroke="black" StrokeThickness="5"/>

        <Polygon Points="220,140 270,240 170,240"
         StrokeLineJoin="Round"
         Stroke="black" StrokeThickness="20"/>
  </Canvas>
</Border>

Figure 3-13. Various shapes on a Canvas

So far, we've used only solid colors with the Stroke and Fill properties. But in XAML, almost anywhere you can use a color you can specify a Brush. SolidColorBrush is the kind of brush we've been using so far, but XAML also supports ImageBrush, LinearGradientBrush, and RadialGradientBrush. ImageBrush has an ImageSource property that specifies the name of the image file. SolidColorBrush has a Color property. LinearGradientBrush and RadialGradientBrush contain a GradientStopCollection, which allows for very complicated gradients. This example defines four brushes as resources, and uses them as the Stroke and Fill of the ellipses. You can see what they look like in Figure 3-14.

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <Border.Resources>
        <LinearGradientBrush def:Name="lineargradient" StartPoint="0,0" 
                             EndPoint="1,1"  >
          <LinearGradientBrush.GradientStops>
            <GradientStopCollection>
              <GradientStop Color="Blue" Offset="0"/>
              <GradientStop Color="white" Offset="1"/>
            </GradientStopCollection>
          </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>

        <RadialGradientBrush def:Name="radialgradient" Focus="0.3,0.3">
          <RadialGradientBrush.GradientStops>
            <GradientStopCollection>
              <GradientStop Color="red" Offset="0"/>
              <GradientStop Color="yellow" Offset="1"/>
            </GradientStopCollection>
          </RadialGradientBrush.GradientStops>
        </RadialGradientBrush>

        <ImageBrush def:Name="image" ImageSource="Tulip.jpg" TileMode="Tile"/>

        <SolidColorBrush def:Name="solid" Color="gray"/>
  </Border.Resources>

  <Canvas Height="400" Width="400">
        <Ellipse CenterX="100" CenterY="75" 
         RadiusX="90" RadiusY="50" 
         Fill="{lineargradient}" Stroke="{image}" StrokeThickness="15"/>

        <Ellipse CenterX="300" CenterY="170" 
         RadiusX="50" RadiusY="150" 
         Fill="{radialgradient}" Stroke="{solid}" StrokeThickness="15"/>
  </Canvas>
</Border>

Figure 3-14. Gradients at work

Transforms

XAML supports several kinds of Transforms. RotateTransform rotates by the amount of the Angle property. TranslateTransform moves things according to the X and Y properties. ScaleTransform will shrink or stretch according to the ScaleX and ScaleY properties. SkewTransform slants things, using the AngleX, AngleY, and Center properties. MatrixTransform supports arbitrary affine transformations. Finally, TransformCollection is itself a Transform that allows you to combine several transforms together.

Some classes, such as Brush, have a Transform property. For other cases, you can use the TransformDecorator element, which has a Transform property. TransformDecorator will transform its child element. (Like Border, it can have only one child.) TransformDecorator can contain any kind of child, including shapes, panels, and controls. This example uses to TransformDecorators. The first contains an Ellipse, and rotates it 45 degrees. The second TransformDecorator contains a ListBox and both rotates and scales the ListBox. You can see what the shapes look like in Figure 3-15.

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <Canvas Height="400" Width="400">
     <TransformDecorator Transform="rotate 45">
       <Ellipse CenterX="100" CenterY="75" 
         RadiusX="90" RadiusY="50" 
         Fill="white" Stroke="black" StrokeThickness="15"/>
     </TransformDecorator>

     <TransformDecorator Canvas.Top="200" Canvas.Left="100">
       <TransformDecorator.Transform>
         <TransformCollection>
           <RotateTransform Angle="135"/>
           <ScaleTransform ScaleX="2" ScaleY="4"/>
         </TransformCollection>
        </TransformDecorator.Transform>

        <ListBox >
          <ListItem> ListItem 1 </ListItem> 
          <ListItem> ListItem 2 </ListItem> 
          <ListItem> ListItem 3 </ListItem> 
        </ListBox>
     </TransformDecorator>
  </Canvas>
</Border>

Figure 3-15. A skewed ListBox and Ellipse

Animations

XAML also supports animations. You can animate nearly every property. Some properties have a corresponding "Animations" property—for example, RotateTransform.Angle and RotateTransform.AngleAnimations. In other cases, you can assign an animation collection to a property using compound property syntax. For example, see the following code:

  <Button>
       <Button.Width>
         … put animation collection here …
       </Button.Width>
  </Button>

Every type of property has a separate animations collection. The type of Button.Width is Length, so one uses the LengthAnimationCollection. Similarly, the animation objects themselves are specific to the type of the property you animate—the LengthAnimationCollection contains a set of LengthAnimation objects.

Animation objects have a From and To property, whose types match the type of the property being animated. The animation object also has Begin, Duration, and End properties, which are measured in seconds and control the timing of the animation. Begin also supports the value Immediate, and Duration supports Indefinite. You can use the RepeatCount and RepeatDuration properties to repeat the animation automatically. The Fill property specifies what happens to the property after the animation is over. Fill="Hold" is one of the most important values; it keeps the property at the animation End value.

Animation classes are not part of the default XAML namespace, so you will need to use the <?Mapping> and xmlns constructs to load the MSAvalon.Windows.Media.Animation namespace. Because this namespace contains classes from multiple DLLs, you'll need a separate <?Mapping> and xmlns for each DLL.

The next example animates two properties, the RotateTransform.Angle property and the Button.Width property, which use classes from both animation namespaces. Figure 3-16 shows the button at different times.

<?Mapping XmlNamespace="animC" ClrNamespace="MSAvalon.Windows.Media.Animation" 
                               Assembly="PresentationCore" ?>
<?Mapping XmlNamespace="animF" ClrNamespace="MSAvalon.Windows.Media.Animation" 
                               Assembly="PresentationFramework" ?>
<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:animC="animC"
       xmlns:animF="animF"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <Canvas Height="400" Width="400">
     <TransformDecorator Canvas.Top="200" Canvas.Left="100">
       <TransformDecorator.Transform>
         <TransformCollection>
           <RotateTransform Angle="135">
              <RotateTransform.AngleAnimations>
                 <animC:DoubleAnimationCollection>
                    <animC:DoubleAnimation From="0" To="360" Duration="4" 
                       AutoReverse="True" 
                       RepeatDuration="Indefinite"/>
                 </animC:DoubleAnimationCollection>

              </RotateTransform.AngleAnimations>

           </RotateTransform>
         </TransformCollection>
        </TransformDecorator.Transform>

        <ListBox >
          <ListItem> ListItem 1 </ListItem> 
          <ListItem> ListItem 2 </ListItem> 
          <ListItem> ListItem 3 </ListItem> 
        </ListBox>
     </TransformDecorator>

     <Button Width="40" Canvas.Top="10" Canvas.Left="10">
       <Button.Width>
         <animF:LengthAnimationCollection>
           <animF:LengthAnimation From="40" To="300" 
             AutoReverse="true" Begin="1" Duration="1.2"
             RepeatDuration="Indefinite"/>
         </animF:LengthAnimationCollection>
       </Button.Width>
       Button
     </Button>

  </Canvas>
</Border>

Figure 3-16. Views of the animated button at different times

Document Services

The Longhorn platform provides extensive services that support a better online document viewing experience. There are two main services: a control designed for viewing, paginating, and navigating through the content of a document, and layout services designed to enhance the reading experience.

PageViewer Control

You use the PageViewer control when you want to display a document to the user for online viewing. The PageViewer control provides pagination and page navigation functionality. The control automatically formats the document's content into separate pages. The user can directly navigate to different pages using the controls provided by the page viewer.

Pagination and Navigation

Traditionally, online content, such as Web pages, was continuous. A user interface provided scroll bars to allow you to view content that couldn't fit in the visible area. In effect, you would "scroll" the view window to the position in the document you wished to see.

With pagination, you split the content of the document into one or more individual pages, similar to a book. The Longhorn platform provides support for paginated content by including several controls that help you display and navigate through content displayed as discrete pages. In addition, Longhorn provides a pagination application programming interface (API) to extend these capabilities and provide rich pagination support for custom pagination applications.

The PageViewer control is actually a complex control built from smaller controls using control composition techniques I've previously described. The PageViewer control uses the PageSource and the PageElement controls to provide its pagination functionality. The PageSource control breaks and formats the content across pages. The PageElement control renders a single page. The PageViewer control also uses the PageBar control to allow you to navigate through the pages.

Using the PageViewer control is very simple. To display a known document, you can use it as shown in the following code. Of course, you can hook up event handlers and change the source document to cause the page viewer to display different documents.

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <PageViewer Source="AuthorsandPublishers.xaml" />
</Border>

Figure 3-17 shows the page viewer hosted in a browser window displaying its document. Note the page navigation controls at the top of the document.

Figure 3-17. The PageViewer control

Document Layout Services

Longhorn also provides document layout services designed to make the reading experience better. Longhorn contains support for two new types of documents:

  • Adaptive flow documents
  • Fixed layout documents

These new document formats allow developers to provide their applications' users a better document reading experience.

Adaptive flow documents use specialized markup elements that declare that a document should be Adaptive. Longhorn automatically optimizes an adaptive document to best use the available screen space and provide the best reading experience for the user based on the capabilities or limitations of his or her system.

For example, the user might have one of the recently introduced 16-by-9 aspect ratio wide-screen displays. It's very difficult to read a line of text that spans a lengthy horizontal line. Depending on the width of the window, an adaptive document might divide the text into two, three, or more columns, thus reducing the effort for a user to scan a text line.

In another example, a document might contain an image and text that flows around the image. As you shrink the size of the document's window in a nonadaptive document, the image remains a fixed size and you see less and less of the text. An adaptive document could shrink the image when it determines that insufficient text is visible in the window. This allows the reader to still get a general idea of what the image portrays but continue to read the text in the context of the image. In a smaller window, seeing every pixel of an image is likely to be less important and useful to the reader than being able to read more text. An alternative, such as separating the image and surrounding text onto separate pages, defeats the intent of the document's author, which was to present the text and image together in context.

Fixed layout documents appear the same every time, regardless of the viewer's screen size, window size, or output device. You create a fixed layout document using specialized markup elements or by printing a document using a Microsoft Windows Vector Graphics (WVG) printer driver.

Adaptive Layout Documents

In an adaptive layout document, you supply key preferences in the root-level markup. Longhorn then can render the document in a way that makes best use of the window area and enhances its readability. Longhorn automatically determines the optimum width and number of columns for a page, ideal sizes for all text elements, optimum sizes and positions for all figures, and the widths of margins and gutters to give the best overall presentation of the content.

Producing an Adaptive Layout Document

To create an adaptive layout document, use declarative markup similar to the following:

<Border
       xmlns="https://schemas.microsoft.com/2003/xaml"
       xmlns:def="Definition"
       Background="BlanchedAlmond" 
   >
  <AdaptiveMetricsContext ColumnPreference="Medium"
                          FontFamily="Arial">
    <TextPanel Background="white"> 
      <Section>
        <Heading OutlineLevel="1">Adaptive Layout Example</Heading>
        <Paragraph>
This example shows the advanced capabilities of Adaptive Flow 
Layout. Lorem ipsum dolor sit amet, consectetuer adipiscing 
elit, sed diam nonummy nibh euismod tin cidunt ut laoreet dolore 
magna aliquam erat volutpat. Ut wisi enim ad minim veni am, quis 
nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip 
ex ea commodo consequat. Duis autem vel eum iriure.</Paragraph>
        <Paragraph>
        <Image TextPanel.FlowBehavior="Figure" Source="picture1.jpg"
               TextPanel.Emphasis="Medium" />
Notice how images and text are flowed intelligently to enhance the 
reading experi ence. Lorem ipsum dolor sit amet, consectetuer 
adipiscing elit, sed diam nonummy  nibh euismod tincidunt ut laoreet 
dolore magna aliquam erat volutpat. Ut wisi e nim ad minim veniam, 
quis nostrud  exerci tation ullamcorper suscipit lobortis ni sl ut 
aliquip ex ea commodo consequat. Duis autem vel eum iriure.
</Paragraph>
        <Paragraph>Adaptive layout is an exciting new feature of Longhorn.
        <Image TextPanel.FlowBehavior="Figure" Source="picture2.jpg"
               TextPanel.Emphasis="Low" />
Lorem ipsum dolor sit amet, consectetuer 
adipiscing elit, sed diam nonummy  nibh euismod tincidunt ut laoreet 
dolore magna aliquam erat volutpat. Ut wisi e nim ad minim veniam, 
quis nostrud  exerci tation ullamcorper suscipit lobortis ni sl ut 
aliquip ex ea commodo consequat. Duis autem vel eum iriure.
</Paragraph>
      </Section>
    </TextPanel>
  </AdaptiveMetricsContext>
</Border>

Fixed-Layout Documents

You use a fixed-layout document to present the document contents in exactly the same layout and format, independent of the application software, hardware, and operating system used. In addition, a fixed-layout document renders identically on all output devices. A fixed-layout document is a set of objects that collectively describe the appearance of one or more pages.

Producing a Fixed-Layout Document

You can use two different techniques to produce a fixed-layout document:

  • Print a document without markup to a file using the Longhorn printer driver
  • Write a fixed-layout document using XAML

When you print a document using most Microsoft Win32 applications (for example, Microsoft Office) using the Longhorn printer driver, the printer driver will create an XAML file that contains markup to paginate and position each character, image, or vector graphic in the printed document.

You can choose to output the document as a markup file directly or to include the markup file inside a container. When you select container output, you can also apply digital rights and document protection to the document.

Alternatively, you can create XAML using an editor. The following is a skeletal example of fixed-layout document:

<FixedPanel xmlns="https://schemas.microsoft.com/2003/xaml/" >
  <FixedPage Width="8.50in" Height="11.00in"> <!-- PAGE 1 -->
    <Text FontFamily="Arial" FontSize="8.4" FixedPage.Left="1.250in"
          FixedPage.Top="0.530in" FontWeight="Bold">1.</Text>
    <Text FontFamily="Arial" FixedPage.Left="1.350in" FixedPage.Top="0.500in"
          FontWeight="Bold" FontSize="12">Fixed Document</Text>
  </FixedPage>
  <FixedPage>
    <Text>This is page 2</Text>
  </FixedPage>
  <FixedPage>
    <Text>This is page 3</Text>
  </FixedPage>
</FixedPanel>

Summary

Panels allow you to divide the display surface into areas with different layout characteristics. You have a great variety of controls that you can use to populate the panels of a display. Shapes, Transforms, and Animations allow you to produce dynamic graphical output. Using control composition, you can combine these features to produce practically any user interface you want. You can produce adaptive documents that lay out your content intelligently and make it easy for the reader to read. Alternatively, you can precisely position every single element on a page and control pagination explicitly to produce a document that appears exactly as you want regardless of the output device. What's more, you can do it all declaratively using XAML. This sure as heck beats writing a WM_PAINT message handler!

Continue to Chapter 4: Storage