Styles and Control Templates

A style is a set of properties applied to content used for visual rendering. If you want to learn more about styles in Windows, you've come to the right place. This article is excerpted from chapter five of the book Programming Windows Presentation Foundation, written by Chris Sells and Ian Griffiths (O'Reilly; ISBN: 0596101139). Copyright © 2006 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 4 stars4 stars4 stars4 stars4 stars / 4
June 21, 2007
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

In a word processing document, a “style” is a set of properties to be applied to ranges of content—e.g., text, images, etc. For example, the name of the style I’m using now is called “Normal,Body,b” and for this document in pre-publication, that means a font family of Times, a size of 10, and full justification. Later on in the document, I’ll be using a style called “Code,x,s” that will use a font family of Courier New, a size of 9, and left justification. Styles are applied to content to produce a certain look when the content is rendered.

In WPF, a style is also a set of properties applied to content used for visual rendering. A style can be used to set properties on an existing visual element, such as setting the font weight of a Buttoncontrol, or it can be used to define the way an object looks, such as showing the name and age from aPersonobject. In addition to the features in word processing styles, WPF styles have specific features for building applications, including the ability to associate different visual effects based on user events, provide entirely new looks for existing controls, and even designate rendering behavior for non-visual objects. All of these features come without the need to build a custom control (although that’s still a useful thing to be able to do, as discussed in Chapter 9).

Without Styles

As an example of how styles can make themselves useful in WPF, let’s take a look at a simple implementation of tic-tac-toe in Example 5-1.

Example 5-1.  A simple tic-tac-toe layout

<!-- Window1.xaml -->
<Window

    x:Class="TicTacToe.Window1"
    
xmlns=http://schemas.microsoft.com/ winfx/avalon/2005
    xmlns:x=http://schemas.microsoft.com/ winfx/xaml/2005
    Text="TicTacToe"> 
 
<!-- the black background lets the tic-tac-toe -->
  <!-- crosshatch come through on the margins -->
  <Grid Background="Black">
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
      <RowDefinition />

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

    </Grid.ColumnDefinitions>
    <Button Margin="0,0,2,2" Grid.Row="0" Grid.Column="0" x:Name="cell00" />
    <Button Margin="2,0,2,2" Grid.Row="0" Grid.Column="1" x:Name="cell01" />
    <Button Margin="2,0,0,2" Grid.Row="0" Grid.Column="2" x:Name="cell02" />
    <Button Margin="0,2,2,2" Grid.Row="1" Grid.Column="0" x:Name="cell10" />
    <Button Margin="2,2,2,2" Grid.Row="1" Grid.Column="1" x:Name="cell11" />
    <Button Margin="2,2,0,2" Grid.Row="1" Grid.Column="2" x:Name="cell12" />
    <Button Margin="0,2,2,0" Grid.Row="2" Grid.Column="0" x:Name="cell20" />
    <Button Margin="2,2,2,0" Grid.Row="2" Grid.Column="1" x:Name="cell21" />
    <Button Margin="2,2,0,0" Grid.Row="2" Grid.Column="2" x:Name="cell22" />
  </Grid>
</Window>

This grid layout arranges a set of nine buttons in a 3× 3 grid of tic-tac-toe cells, using the margins on the button for the tic-tac-toe crosshatch. A simple implementation of the game logic in the XAML code-behind file looks like Example 5-2.

Example 5-2.   A simple tic-tac-toe implementation

// Window1.xaml.cs
...
namespace TicTacToe {
  public partial class Window1 : Window {
    // Track the current player (X or O)
    string currentPlayer;

    // Track the list of cells for finding a winner etc.
    Button[] cells;

    public Window1() {
      InitializeComponent();

    // Cache the list of buttons and handle their clicks
    this.cells = new Button[] { this.cell00, this.cell01, ... };
    foreach( Button cell in this.cells ) {
      cell.Click += cell_Click;
    }

    // Initialize a new game
    NewGame();

    }

    // Wrapper around the current player for future expansion,
    // e.g. updating status text with the current player
    string CurrentPlayer {
     
get { return this.currentPlayer; }
      set { this.currentPlayer = value; }
    }

