Applying ASP.NET 2.0 CSS Themes for Web Controls from DevExpress
I’m using DXperience v2007 vol 3 web controls from DevExpress in my project. These controls rendering is based on CSS delivered by vendor. Of course every developer can easily deliver his own CSS files for custom rendering. All is described in "How to use a sample CSS file together with the CssFilePath and CssPostfix properties" knowledge base article. I recommend to read it first before following the rest of this text.
CSS files for DevExpress controls are passed to the browser by WebResource.axd if any of controls on page has empty CSSFilePath and CSSPostfix. Here is HTML head rendered for ASP.NET’s StyleSheetTheme "WarmSky".
1: <head>
2: <link rel="stylesheet" type="text/css" href="/WebResource.axd?d=0pW-VO1NI7dfSm0O0Zq2ACMk4iRYek_Z5HOc04vPAnw8rKCun3Q8F4Pym8tFdctHHl4XQbUWFljcY6QXnl3p7siLCGDJ1iKxufw0w7iv7o1SPl7T7o3UZQUTi-mf3HfnnTK_ucsD_MJr-bXTL61n0g2&t=633379322277157280" />
3: <link rel="stylesheet" type="text/css" href="/WebResource.axd?d=0pW-VO1NI7dfSm0O0Zq2ADLPYMn7C88nFaD1LYCGrG_m7LNXR4VkbF6eCu4o_f7P_mjdqJx3sBZZrQ4bq76FtHEcj1_39ooWlWE2tPILiLqxFiQpwhIOKyztP9HejXbuTlRxa7U-fgC-XpX8zwnYmQ2&t=633379322285068656" />
4: <link rel="stylesheet" type="text/css" href="/WebResource.axd?d=0pW-VO1NI7dfSm0O0Zq2AMHyrkIXBazF7PZQtbA9x6A1Th7TVoM38ntOqyETlkyMPdYqI3W_YqfVOkNGOxuNNsjpxsJ17m3w-X4OU9YXQdI1&t=633379322298387808" />
5: <link href="../App_Themes/WarmSky/Styles/main.css" type="text/css" rel="stylesheet" />
6: <title>Some Title</title>
7: </head>
Good solution is to prepare CSS files for DevExpress controls and put them in theme folder. Then just make sure that all DevExpress controls in page has set CSSPostfix property. Of course it is quite boring to set properties on every DevExpress control in design time. To make it more automatic let’s create simple DXControlsHelper static class and put there method which applies theme to control.
1: /// <summary>
2: /// Applies theme for DevExpressControl.
3: /// </summary>
4: /// <param name="control">The control.</param>
5: /// <param name="themeName">Name of the theme.</param>
6: internal static void ApplyThemeToControl(ASPxWebControl control, string themeName)
7: {
8: if(control == null)
9: return;
10:
11: if(String.IsNullOrEmpty(themeName))
12: {
13: control.CssPostfix = null;
14: control.CssFilePath = null;
15: }
16: else
17: {
18: control.CssPostfix = themeName;
19: // control.CssFilePath = "~/App_Themes/" + themeName + "/DXControls/{0}/styles.css";
20: }
21: }
This method sets only CssPostfix property because we’ve put DXControls CSS files in Theme folder so they will be automatically included during page rendering. That’s why line (19) is commented out.
Now it’s is time to deliver all DXControls from the page. To make sure that we will deliver all controls let’s make some recursive method to find all of them. To make it more universal let’s use generics.
1: /// <summary>
2: /// Finds recursively all controls of defined type.
3: /// </summary>
4: /// <typeparam name="TControlType">The type of the control type.</typeparam>
5: /// <param name="root">The root.</param>
6: /// <returns>List of controls.</returns>
7: internal static IEnumerable<TControlType> FindAllControlsRecursively<TControlType>(Control root)
8: where TControlType : Control
9: {
10: if(root == null)
11: yield return null;
12:
13: foreach(Control item in root.Controls)
14: {
15: if (item is TControlType)
16: yield return item as TControlType;
17: foreach (TControlType itemChild in FindAllControlsRecursively<TControlType>(item))
18: yield return itemChild;
19: }
20: }
Now it is time to apply theme to all controls. Hmm … maybe not to all …
1: /// <summary>
2: /// Applies theme to all child DXControls from root.
3: /// </summary>
4: /// <param name="root">The root.</param>
5: /// <param name="themeName">Name of the theme.</param>
6: internal static void ApplyThemeToControlsRecursively(Control root, string themeName)
7: {
8: foreach (ASPxWebControl item in FindAllControlsRecursively<ASPxWebControl>(root))
9: {
10: if(String.IsNullOrEmpty(item.CssPostfix) && String.IsNullOrEmpty(item.CssFilePath))
11: ApplyThemeToControl(item, themeName);
12: }
13: }
Line (10) checks if control has already set one of property, for example in design mode then theme will not be applied to such control. Last step is to modify BasePage like this …
1: /// <summary>
2: /// Handles the Render event of the Page control.
3: /// </summary>
4: /// <param name="sener">The source of the event.</param>
5: /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
6: protected virtual void Page_PreRender(object sender, EventArgs e)
7: {
8: DXControlsHelper.ApplyThemeToControlsRecursively(this.Master, this.Page.StyleSheetTheme);
9: }
.. now HTML header of rendered page looks like this.
1: <head>
2: <link href="../App_Themes/WarmSky/DXControls/Editors/styles.css" type="text/css" rel="stylesheet" />
3: <link href="../App_Themes/WarmSky/DXControls/GridView/styles.css" type="text/css" rel="stylesheet" />
4: <link href="../App_Themes/WarmSky/DXControls/Web/styles.css" type="text/css" rel="stylesheet" />
5: <link href="../App_Themes/WarmSky/Styles/main.css" type="text/css" rel="stylesheet" />
6: <title>Title</title>
7: </head>


