hen Microsoft set about building the Windows Presentation Foundation, one core goal was to create an environment for applications capable of both two-dimensional and three-dimensional content simultaneously. Using a traditional approach, this would have led to an abstract Button class followed by Button2D and Button3D subclasses that would override the painting rules. Tearing a page from the CSS-based Internet, Microsoft also wanted the look and feel of all controls to be controllable via styles that, when updated, would in turn update the look and feel of every component of that type in the application.
Microsoft’s solution is called “Lookless” controls. A Lookless control is a control that defines the behavior of a control without regard to how it will look when rendered. For example, a button is a control that has a normal state, a mouseover state and a clicked state. The appearance of the button could be anything?from the familiar grey box to an obnoxiously spinning 3D flaming corporate logo circa web design 1997 style.
All WPF controls implement a default look?typically the same as the corresponding last generation Windows Forms control. In this article you’ll see how to take that ugly grey button and transform it into a glass-effect pill button for a drug information application.
XAML styling for lookless controls owes a lot of its heritage to HTML and CSS, and you can apply styling in similar ways. For example, you can define the look for all buttons in an application, restrict it to all buttons on a specific screen, to a designated group of buttons, or to apply a special style for just one button. To do that, XAML allows you to define the look of your components at several levels. In terms of actual implementation, styles are applied to components and are then available to sub-components. Logically, they can be applied to an individual component, a page or window or in an external file that can be shared by multiple pages, windows, or applications.
Each option has its place: from one-offs, to page-specific items, to styles you want applied universally across your application. When more than one style could potentially apply to the same element, the XAML renderer applies styles in order of precedence: Inline –> Component –> External File. That’s the same way CSS applies styles to elements. Generally, “closer” styles take precedence over styles defined further away from elements.
You can apply inline styles directly to an element simply by adding the style dependency property to it:
For small projects or when you’re in the initial development phases, you will most likely define your styles in the
To define your styles in a separate file, create a XAML file with a root element of
It’s worth noting that if you use an external XAML file on the currently released version of WPF for Visual Studio 2005, any attempt to use the designer preview will error out complaining that, “Value ‘ResourcesCommon.xaml’ cannot be assigned to the property ‘Source’.” even though the syntax is correct and will work if you run the application. Due to this bug, you should build your styles in the resources section of the page until you get them the way you want them, and only then move them to an external resource file.
You can apply styles to all components of a given type, and any component can reference a named style. The difference between the two is set in the style definition itself. If the style tag defines an x:Key then that style will only be applied to components that specifically reference that style.
The example below shows the declaration of a named style referenced by a button. The style is defined in the Windows.Resources section and the Button itself is somewhere in the main content area of the page:
|Figure 1. The PillButton: The figure shows several buttons using the PillButton style, complete with reflection.
By not defining an x:Key, your style will be applied to all components on the page that match the TargetType attribute value. In the following example, all buttons on the page automatically get the style defined applied to them:
Figure 1 shows three buttons defined using this style in their default state.
Building the Template
The core of overriding the default look of a WPF component is to create a Template setter as shown in the snippet below. For a complete listing of the components look, see Common.xaml which you can find in both the downloadable source file and in Listing 1:
It’s important to note that you need to define the TargetType in your style property in order for the proper dependency properties to be available. Because different event triggers are available for different types of components, this property helps determine what events and options are available as setters. If the syntax of this style property looks strange to you, you might want to take a look at the sidebar WPF Oddities.
Once inside the ControlTemplate tag, you’re free to put any kind of component or container. It expects only one component, so to do something more complex, your one component will need to be a layout component that can contain more sub-components, such as a Grid or StackPanel. The code example included with this article uses a Border component and a few sub-border components, one of which contains a StackPanel to hold multiple child components inside of a single Border.
Inside your template, one important tag is the ContentPresenter, which is responsible for displaying the sub-component provided to the usage of your template. For a button, this is typically the text label provided to the button via the Text attribute:
The ContentPresenter itself supports a number of dependency properties that allow you to customize how you want sub-components to be handled. For most types of components, it’s enough to just include an empty ContentPresenter tag, just to get the included text rendered.
Responding to Events
|Figure 2. Mouseover Effect Applied: In this figure, the “Eyes” button has been clicked, and the mouseover effect has added a shadow at the top and the shine at the bottom.
Most components include some form of interactivity?and any form of interactivity requires that something happen with the component to let the user know that the component has accepted their input. The code example included provides support only for a mouseover effect. When the mouseover effect is applied it adds the shadow at the top of the button and the shine at the bottom to create the look of a slight depression in the button area. Figure 2 illustrates the mouse-over state of the “Eyes” button:
You don’t have to create the inverse state when working with events in XAML. For example, the included code provides setters that set the borders to use different gradients to invert the direction of the highlight and shadow on the PillButton if IsMouseOver=True. You don’t have to define an event for IsMouseOver=False to return these to a normal state.
If your component contains a multiple components as the PillButton does, you can address each of these items to change different style properties on the elements by setting the TargetName. When defining the component’s template, each component you define will need to have an x:Name attribute set if you want to be able to modify this element in your event triggers. In the setters of your event trigger, the TargetName correlates to the x:Name of the component defined in the template.
Each component type has a multitude of events?many are unique to that particular component type. For some help locating what events are supported by a given WPF component, take a look at the What Events Can I Respond To?
Achieving the Glass Look
The reflection is courtesy of a rectangle placed below the pill itself that applies a few filters. This rectangle is automatically sized to match the pill via the binding reference:
Within the rectangle, you can use a VisualBrush object that uses another component as a source. In this case, the code uses the pill button as the visual source. It then applies a group of filters chained together that invert the image (mirror), apply a gradient transparency to it (to make it fade), and apply a blur effect. The combined result of these three filters on the rendered result of the pill button is the coveted glass effect shown in Figure 1.
Because all this magic is done in real time using full binding, if you animate the pill the reflection will move and resize with the pill.
Lookless controls provide a powerful and flexible way to define application look and feel while cleanly separating visual style from behavior. For further exploration, look into styling other types of WPF controls. Each control has unique properties and approaches that you’ll need to explore to figure out how to take each control to its limits?and beyond.