    // Use the buttons to track game state
    void NewGame() {
     
foreach( Button cell in this.cells ) {
        cell.Content = null;
      }
     
CurrentPlayer = "X";
    }

    void cell_Click(object sender, RoutedEventArgs e) {
      Button button = (Button)sender;

      // Don't let multiple clicks change the player for a cell
      if( button.Content != null ) { return; }

      // Set button content
      button.Content = CurrentPlayer;

      // Check for winner or a tie
     
if( HasWon(this.currentPlayer) ) {
        MessageBox.Show("Winner!", "Game Over");
        NewGame();
        return;
     
}
     
else if( TieGame() ) {
        MessageBox.Show("No Winner!", "Game Over");
        NewGame();
        return;
     
}

      // Switch player
      if( CurrentPlayer == "X" ) {
       
CurrentPlayer = "O";
      }
      else {
       
CurrentPlayer = "X";
      }
   
}

    // Use this.cells to find a winner or a tie
    bool HasWon(string player) {...}
    bool TieGame() {...}
 
}
}

Our simple tic-tac-toe logic uses strings to represent the players and uses the buttons themselves to keep track of the game state. As each button is clicked, we set the content to the string indicating the current player and switch players. When the game is over, the content for each button is cleared. The middle of a game looks like Figure 5-1.


Figure 5-1.  A simple tic-tac-toe game

Notice in Figure 5-1 how the grid background comes through from the margin. These spacers almost make the grid look like a drawn tic-tac-toe board (although we’ll do better later). However, if we’re really looking to simulate a hand-drawn game, we’ve got to do something about the size of the font used on the buttons; it doesn’t match the thickness of the lines.

One way to fix this problem is by setting the size and weight for each of the Button objects, as in Example 5-3.

Example 5-3.   Setting control properties individually

<Button FontSize="32" FontWeight="Bold" ... x:Name="cell00" />
<Button FontSize="32" FontWeight="Bold"... x:Name="cell01" />
<Button FontSize="32" FontWeight="Bold"... x:Name="cell02" />
<Button FontSize="32" FontWeight="Bold"... x:Name="cell10" />
<Button FontSize="32" FontWeight="Bold"... x:Name="cell11" />
<Button FontSize="32" FontWeight="Bold"... x:Name="cell12" />
<Button FontSize="32" FontWeight="Bold"... x:Name="cell20" />
<Button FontSize="32" FontWeight="Bold"... x:Name="cell21" />
<Button FontSize="32" FontWeight="Bold"... x:Name="cell22" />

While this will make the X’s and O’s look better according to my visual sensibilities today, if I want to change it later, I’ve now committed myself to changing both properties in nine separate places, which is a duplication of effort that offends my coding sensibilities. I’d much prefer to refactor my decisions about the look of my tic-tac-toe cells into a common place for future maintenance. That’s where styles come in handy.

Inline Styles

Each “style-able” element in WPF has a Style property, which can be set inline using standard XAML property-element syntax (discussed in Chapter 1), as in Example 5-4.

Example 5-4.   Setting an inline style

<Button ... x:Name="cell00" /> 
  <Button.Style>
   
<Style> 
     
<Setter Property="Button.FontSize" Value="32" />
     
<Setter Property="Button.FontWeight" Value="Bold" />
   
</Style
>
  </Button.Style>
</Button>

Because we want to bundle two property values into our style, we have a Style element with twoSetter sub-elements, one for each property we want to set—i.e.,FontSizeandFontWeight—both with theButtonprefix to indicate the class that contains the property. Properties suitable for styling are dependency properties, which are described in Chapter 9.

Due to the extra style syntax and because inline styles can’t be shared across elements, inline styles actually involve more typing than just setting the properties. For this reason, inline styles aren’t used nearly as often as named styles.

Named Styles

By hoisting the same inline style into a resource (as introduced in Chapter 1), we can award it a name and use it by name in our button instances, as shown in Example 5-5.

Example 5-5.   Setting a named style

<!-- Window1.xaml -->
<Window ...>
  <Window.Resources>
   
<Style x:Key="CellTextStyle">
     
