February 14, 2008

Applying ASP.NET 2.0 CSS Themes for Web Controls from DevExpress

Filed under: DevExpress, ASP.NET

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&amp;t=633379322277157280" />
   3:   <link rel="stylesheet" type="text/css" href="/WebResource.axd?d=0pW-VO1NI7dfSm0O0Zq2ADLPYMn7C88nFaD1LYCGrG_m7LNXR4VkbF6eCu4o_f7P_mjdqJx3sBZZrQ4bq76FtHEcj1_39ooWlWE2tPILiLqxFiQpwhIOKyztP9HejXbuTlRxa7U-fgC-XpX8zwnYmQ2&amp;t=633379322285068656" />
   4:   <link rel="stylesheet" type="text/css" href="/WebResource.axd?d=0pW-VO1NI7dfSm0O0Zq2AMHyrkIXBazF7PZQtbA9x6A1Th7TVoM38ntOqyETlkyMPdYqI3W_YqfVOkNGOxuNNsjpxsJ17m3w-X4OU9YXQdI1&amp;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>

4 Comments »

The URI to TrackBack this entry is: http://rod.blogsome.com/2008/02/14/applying-aspnet-20-css-themes-for-web-controls-from-devexpress/trackback/

  1. 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 Sub
    

    The 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 Function
    

    Comment by Todd — March 19, 2008 @ 4:44

  2. 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

  3. 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

  4. 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

RSS feed for comments on this post.

Leave a comment

Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>


Get free blog up and running in minutes with Blogsome | Theme designs available here