黑夜的狼

沮丧吗,那就是一种无病呻吟!留恋它就是一种高度近视!目光应该放得更远一点! 别不想飞,只是要一步跨过太平洋!

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

Themes for Silverlight Applications

A theming system for Silverlight 2 apps... this post steps you through creating a set of theme assets, adding customizability of themes (colors and fonts in the sample), and using them in an application, including selecting a theme dynamically... complete with source code.

Silverlight provides the ability to completely customize the look and feel of controls (including the intrinsic ones - often something one desires when working with HTML), while preserving their behavior, through a combination of styles, templates, visual states and transitions. For example, the screen shot below (click to run), shows the stock look replaced with a sketch or napkin look and feel (based on Corrina's (our resident designer) rough skin sample).

Red, Blue, Green Sketch controls vs. Stock Silverlight controls (Click to Run)

The general approach to defining app-wide customizations in Silverlight today is to define a bunch of named styles in the Resources dictionary of the Application's XAML markup, and then reference them via the StaticResource markup extension elsewhere in the UI. However this approach has some shortcomings, that HTML designers have long overcome through the use of separate and multiple style sheets.

  • Application.Resources become unwieldy. Styling just a few controls can quickly lead to 1000's of lines of XAML. Simply navigating and finding what you're looking for becomes a chore. The first thing I do when I pick up a theme created by a designer is try to separate out the set of individual styles per control.
  • Styles get mixed up with functionality. To me (speaking from my developer mindset), Application.Resources is better suited for global data and other behavioral aspects of my application shared across the application, rather than serving as a container for visual and presentation items. Hence my desire for separating these out, so I can just import a designer's creation into my app.
  • Styles are not easily swappable. This is also partly because styles are mixed up in an application's resources. If I want to switch from say a Flat look to a Sketch look in my app, I have to carefully swap things in and out, track dependencies between styles etc.
  • Styles cannot be dynamically selected. I'd like to create an application that can dynamically pick a different font/color scheme, i.e. have the notion of themes, perhaps based on a user's profile. This requires some level style merging as well as the ability to include multiple themes separately in the xap package and pick one of them dynamically.

 

There are probably a few other issues that came up in my discussion with Corrina but suffice to say, these stem from placing styles statically in Application.Resources. Designers working on HTML have long overcome these issues through the use of separate and multiple style sheets that help separate content and behavior from presentation.

I started to think about how I might go about creating a more concrete theming system for Silverlight apps, and naturally I looked to CSS as well as what we had done with Themes in ASP.NET (the App_Themes folder pattern) to see how much I could re-use here, while still feeling natural in the context of Silverlight, and reasonably designable via Expression Blend. The end result was a very small theming feature addition to on-going Silverlight.FX framework.

In the rest of the post, I'll describe how to go about creating the themes above and consuming them in the application.

I've included the code for the theming feature along with the rest of Silverlight.FX with the sample app for you to download and use.

Creating the Theme Assets
The first step is creating the custom control looks, using Expression Blend and the goodness that is Silverlight styles, templates, storyboards and visual state manager. I'll be using Expression Blend for the most part for this part of the work (putting on my virtual designer hat for a moment).

An asset in a theme is simply a user control. At runtime, the Resources defined within the user control are extracted into the application. The reason I chose the user control approach is to allow a design experience in Blend. Furthermore, you can place test controls within the user control that provide an interactive design experience for the styles.

To create the custom sketch look for the Button, simply create a new user control, in a file named Button.xaml for example, and add a Button into it. Right click on the Button, and choose to edit its template. At this point you can define the overall look. I won't go into this step by step - check out Tim's blog post on the VisualStateManager for an overview of the design process.

Building the Button asset in Expression Blend

Tip: You don't need an actual code-behind class when creating an asset. When you add a xaml file in Blend, simply uncheck the "Include Code File" checkbox. Blend will actually have an error opening the file in design view when you do this. To fix that, simply add x:Class="UserControl" to your <UserControl> tag in source view. This effectively sets the code-behind class to be the existing UserControl class from the framework.

You can repeat this process for any other assets you need to create. For my particular sample, I defined styles for TextBox and CheckBox as well. Note that if you want to, you can combine styles for multiple controls in the same xaml file. I created them separate, because I prefer to keep each xaml file small and simple.

Making Assets Themeable
The next step is to enable customizability of the asset from a theme. This is optional... depends if you need or want to support the ability to have multiple themes or variants of the same visual structure. For example, you might want the same overall button look, but create multiple variants of the sketch button that differ in terms of colors and fonts.

The goal is to enable this without requiring duplication of the entire style in each theme. The approach we'll take is creating named values for numbers, colors, strings that are then referenced in the style. I'll switch into XAML view in the designer and add the following resources within my asset.

 

<UserControl
  ...
  xmlns:sys="clr-namespace:System;assembly=mscorlib">

  <UserControl.Resources>

    <sys:Double x:Key="lineThickness">2</sys:Double>
    <Color x:Key="lineColor">#000000</Color>
    <Color x:Key="fillColor">#CAF562</Color>
    <Color x:Key="fontColor">#000000</Color>
    <SolidColorBrush x:Key="fontBrush" Color="{StaticResource fontColor}" />
    <sys:String x:Key="fontFamily">Verdana</sys:String>
    <sys:Int32 x:Key="fontSize">11</sys:Int32>
    <sys:String x:Key="fontWeight">Bold</sys:String>
    ...
  </UserControl.Resources>
</UserControl>

 

These resources provide defaults for various aspects of the asset. To complete the work of creating the asset, I'll refer to them within the button's style. Specifically, I'll replace the literal constant values with references to the named resources.

 

<Style x:Key="sketchButton" TargetType="Button">
  <Setter Property="Foreground" Value="{StaticResource fontBrush}"  
  <Setter Property="FontFamily" Value="{StaticResource fontFamily}"  
  <Setter Property="FontWeight" Value="{StaticResource fontWeight}"  
  <Setter Property="FontSize" Value="{StaticResource fontSize}" 
  ...
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="Button">
        <Grid x:Name="RootElement">
          ...
          <Path x:Name="LineElement" StrokeThickness="{StaticResource lineThickness}" ...>
            <Path.Stroke>
              <SolidColorBrush Color="{StaticResource lineColor}" />
            </Path.Stroke>
          </Path>
          <Path x:Name="FillElement" ...>
            <Path.Fill>
              <SolidColorBrush Color="{StaticResource fillColor}" />
            </Path.Fill>
          </Path>
          ...
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
/>/>/>/>

 

The design view in Blend continues to work, in that the resources are resolved just fine at design-time.

Creating Themes
Now it is time to use the assets to define an actual theme. A theme can contain theme-specific resources and link in specific assets through an include mechanism. It can also override any default values for the variables we just created.

In my sample, I created Red, Blue and Green themes. Each theme is defined in a file named Theme.xaml, and placed in a folder whose name represents the name of the theme. For example, Theme.xaml in the Blue folder is defined as follows (note this also shows an embedded font sample):

 

<fxa:Theme
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:fxa="clr-namespace:Silverlight.FX.Applications;assembly=Silverlight.FX"
  xmlns:sys="clr-namespace:System;assembly=mscorlib"
  Includes="Button, CheckBox, TextBox"

  <fxa:Theme.Resources>

    <Color x:Key="fillColor">#7FCAFF</Color>
    <sys:String x:Key="fontFamily">/Assets/Architect.ttf#Architect</sys:String>
    <sys:Int32 x:Key="fontSize">16</sys:Int32>
    <sys:String x:Key="fontWeight">Normal</sys:String>

  </fxa:Theme.Resources>

</fxa:Theme>
>

 

The Theme class is from my framework. It derives from UserControl, and adds the Includes property. At runtime, the theme processor looks for each named include within the theme directory, and if it is not found, it looks up one directory for a shared asset (more on the directory structure below).

As you can see in the XAML above, the Blue theme is overriding the default fillColor, and font information, as well as including a reference to an embedded font.

Adding Themes to the ApplicationUsing the Themes
Once I've created the assets and themes, it is now time to consume them in an application. Imagine the designer handing these off to the developer, as I get back into developer mode.

At runtime, the theme feature expects to find all the themes placed within a "Themes" folder within the application's xap package. I'll simply add the folder to my application, and make sure the Build action for each XAML is set to "Content" (so it gets included into the xap), and remove any custom build tool associated with the xaml files (since I don't want any code-behind to be generated).

Selecting a Theme
The final step is to select a theme, given I have three in the application. Here is what I have in my App.xaml file:

 

<fxa:XApplication ...
  xmlns:fxa="clr-namespace:Silverlight.FX.Applications;assembly=Silverlight.FX"
  ThemeName="Green" WindowName="Page">
</fxa:XApplication>

 

XApplication is a derived Application type defined within the Silverlight.FX framework. It has various useful features. The two shown here are the ThemeName property and the WindowName property.

The application loads up the specified theme, and then loads up the specified window as the root visual of the application. Incidently, there is nothing in the code-behind for the application. All of this is declarative.

Selecting a Theme Dynamically
Given I have multiple themes, I want to pick one based on some external variable. For example, this might be based on the user's preferences. XApplication supports this scenario through the use of Silverlight's initParams.

 

<fxa:XApplication ...
  xmlns:fxa="clr-namespace:Silverlight.FX.Applications;assembly=Silverlight.FX"
  ThemeName="$theme|Green" WindowName="Page">
</fxa:XApplication>

 

Basically this says, lookup the "theme" variable in initParams, and if its found, use its value. Otherwise use the "Green" theme as the default.

If you are using ASP.NET, you might be storing the user's preference via the Profile feature, and you can use it to set the initParams when rendering out the Silverlight object tag.

 

<object style="width: 290px; height: 100px" type="application/x-silverlight">
  <param name="source" value="ThemeSample.xap" />
  <param name="version" value="2.0" />
  <param name="initParams" value="theme=<%= Profile.Theme %>" />
</object>

 

How it Works?
You're probably curious about how the system overall works, so here is a summary of the mechanics. However, you do not need to have an in-depth understanding of the framework implementation to use the feature.

The XApplication application class loads the specified theme during its Startup event handling, and then loads the root visual. This is important as the theme must be loaded before the first visual is created, so that any resource references can be resolved.

The theme processor is responsible for a few things. It looks for the right Theme.xaml file and loads that in the xap. It then parses the XML to extract out the resources, as well as the list of includes, and then recursively parses the XML in those included assets to extract out resources. As it is extracting the resources, it merges them into a single XML stream consisting of unique keys (i.e. keys in Theme.xaml override the same keys in assets - this allows the overriding).

The merged XML stream is wrapped in a tag, and parsed via XamlReader.Load. The theme processor pulls out the resulting resources using the keys it accumulated during the XML parsing, and adds them into Application.Resources.

Finally when visual elements are created, and they have a StaticResource reference to something in the theme, those resource references get resolved to the resource that was added into Application.Resources. Basically, once the theme processor is done with its job, it is out of the picture, and the normal Silverlight resource lookup just works.

Summary
The thing I like most about this approach is that it allows for themes to be separated from the core application, can be designed independently, and just dropped in into the application, and requires minimal changes to the model to incorporate into the application. Now I just wish Silverlight had implicit styles, so I could also get rid of all the StaticResource references to named styles...

 

copy from:http://www.nikhilk.net/Silverlight-Themes.aspx

posted on 2011-01-19 23:38  anncesky  阅读(324)  评论(0编辑  收藏  举报