Styles and Templates

The capability to customize the look of a control in WPF, without losing any of its built-in functionality, is one of the huge advantages that WPF brings to the development scene. Consider the two slider controls in Figure 21.16. The top is the default style, and the bottom represents a restyled slider. Functionality is identical. We have simply changed the appearance of the control.

Image

FIGURE 21.16 The standard slider (top) and a restyled slider (bottom).

Style is an actual class (in the System.Windows namespace) that is used in association with a control; it groups property values together to enable you, as a developer, to set them once and have them applied to controls en masse instead of having to set them individually on each control instance. Suppose, for instance, that your application uses a nice grayscale gradient for its button backgrounds. In addition, each button has a white border and renders its text with the Segoe UI font. We can manipulate each of these aspects using Button properties, but it would quickly become laborious to do this on every button. A Style class enables us to set all these properties once and then refer each Button control to these properties by assigning the style to the button.

Here is the Style class defined within a window in XAML.

<Window.Resources>
   <Style x:Key="GradientButton" TargetType="Button">
      <Setter Property="Margin" Value="2"/>
      <Setter Property="BorderBrush" Value="White" />
      <Setter Property="FontFamily" Value="Segoe UI"/>
      <Setter Property="FontSize" Value="12px"/>
      <Setter Property="FontWeight" Value="Bold"/>
      <Setter Property="Foreground" Value="White" />
      <Setter Property="Background" >
         <Setter.Value>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" >
               <GradientStop Color="Gray" Offset="0.2"/>
               <GradientStop Color="DarkGray" Offset="0.85"/>
               <GradientStop Color="Gray" Offset="1"/>
            </LinearGradientBrush>
         </Setter.Value>
      </Setter>
   </Style>
</Window.Resources>

Assigning this style to any button within the window is as simple as this:

<Button Style="{StaticResource GradientButton}" Height="38" Name="button1"
Width="100">OK</Button>

This works well for simplifying property sets. But what happens when we want to customize an attribute that isn’t surfaced as a property? To continue with our Button control, what if we wanted an oval shape rather the standard rectangle? Because the Button class doesn’t expose a property that we can use to change the background shape, we appear to be out of luck.

Enter the concept of templates. Templates enable you to completely replace the visual tree of any control, giving you full control over every aspect of the control’s user interface. A visual tree in WPF is the hierarchy of controls inheriting from the Visual class that provide a control’s final rendered appearance. You can find a good overview of WPF visual trees and logical trees at http://www.msdn.microsoft.com. Search for the article “Trees in WPF.”


Note

Earlier we mentioned that controls in WPF were “lookless”; templates are evidence of that fact. The functionality of a control exists separately from its visual tree. The default look for all the controls is provided through a series of templates, one per each Windows theme. This means that WPF controls can automatically participate in whatever operating system (OS) theme you are running.


Templates are created via the ControlTemplate class. Within this class (or element, if you are implementing the template in XAML), you need to draw the visuals that represent the button. The Rectangle class in WPF can be used to draw our basic background shape. By tweaking the RadiusX and RadiusY properties, we can soften the normal 90-degree corners into the desired elliptical shape.

<Rectangle RadiusX="25" RadiusY="25" Width="100" Height="50"
Stroke="Black" StrokeThickness="1" />

We can also add some more compelling visual aspects, such as a gradient fill, to the button.

<Rectangle.Fill>
   <LinearGradientBrush>
      <LinearGradientBrush.GradientStops>
         <GradientStop Offset="0" Color="Gray" />
         <GradientStop Offset="1" Color="LightGray" />
      </LinearGradientBrush.GradientStops>
   </LinearGradientBrush>
</Rectangle.Fill>


Tip

To test the look and feel so far, type your “shape” XAML into the XAML editor, and tweak it as desired. When you are satisfied, you can copy and paste the XAML into the template. A better tool for designing user interfaces is Microsoft Blend for Visual Studio, but handcrafting the XAML or relying on Visual Studio’s designer should be sufficient for simple design scenarios.


The text within the button is easily rendered using a TextBlock object.

<TextBlock Canvas.Top="5" Height="40" Width="100" FontSize="20"
TextAlignment="Center">OK</TextBlock>

Once we are happy with the look and feel, we can “template-ize” this appearance by nesting everything within a ControlTemplate element. Because we need to refer to this template later, we associate it with a key.

<ControlTemplate x:Key="OvalButtonTemplate">