<Setter Property="Control.FontSize" Value="32" />
     
<Setter Property="Control.FontWeight" Value="Bold" />
   
</Style>
  </Window.Resources>
  ...
 
<Button Style="{StaticResource CellTextStyle}" ... x:Name="cell00" />
  ...
</Window>

In Example 5-5, we’ve used theControlprefix on our properties instead of theButtonprefix to allow the style to be used more broadly, as we’ll soon see.

The TargetType Attribute

As a convenience, if all of the properties can be set on a shared class, like Control in our example, we can promote the class prefix into theTargetType attribute and remove it from the name of the property, as in Example 5-6.

Example 5-6.   A target-typed style

<Style x:Key="CellTextStyle" TargetType="{x:Type Control}">
 
<Setter Property="FontSize" Value="32" />
 
<Setter Property="FontWeight" Value="Bold" />
</Style>

When providing aTargetTypeattribute, you can only set properties available on that type. If you’d like to expand to a greater set of properties down the inheritance tree, you can do so by using a more derived type, as in Example 5-7.

Example 5-7.   A more derived target-typed style

<Style x:Key="CellTextStyle" TargetType="{x:Type Button}">
 
<!-- IsCancel is a Button-specific property -->
 
<Setter Property="IsCancel" Value="False" />
 
<Setter Property="FontSize" Value="32" />
 
<Setter Property="FontWeight" Value="Bold" />
</Style>

In this case, theIsCancelproperty is only available onButton, so to set it, we need to switch theTargetTypeattribute for the style.

You may be wondering why I’m setting theFontSizeto"32"instead of"32pt"when the latter is more in line with how font sizes are specified and the two representations are definitely not equivalent (the former is pixels, while the latter is points). I’m using pixels because as of this writing, WPF styles using a non-prefixed property allow"32pt"to be specified forFontSize, while prefixed properties do not. For example, the following works (assuming aTargetTypeis set):

  <Setter Property="FontSize" Value="32pt" />

whereas the following does not (regardless of whether aTargetTypeis set or not):

  <Setter Property="Control.FontSize" Value="32pt" />

Hopefully this problem will have been fixed by the time you read this (and not replaced with others).

Reusing Styles

In addition to saving you from typing out the name of the class prefix for every property name, the TargetTypeattribute will also check that all classes that have the style applied are an instance of that type (or derived type). What that means is that if we leaveTargetTypeset toControl, we can apply it to aButtonelement, but not to aTextBlockelement, as the former derives ultimately fromControl but the latter does not.

On the other hand, whileControlandTextBlockboth share the common ancestorFrameworkElement, theFrameworkElementclass doesn’t define aFontSizedependency property, so a style with aTargetTypeofFrameworkElementwon’t let us set theFontSizeproperty because its not there, despite the fact that bothControlandTextBlockhave aFontSizeproperty.

Even with theTargetTypeset toControl, we gain a measure of reuse of our style across classes that derive fromControl—e.g.,Button,Label,Window, etc. However, if we drop theTargetTypeattribute from the style altogether, we gain a measure of reuse of styles across controls that don’t have a common base but share a dependency-property implementation. In my experimentation, I’ve found that dependency properties that share the same name across classes, such asControl.FontSizeandTextBlock.FontSize, also share an implementation. What that means is that even thoughControlandTextBlockeach define their ownFontSizeproperty, at runtime they share the implementation of this property, so I can write code like Example 5-8.

Example 5-8.   Reusing a style between different element types

...
<Style x:Key="CellTextStyle">
 
<Setter Property="Control.FontSize" Value="32" />
</Style>
...
<!-- derives from Control -->
<Button Style="{StaticResource CellTextStyle}" ... />

<!-- does *not* derive from Control -->
<TextBlock Style="{StaticResource CellTextStyle}" ... />
...

In Example 5-8, I’ve dropped theTargetType attribute from the style definition, using instead the class prefix on each property the style sets. This style can be applied to aButtonelement, as you’d expect, but can also be applied to aTextBlock control, with theFontSizeset as specified by the style. The reason this works is that both theButton, which gets itsFontSizedependency property definition from the Control class, and theTextBlock, which provides it’s ownFontSizedependency property definition, share theFontSizedependency property implementation with theTextElementclass. Figure 5-2 shows the relationship of elements to their dependency-property implementations.