I love what you’re trying to do. Unfortunately it’s over my head :( I tried converting this to vb.net but i’m not having any luck. Showing the namespaces that you use might help a little. I converted the first routine to vb.net…
' Applies theme for DevExpressControl. Public Shared Sub ApplyThemToControl(ByVal control As ASPxWebControl, ByVal themeName As String) If (IsDBNull(control)) Then Exit Sub If (String.IsNullOrEmpty(themeName)) Then control.CssPostfix = Nothing control.CssFilePath = Nothing Else control.CssPostfix = themeName ' control.CssFilePath = \"~/App_Themes/\" + themeName + \"/DXControls/{0}/styles.css\"; End If End If End SubThe 2nd one I can’t quite translate. So far I have…
Private Function FindAllControlsRecursively(ByVal root As Control) As IEnumerable(Of Control) If IsDBNull(root) Then Return Nothing End If For Each Control In root.Controls If Control.GetType.ToString = \"TControlType\" Then ' i don't think this is right Return Control End If Next ' .... lost at this point End FunctionComment by Todd — March 19, 2008 @ 4:44
It is all about new C# 2.0 “yield” keyword. It really helps to make simple iteration implementing. As far as I know VB.NET does not have language support for iterators so it has no support for yield keyword also. “yield return something” it is not the same as “return something”. I’m not VB.NET expert so I don’t know how to help you.
Comment by rod — March 20, 2008 @ 0:09
This is all fine and dandy, however, they should never have created their themes like this and should have just used the intrinsic way of using Themes. For example, whenever I add a pre-defined “skin” from DevExpress, I go through every single Css and .Skin file and remove all the Post-fixes and remove all related “hard-coding” that they do in the codebehind as well. Then, all I need to do to use one of their themes is..well, read up on themes. The only issue I’ve run into is that some of their controls are STILL hard-coding the path to the Theme files and as such it’s kind of difficult to get everything working as it should. I have no idea why they did this, it just makes things more difficult.
Comment by Tim — May 1, 2008 @ 2:32
First line in the yield works better for me when it reads:
if (root == null) yield break;
Otherwise great.
Comment by Mark — June 10, 2008 @ 22:07