Skinnable/Themeable Controls and Script Includes

Published 05 October 2005 16:02 by Derek Lakin
Filed Under: ,

Yesterday I moved on from the back-end logic and data access of my pet project to the front-end stuff and so quickly introduced myself to MasterPages and Skins/Themes.

I had already produced the design for the site and so it was a fairly simple process to create a Theme based on the design.

My first obstacle was one of understanding and documentation interpretation. The whole point of themes is that you can switch from one to another with relatively little effort (if you define it in the pages element in web.config you only have to change it in one place) and so when I wanted to use an image from the theme in a page I figured it would be as easy as includin an <asp:Image /> control and specifying the relevant ImageUrl. The documentation seemed to back me up on this point as in the ASP.NET Themes and Skins Overview in the Theme Graphics and Other Resources section it states:

"Themes can also include graphics and other resources, such as script files or sound files. For example, part of your page theme might include a skin for a TreeView control. As part of the theme, you can include the graphics used to represent the expand and collapse buttons.

Typically the resource files for the theme are in the same folder as the skin files for that theme, but they can be elsewhere in the Web application, in a subfolder of the theme directory for example. To refer to a resource file in a subfolder of the theme directory, use a path like the one shown in this Image control skin:

<asp:Image runat="server" ImageUrl="ThemeSubfolder/filename.ext" />"

At first reading this would seem to infer that my initial assumption was correct and that to put a theme-based image on a page I simply need to add the above <asp:Image /> type element specifying the subfolder (if required) and filename within the theme folder as the ImageUrl. However, my understaning was wrong (and thanks to Craig Andera for correcting my understanding), what the documentation is trying to say is that you create a .skin file with the relevant <asp:Image /> control in it and specify the ImageUrl in there according to the theme subfolder and image filename. Then you just add the <asp:Image /> control to a themed page and set it's SkinID to match the SkinID in the .skin file.

This approach allows you to tie the <asp:Image /> control with a specific ID to a specific image for this theme only, which is a much better idea. Subsequent themes might not name the image file the same or may not even have that specific image file in them.

The next problem was that my site design uses a javascript include file to process certain <div /> elements to display rounded boxes. This is, of course, theme specific and should also reside in the theme folder, but how do I get a script include from the theme folder? The ClientScriptManager class is the first port of call as this provides us with methods such as RegisterStartupScript and RegisterClientScriptInclude, which allow us to dynamically include script blocks (former) and script includes (latter). To make this work we need to get to the theme folder which is "/App_Themes/<theme name>/scripts/". We could just hack about in the Page_Load event handler and pull out the value of the Theme property to format a string to get the script location, but this feels all wrong. The following approach is much better (and thanks again to Craig for his help on this):

First of all create a UserControl derived class with a ScriptUrl property that does nothing but register the script:

ClientScriptManager csm = this.Page.ClientScript;
string key = "cssCorners";
Type type = this.GetType();

if ((false == csm.IsClientScriptIncludeRegistered(type, key)) && (this.ScriptUrl.Length > 0))
    string scriptLocation = string.Format("App_Themes/{0}/{1}", HttpUtility.UrlPathEncode(this.Page.Theme), HttpUtility.UrlPathEncode(this.ScriptUrl));
    csm.RegisterClientScriptInclude(type, key, scriptLocation);

Then create a .skin file (or extend an existing one) that specifies that for a specific SkinID for our UserControl, where the script include is. Then you can either put an instance of the UserControl on the MasterPage, or just include it on pages that you need the script on. Then, if I change the theme and it doesn't use a script file I just don't specify the UserControl in that theme's skin file, or if it's a different script include, change the ScriptUrl in the new theme's skin file.


# re: Skinnable/Themeable Controls and Script Includes @ 05 October 2005 17:22

Nice find, Derek. Certainly save me some time when I get into ASP.NET 2.0 themes.

Paul Watson