As Figure 5-2 shows, if we wanted to, we could redefine our style in terms of theTextElementclass, even though it falls into the inheritance tree of neitherControlnorTextBlock, as in Example 5-9.


Figure 5-2.  Elements and dependency properties

Example 5-9. Depending on the implementation of dependency properties

<Style x:Key="CellTextStyle">
 
<Setter Property="TextElement.FontSize" Value="32" />
</Style>
...
<Button Style="{StaticResource CellTextStyle}" ... />
<TextBlock Style="{StaticResource CellTextStyle}" ... />

Taking this further, if we’d like to define a style that contains properties not shared by every element we apply them to, we can do that, too, as in Example 5-10.

Example 5-10.   Styles can have properties that targets don’t have

<Style x:Key="CellTextStyle">
  <Setter Property="TextElement.FontSize" Value="32" />
  <Setter Property="Button.IsCancel" Value="False" />
</Style>
...
<!-- has an IsCancel property --> <Button Style="{StaticResource CellTextStyle}" ... />

<!-- does *not* have an IsCancel property -->
<TextBlock Style="{StaticResource CellTextStyle}" ... />

In Example 5-10, we’ve added theButton.IsCancelproperty to theCellTextStyle and applied it to theButtonelement, which has this property, and theTextBlockelement, which doesn’t. This is OK. At runtime, WPF will apply the dependency properties that exist on the elements that have them and silently swallow the ones that aren’t present.

Named Styles

WPF’s ability to apply styles to objects that don’t have all of the properties defined in the style is analogous to applying the Word Normal style, which includes a font size property of its own, to both a range of text and an image. Even though Word knows that images don’t have a font size, it applies the portions of the Normal style that do make sense (such as the justification property), ignoring the rest.

Getting back to our sample, we can use theCellTextStyleon aTextBlockin a new row to show whose turn it is, as in Example 5-11.

Example 5-11.   Applying a style to Button and TextBlock elements

<Window.Resources>
 
<Style x:Key="CellTextStyle">
    <Setter Property="TextElement.FontSize" Value="32" />
    <Setter Property="TextElement.FontWeight" Value="Bold" />
 
</Style>
</Window.Resources>
<Grid Background="Black">
  
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
   
<RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
 
<Grid.ColumnDefinitions> 
    <ColumnDefinition /> 
    <ColumnDefinition />
    <ColumnDefinition />

  </Grid.ColumnDefinitions>
  <Button Style="{StaticResource CellTextStyle}" ... />
  ...
  <TextBlock
   
Style="{StaticResource CellTextStyle}"
    Foreground="White"
   
Grid.Row="3"
    Grid.ColumnSpan="3"
    x:Name="statusTextBlock" />
</Grid>
</Window>

This reuse of the style across controls of different types gives me a consistent look in my application, as shown in Figure 5-3.

One thing you’ll notice is that the status text in Figure 5-3 is white, while the text in the buttons is black. Since black is the default text color, if we want the status text to show up against a black background, we have to change the color to something else, hence the need to set theForegroundproperty to white on theTextBlock. Setting per-instance properties works just fine in combination with the style, and you can
combine the two techniques of setting property values as you see fit.


Figure 5-3.  A tic-tac-toe game with style

 

Please check back next week for the continuation of this article.

blog comments powered by Disqus
XML ARTICLES

- More on Triggers and Styles and Control Temp...
- Looking at Triggers with Styles and Control ...
- A Closer Look at Styles and Control Templates
- Styles and Control Templates
- Properties and More in XAML
- Elements and Attributes in XAML
- XAML in a Nutshell
- Importing XML Files into Access 2007
- Using MSXML3.0 with VB 6.0
- MSXML, concluded
- MSXML, continued
- MSXML Tutorial
- Generating XML Schema Dynamically Using VB.N...
- XSL Transformations using ASP.NET
- Applying XSLT to XML Using ASP.NET

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 8 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials