A Quick Tour of Themes in ASP.NET 2.0
Code download available at:CuttingEdge0511.exe (116 KB)
Special Themes Features
Summing It Up
It's much easier to build a rich user interface into your Web application in ASP.NET 2.0 than it was in previous versions. Master Pages let you build pages based on existing templates of markup and code. ASP.NET 2.0 wizards make implementing navigation easier. In addition, you can add personalization to ASP.NET 2.0 pages thanks to features like the user profile that I covered last month. This month I'll take a quick tour through another feature that makes implementing ASP.NET applications easier—themes—which let you skin control pages and achieve visual consistency with minimal effort. Note that I'm working with a beta release of ASP.NET 2.0 and some details discussed here may change by the final release.
The beauty of ASP.NET 2.0 themes is that, much like Windows® XP themes, they allow you to radically change the appearance of a set of pages by applying minimal changes. In a themed control, visual properties can be set and changed in a single shot. With ASP.NET themes, you can build skinned controls as long as the control supports templates. I'll return to this in a moment.
A theme is an attribute of the page class that you can set in various ways. Once the theme is set, the page ensures that all of its controls will render according to the visual settings defined in the theme. Themes let you keep style markup out of your ASPX code so you can write pages without concerning yourself with the style of controls and the graphics around them. Later, designers can create the theme.
In ASP.NET 2.0, a theme is made up of various files all installed in a given folder. The name of the folder determines the name of the theme. In the folder, there are a few types of files: a skin file, a CSS file, and optionally a subfolder of images or other auxiliary files such as XSLT and text files. Figure 1 shows an example. Note that the theme displayed—SmokeAndGlass—was originally included in Beta 1 of ASP.NET 2.0, but was later removed. I just made a backup copy of the files and kept them as a reference.
Figure 1** Custom Themes in ASP.NET 2.0 **
To be recognized by an application, a theme folder must either be located under the App_Themes folder below the application's root, or in a global folder. A theme located under the App_Themes folder at the root of the application is said to hold local themes as only the specific application can use it. Global themes are contained in child directories located under the following path:
The [version] placeholder indicates the version number of the ASP.NET 2.0 build you have installed. For Beta 2, it is v2.0.50215. Expect it to be different if you have installed any post-Beta 2 releases.
A skin is a markup file made of the control definitions like those you find in an .aspx file. One way to create a skin file is to simply create a temporary page, dropping controls in and styling as appropriate. When you're finished, just copy and paste the markup source of the control file to a skin file (see Figure 2), making sure to remove each control's ID property from the markup.
Figure 2 Create a Skin
<asp:GridView runat="server" SkinID="Classic" CellPadding="4" ForeColor="#333333" GridLines="None"> <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <RowStyle BackColor="#EFF3FB" /> <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" /> <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" /> <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <EditRowStyle BackColor="#2461BF" /> <AlternatingRowStyle BackColor="White" /> </asp:GridView>
A skin contains standard definitions for as many controls as you want. You can also define multiple skins per control. In this case, you add an extra attribute to the control definition—SkinID. The SkinID attribute names the control skin and allows controls to select that template at run time. Consider the following GridView fragment in an .aspx page:
<asp:GridView runat="server" id="grid" skinID="Classic" />
The SkinID attribute selects the specified skin from the bound theme, if there is one. Should the specified skin ID not match an existing skin for the control, you will get a warning from Visual Studio® 2005, and the skin ID will be ignored, thus resulting in the control being displayed without a theme.
A theme can exist without a CSS file, and vice-versa, of course. Skin files are not a superset of CSS per se, but rather of control property sets, and like regular rendering they result in inline CSS on markup elements.
Sound coding practices dictate that skin files should not contain behavioral properties such as AllowPaging. However, this is simply a recommendation; if you set AllowPaging in the skin file, it will work just fine, but storing behavioral properties in the skin like this violates the logical separation between style and behavior. You should also note that it is the control that dictates which of its properties can be themed.
You typically bind a theme to a page using the Theme attribute on the @Page directive:
<% @Page ... Theme="SmokeAndGlass" %>
You can also declaratively bind a theme to all the pages in a given folder by moving the setting to the folder-specific Web.config file:
<pages theme="SmokeAndGlass" />
A given theme can be bound to a page in two different ways: as a stylesheet theme or as a customization theme. Stylesheet themes are designer-supported and mimic CSS in a way that allows the developer to abstract styles to one location (for specific controls, you can override the style on the control directly). Aside from this and from the keyword you use, the difference is all in the timing. If you use the Theme attribute, you are binding a customization theme so the ASP.NET runtime will apply the settings of the theme only after processing the declarative attributes of controls. A stylesheet theme is bound by using the Stylesheet attribute, like so:
<% @Page ... StylesheetTheme="SmokeAndGlass" %>
In this case, the theme is applied before processing the declarative attributes. To better understand the difference, let's take a look at the following markup:
<!-- In the skin file --> <asp:DataGrid runat="server" backcolor="red" /> <!-- In the page --> <asp:DataGrid runat="server" backcolor="yellow" />
If the control belongs to a page bound to a customization theme, the grid will be displayed with the background color red as set in the theme. If the page is styled with a stylesheet theme, yellow will be the color of the grid's background, regardless of the color set in the theme. Note that any properties set through a theme can also be overridden programmatically by handling the Page_Load event if you want.
The GridView control supports the EmptyDataTemplate property for you to specify the markup that should be used when the control is bound to an empty data source:
<asp:GridView ...> ... <EmptyDataTemplate> <table style="border:solid 1px black"><tr><td> <img runat="server" align="absmiddle" src="images/critical.gif"> <b>Hello!</b><br>I'm a <b>GridView</b> control. Unfortunately, though, at this time I have no data to display. </td></tr></table> </EmptyDataTemplate> </asp:GridView>
The image critical.gif will be loaded from the Images folder below the theme folder (as you saw in Figure 1). Figure 3 shows the same grid control skinned differently. The figure shows the grid bound to distinct skins in the same theme, but you can easily move the two skins to distinct theme files.
Figure 3** Empty GridView Control Skinned Two Ways **
Special Themes Features
Loading themes programmatically when needed for individual pages couldn't be easier. All you have to do is set the Theme property on the Page class to the string that represents the name of the theme (stylesheet themes cannot be set in this manner, for they require an override of the StyleSheetTheme property on the Page class). The only trick is that you must set the theme in the PreInit event of the page. PreInit is a new event introduced in ASP.NET 2.0 which is fired just before Init, typically the first point in the control's lifecycle, and therefore a point where property sets for the theme can be applied before a control starts to manipulate properties. At this time PreInit is called, the page's control tree is already built, but the individual controls' trees themselves may not be.
By hooking the PreInit event, you can set the theme to a predetermined theme name or to a theme name that you get from the outside world—the user's profile, a dropdown list, the query string, or any number of other places. The following illustrates a good code pattern for loading themes dynamically into a page:
Sub Page_PreInit(ByVal sender As Object, ByVal e As EventArgs) Dim theme As String = GetThemeToApply(); If theme = "" Then Return Me.Theme = theme End Sub
A theme can also be used to style custom controls, as long as custom controls are properly registered in the skin file through the @Register directive or in the configuration file.
A control will be affected by a relevant theme unless its EnableTheming property is set to false (the default is true). When designing a control, you can mark one or more properties as theme-resistant by applying the Themeable attribute and setting it to false:
Public Class MyControl Inherits WebControl <Themeable(false)> Public Property TextFont As Font ... End Property End Class
This normally happens if it is essential for the user interface of the control that the property be set programmatically or declaratively but always under the control of the developer or page code. Of course, properties that are not stylistic or that are security conscious (for example, DataSourceID) should not be themeable. You would not want to allow a third party to change a control's DataSourceID through a theme.
Summing It Up
There is a lot more to themes than I've covered here, such as programmatically setting skinIDs and how themes and stylesheet themes interact with dynamic controls, but I hope this quick tour has helped to instill an appreciation for how useful this new feature can be. Personalization is important for users, but it also helps application designers to produce a more flexible and configurable framework. Skins and themes are ASP.NET 2.0 facilities at your disposal that really make personalization and customization much easier to accomplish.
Send your questions and comments for Dino to email@example.com.
Dino Esposito is a mentor at Solid Quality Learning and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch with Dino at firstname.lastname@example.org or join the blog at weblogs.asp.net/despos.