Finally, we embed the whole thing as a resource. A resource is simply a .NET object (written in XAML or code) that is meant to be shared across other objects via its key. In this specific case, we want to be able to use this template with any button we want. Resources can be declared at any level within a WPF project. We can declare resources that belong to the overall window or to any element within the window (such as a Grid panel), or we can store all our resources in something known as a ResourceDictionary and allow them to be referenced from any class in our project. For this example, we stick to a simple resource defined in our parent window. (For reference, this is the Window.Resources element that you see in the following code.)

Listing 21.2 pulls this all together, and Figure 21.17 shows the resulting button.

Image

FIGURE 21.17 A custom button template assigned to a button.

LISTING 21.2 Replacing a Button’s Template


<Window x:Class="ContosoAvalon.CustomLook"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="CustomLook" Height="300" Width="300"
    Background="#F8F8F8">
   <Window.Resources>
      <ControlTemplate x:Key="OvalButtonTemplate">
         <Canvas Width="100" Height="25" Margin="2">
            <Rectangle x:Name="BaseRectangle" Canvas.Top="0" RadiusX="25"
             RadiusY="25" Width="100" Height="40" Stroke="DarkGray"
             StrokeThickness="1">
               <Rectangle.Fill>
                  <LinearGradientBrush>
                     <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0" Color="Gray" />
                        <GradientStop Offset="1" Color="LightGray" />
                     </LinearGradientBrush.GradientStops>
                  </LinearGradientBrush>
               </Rectangle.Fill>
            </Rectangle>
            <TextBlock Canvas.Top="5" Height="40" Width="100" FontSize="20"
             TextAlignment="Center">OK</TextBlock>
         </Canvas>
      </ControlTemplate>
   </Window.Resources>
   <Canvas>
   <Button Canvas.Left="49" Canvas.Top="44" Height="38" Name="button1"
    Width="93" Template="{StaticResource OvalButtonTemplate}" />
   </Canvas>
</Window>


Debugging the Visual Tree

Although the ability to replace portions of the visual tree via templates and styles is a powerful feature, it does come with a downside. Depending on the depth of the visual tree and the number of properties and attributes overridden at various levels by templates, it can become exponentially difficult to actually understand and debug issues that might arise somewhere within the tree.

Consider a template that overrides a few properties on a button and then a series of additional templates that override different properties on the base button and within the other templates themselves. Trying to unwind those dependencies by simply looking at the XAML can be difficult if not impossible. For instance, simply locating all the XAML can be a challenge because a template could live in a multitude of places within the project. Second, any errors within a given template may not produce a readily identifiable exception or stack trace that tells the whole story.

Enter the Live Visual Tree window, brand new with Visual Studio 2015. The Live Visual Tree, as its name implies, is a hierarchical view of the running XAML application and all its elements. The “live” moniker stems from the fact that it is reactive to changes within the visual tree as the application is running.

You access the Visual Tree window under the Debug, Windows menu. Figure 21.18 shows the visual tree associated with Listing 21.2. You will notice that the Live Visual Tree window provides information on every element within the visual tree, including child counts for each element. You can also click on any element and, when possible, the IDE will immediately navigate to the XAML that implements that element.

Image

FIGURE 21.18 The Live Visual Tree window.

If you right-click on an element and select Show Properties, you will see the Live Property Explorer. The Live Property Explorer works as a companion window to the Live Visual Tree. It provides current property information for any given element within the running application. This window has a few attributes that make it extremely useful for debugging a running WPF application. For one, it shows an element’s properties grouped on the scope in which they were set. This window also lets you change properties on an element and see those changes immediately take effect within the running application.

Let’s see how this works using our button template example. Using Listing 21.2, run the WPF application, and then launch the Live Visual Tree window. With the window open, expand all the nodes within the tree and locate the node that corresponds to the button itself (button1). Right click that node, select Show Properties, and then examine the details within the Live Property Explorer window (see Figure 21.19).

Image

FIGURE 21.19 The Live Property Explorer window.

Because you see those properties displayed within the Local section of the Explorer window, it’s easy to determine that the Height and Width properties of the button are being set within the Local scope. You can also quickly see those attributes of the button that are being overridden by the template that we attached to the button. For example, the default style for a button would use the system default template. We have replaced that with our own template, so that property within the Live Property Explorer has a line through it, showing that we have overridden with another value.

As mentioned, you can change any of these elements right within the window. For example, select the BaseRectangle element within the Live Visual Tree. In the Live Property Explorer, locate the RadiusX and RadiusY properties. (You can search for the properties using the Search Properties text box at the top of the window.) If you recall from Listing 21.2, we tweaked these properties in our template to create the oval structure for our button. In the Property Explorer, change both of these values to 0, and you should see the button instantly change to a rectangle with no rounded corners (see Figure 21.20).

Image

FIGURE 21.20 Changing visual elements at runtime using the Live Property Explorer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset