Free!  FoxPro to WPF screen converter
   Almost free!  One day of FREE VFP to WPF evaluation of your FoxPro app ONSITE
   Free!  Read the foreword to Les' new book (available here)...
Skip Navigation Links

Using Styles in WPF

If you''ve always wanted CSS for WinForms, this is for you


Les Pinter

Login or register to download source code

Coding time: 20 minutes.   

Jump to:   Styles 101   Inheritance   Triggers   Templates   Raising the Bar   Conclusion 

     


Introduction

Windows Presentation Foundation (WPF) is Microsoft's replacement for the WinForms designer. You can use it to give a face-lift to your Winforms applications, or to build new ones. In either case, you're in for a treat.

DataBinding is a little different in WPF. You have to return data from whatever data store and load it into your data object (a DataSet, for example), then assign the object as the DataContext of the window or control. WPF (actually reflection) then takes care of finding the current record and binding its columns to your controls.

Specifically, an attribute called Binding is used, like this:

   <TextBox ... Text="{Binding Path=CompanyName}" ... />

The big difference is screen layout. WinForms screens are actually rendered by the IDE by reading the designer code-behind. Change something on the screen, and the designer code changes; Change the Designer code, and the screen changes.

XAML (EXtended Application Markup Language) is what WPF and SilverLight use instead. XAML is a text format from the XML lineage that maps your screen's elements to parts of the .NET framework. In fact, you could do everything that XAML does in code. But it would be a pain in the neck. So a textbox is created on the screen if your xaml contains a <TextBox> tag. To make it 100px wide, use the attribute setting Width="100". Et cetera.

One big difference is that, instead of absolute positioning (X,Y coordinates), you generally will use containers in xaml. There are seven:

  • Grid
  • FlowPanel
  • StackPanel
  • Window
  • Canvas
  • WrapPanel
  • TabControl

You can combine these in many ways, nesting one inside the other to create the exact screen look and behavior that you need. You might, for example, start with a two-column grid, placing a stackpanel in the left column and a flowpanel in the second column. By making the left stackpanel non-resizable and locating your fixed content there, and then putting more flexible elements in the flowpanel on the right, you create a screen that resizes in a purposeful way.

Expression Blend is a new Microsoft product for designing WPF markup, and it does a better job than the Visual Studio 2008 IDE does. VS 2010, now a Release Candidate, does a better job, but it's still a work in progress. But you can do quite a lot in Visual Studio, and that's what we'll use today.

One shortcoming of WinForms controls is that, in order to apply styles to them, you essentially had to create a hierarchy of subclassed controls, then apply styling at each subclass. It was a lot of work, and had to be done in code. WPF allows you to use Styles, which can be applied at the control, form or application level.

Styles perform the function that Cascading Style Sheets do in ASP.NET applications, except even better. Not only can you set properties like color, size and font family; you can attach behaviors, call event handlers and pass them parameters, and do things that you never imagined you could do without writing a boatload of code.

In this article, you'll learn how to style WPF applications in ways that give you complete control over the look and feel of your application. And you'll learn some new tricks that will put smiles on your users' faces.


Styles 101
top

First, we'll need a WPF project. From the IDE menu, select File, New, Project, then select either VB or C#, and from the Windows templates group select WPF Application, supplying the project name Styles (Fig. 1):


After a few seconds you'll have a project that looks like Fig. 2 in the Solution Explorer:


The generated Window contains a grid, which is one of five containers you can use. Note that this isn't a grid as in DataGridView; it's just a container.

Use Ctrl+Alt+X to open the WPF Toolbox and drag a TextBox onto the window. Press F4 to open the Properties Window. With the TextBox selected, set the FontFamily property to Courier New and the FontSize to 12, and note the changes to the XAML (Fig. 3). Press F5 to see how the textbox styles your input.


This is how styles are applied to each individual control. If you use this method to style all of your controls, changing all of the TextBoxes to a different font family, size or color would be a real chore.

Styles to the rescue

Trim the FontFamily and FontSize attributes out of the TextBox xaml, then add the following to the top of the Window1 file, just before the <Grid> tag:

<Window.Resources>
   <Style TargetType={x:Type TextBox}">
      <Setter Property="FontFamily" Value="Courier New" >
      <Setter Property="FontSize" Value="12" >
   <Style>
</Window.Resources>

Press F5 and type into the textbox, and you'll see that the effect is exactly the same as it was when the style was applied within the TextBox tag.

However, this wouldn't be all that helpful if you had to apply it to each window in your 400-window application. Luckily, it can be applied at the Application level just as easily. Snip out the <Window.Resoures> tags, then cut the <Style> block of the window and paste it into the <Application Resources >block at the top of App.xaml:

<Application.Resources>
   <Style TargetType={x:Type TextBox}">
      <Setter Property="FontFamily" Value="Courier New" >
      <Setter Property="FontSize" Value="12" >
   <Style>
</Application.Resources>

Press F5, and voilĂ , the style is yet again applied to the textbox in Window1 - only now, it will be applied to every textbox in every screen in your application!

For most of us, this is where styles belong - not in a separate resource dictionary file, and not in the individual windows and usercontrols.

Basic control styles

You should start with a base set of styles for all of your controls. Copy the following to a new app.xaml (Application.xaml if you're using VB):

    <Application.Resources>

    <Style TargetType="{x:Type TextBox}">
      <Setter Property="FontFamily" Value="Courier New" />
      <Setter Property="FontSize"   Value="12" />
      <Setter Property="HorizontalAlignment"   Value="Left" />
      <Setter Property="VerticalAlignment"   Value="Center" />
    </Style>

    <Style TargetType="{x:Type Label}">
      <Setter Property="FontFamily" Value="Comic Sans MS" />
      <Setter Property="FontSize"   Value="11" />
      <Setter Property="Foreground" Value="Blue" />
      <Setter Property="HorizontalAlignment" Value="Right" />
      <Setter Property="VerticalAlignment"   Value="Center" />
    </Style>

    <Style TargetType="{x:Type Grid}">
      <Setter Property="Background" Value="LightBlue" />
    </Style>

    <Style TargetType="{x:Type Window}">
      <Setter Property="Background" Value="LightBlue" />
    </Style>

  </Application.Resources>

The HorizontalAlignment and VerticalAlignment settings are optimized for a two-column grid, which is a good way to present a single record. I've added a column divider and five row dividers, below, by clicking on the grid borders, first at the top middle to add the columndefinition, then along the left margin five times to add the RowDefinitions. I manually corrected the Width and Height values in the xaml:


This allows you to use Grid.Row and Grid.Column values to position labels and textboxes within the grid in such a way that resizing doesn't damage your layout (Fig. 5):


NOTE: In order for these textboxes to display data and interact with a data source, you would have to return data into a conformable data store, i.e. one having one public property for each column in the table (the columns named in the Path= statements, and then assign that datasource to the form's DataContext. We'll deal with WPF databinding in many upcoming articles, but it's not part of the scope for this article.


Inheritance
top

You'll probably want to have several types of labels, some larger than others, but all sharing some common properties - FontFamily, for example. The BasedOn attribute, used with the x:Key attribute in the inherited style, is how this is done. Here's an example using labels:

    <Style TargetType="{x:Type Label}" x:Key="BasicLabel" >
      <Setter Property="FontFamily" Value="Comic Sans MS" />
      <Setter Property="FontSize"   Value="11" />
      <Setter Property="Foreground" Value="Blue" />
      <Setter Property="HorizontalAlignment" Value="Right" />
      <Setter Property="VerticalAlignment"   Value="Center" />
    </Style>

    <Style TargetType="{x:Type Label} BasedOn="{StaticResource BasicLabel}" x:Key="Title" ">
      <Setter Property="FontFamily" Value="Comic Sans MS" />
      <Setter Property="FontSize"   Value="22" />
      <Setter Property="Foreground" Value="White" />
      <Setter Property="Background" Value="Red" />
    </Style>

In order to show how this works, I will need to make room above the grid. This will require an outer container, and a StackPanel is as good as any. So, I preceded the Grid tag with this:

<StackPanel>
<lLabel Content="Customers" Width=500 Style="{StaticResource Title}" />

and of course added </StackPanel> after the closing </Grid> tag. The result was this:


What happened to the labels? They're no longer right-aligned, and in general aren't using the default style? Here's why: Now that the Label style has a Key, it's no longer used as the default label style! Happily, there's an easy fix: Just add this in Application.xaml:

  <Style TargetType="{x:Type Label}" BasedOn="{StaticResource BasicLabel}" />

Since it's BasedOn the original label style and doesn't have an x:Key value, it becomes the default style. Note that you could also include Style="{StaticResource BasicLabel}" in every label, but that's more work that we have to do. Just make sure to leave one style unnamed for each control type, and it will be used as the default style for that control type.

Of course, you can still override any property of any object, whether it has a default style or a named style applied to it; just select the object and either modify its XAML or make the change in the Properties Window.


Triggers
top

The attributes exposed in xaml can do more than just set values for properties. The Trigger mechanism lets you assign properties upon raising a particular event. This can give you some visually interesting effects, and in some cases do things you might have thought would require some code.

For example, modify the TextBox style as follows:

    <Style TargetType="{x:Type TextBox}">
      <Setter Property="FontFamily" Value="Courier New" />
      <Setter Property="FontSize"   Value="12" />
      <Setter Property="HorizontalAlignment" Value="Left" />
      <Setter Property="VerticalAlignment" Value="Center" />
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Background" Value="Yellow"/>
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
          <Setter Property="Background" Value="Blue"/>
          <Setter Property="Foreground" Value="White" />
          <Setter Property="BorderThickness" Value="2" />
        </Trigger>
      </Style.Triggers>
    </Style>

Now, moving the mouse cursor over a textbox turns the background yellow; and, as the user tabs through textboxes, the textbox that has the focus displays white letters on a blue background. And all without a single line of code! (Fig. 7)


Event Triggers

You can also use a trigger to momentarily make the button that's under the mouse cursor change size. However, the Trigger mechanism doesn't quite get you there; you have to use a RoutedEvent, and the mechanism is slightly more involved. This also invokes a StoryBoard element, which informs animation of your controls. Click below to see an example (Listing 1):


The resulting visual effect is interesting, and it will be applied to all Buttons in all of your forms with - all together now - no code (Fig. 8).


NOTE: I added a Button between the </Grid> and the the </StackPanel> tags to demonstrate this style, and to my surprise, it produced an error when it ran. By experimenting, I discovered that I had to take the Width and Height settings off of the Button and add them as Setters in the Button Style in app.xaml in order for it to work. I'm sure there's a reason for this, but I hope it won't work this way in the next version.


Templates
top

Another way to apply styles is by way of Data Triggers and Data Templates. A Data Trigger invokes one or more setters based on a condition, e.g. highlight customers located in the United States in red, A Data Template specifies the layout for a group of controls. They're often used together, as the next example will show. (Note: This example is located in the DataTemplateDemo solution in the sample code for this article.)

I want to display the Company Name, City and Country of the customers in the NorthWind Customers table, highlighting US customers in white letters on a red background. Fig. 9 shows what I want to see:


I'll use a ListBox with three columns. I'll need to load the NorthWind customers table into a dataset, then assign the Customers DataTable in the dataset as the DataContext for the ListBox.

Create a new WPF project, changing the name of Window1 to MainWindow by right-clicking on the Window1.xaml, selecting Rename, and changing it to MainWindow. All internal references to Window1, including the StartupUri in app.xaml, will be changed as well.

To add the dataset, right-click on the project, select Add, Item, and select DataSet from the available items, naming it Dataset1. Create a NorthWind Data Connection and select the Customers table to generate the dataset.

The codebehind for MainWindow appears below in Listing 2 (change the ConnectionString to refer to your instance of SQL Server):


And here's the XAML for MainWindow (Listing 3):


The MainWindow xaml code is where most of the story is told. The MyListBox object is created in response to the <ListBox> tag, which uses the Name attribute to assign it a name. The CustomersData is applied because of the ItemsSource attribute assignment. The CustItemTemplate is used to format ListBoxItems because of the ItemTemplate attribute of the ListBox. And the white-on-red coloring of customers in the USA is applied because it's the default style for ListBoxItems (<Style TargetType="{x:Type ListBoxItem}">). I applied these styles in the window because it's the only place they're used; they could have just as easily been located in app.xaml, but since they apply only to this window's ListBox, locating them here makes more sense.

This is a simple example, but I think it gives a good picture of what the interaction of DataTemplates and ListBoxItems styles can accomplish.


Raising the Bar
top

For most of us, the simple use of Setters to set properties, and the occasional use of a Trigger, will be good enough. However, I can't begin to describe to you the variety of controls that you can build. It's just a matter of time, talent and inclination.

There are many, many impressive WPF control examples to be found on the Internet, and by way of example, I wanted to showcase one of the more impressive ones I've found. Ron Eisenberg at DevLicio.us has published a beautiful glowing mouseover button. The GlowButton can be seen in Fig. 10:


Expand the code listing below if you want to see the code, but clearly, this is a job for cutting and pasting. I can only imagine how long it took to design this effect. The xaml is in Window2 in the sample app; to run it, change the StartURI in Application.xaml from Window1.xaml to Window2.xaml:



Conclusion
top

Clearly, WPF styles provide a simple and powerful tool to help you manage your UI that has been lacking in the WinForms universe. However, the subtext of what I've shown you above is that casual stylists, and by that I mean people whose main job is to return data from SQL Server and pass it through forms, don't have the time or inclination to come up with works of art like these.

That's why Josh Smith, the principal guru of WPF, has said that the first thing you need to do is to hire a graphic artist and let him or her build the UI. The interface has finally been decoupled from the implementation, and there's a full-time job taking care of each of them. But you can at least whet the appetites of your users with styling. Then, get them to lobby the controller for the money to hire a full-time XAML designer.

qqq


Copyright(C) Pinter Consulting, 2012Tel: +1 (650) 464-6924
Automated conversion between C# and VB by Visible C#/Visible VB from Tangible Software Solutions