Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Easily apply visual theming to Windows Forms for .NET v1.1

0.00/5 (No votes)
1 Apr 2007 1  
An article which shows how to easily apply Windows XP themes to Windows Forms in .NET v1.1.

Introduction

This article shows how to easily apply Windows XP themes to theme aware controls within a .NET v1.1 Windows Forms application.

Note: Theming is available as standard is .NET v2.0; this is showing how to apply themes to .NET v1.1 Windows Forms applications.

Some initial information

Windows XP ships with two versions of the Common Controls Library (comctl32.dll) - versions 5.8 and 6.0. v5.8 renders controls in the "Classic" style that you get on Windows NT/2000 and Windows 9x. v6.0 renders controls using the XP Visual Styles look and feel. Since most Windows Forms controls are based on ComCtl32, how they are rendered depends on which version of ComCtl32 is used to do the rendering. By default, v5.8 is used to render the client area of the app, and v6.0 is used to render the non-client area. That is why you see the title bar and window borders automatically render "themed", while the controls (like Button, TextBox, ListView, ComboBox, and so on) have the classic look by default.

In v1.0 of the Framework, the way to get visual styles in a Windows Forms app was to ship a manifest file with the app, that has information in it to indicate that v6.0 of ComCtl32 should be used for rendering. While this works fine, many developers felt it cumbersome to author, maintain, and deploy manifest files. They felt the need to be able to do this programmatically. Now, the Platform SDK does provide API to do this. Basically, you need to create and activate an Activation Context that has pretty much the same DLL redirection information in it as the manifest file. The Activation Context API can be used to do this in a way suitable to your application.

If you take a look at these APIs, you will probably notice that they aren't very easy to use. While the advanced developer may like to tinker around with activation contexts, it is probably not something a developer who wants some "quick and dirty" code to get visual styles will do. So the Windows Forms team decided to wrap these APIs and expose a simple method that developers could call, that would isolate them from these complexities. So, essentially, when you call Application.EnableVisualStyles, we set up an activation context around the application's message loop, so that ComCtl32 function calls can be properly redirected to ComCtl32 v6.0. That way, you don't need to include a manifest with your app. [4]

What this article is not

Applying visual styles to a .NET v1.1 application may also be achieved by using either of the following options, which really relate to the two options discussed in the Initial Information area above:

  1. Use an assembly manifest; there is a great article on this by Heath Stewart, right here at the CodeProject, at the following URL: Windows XP Visual Styles for Windows Forms. Which will work all the time, but is a little bit more effort.
  2. Use the Application.EnableVisualStyles() as shown below:
  3. Application.EnableVisualStyles();
    Application.DoEvents();
    Application.Run(new Form1());

    Which is quite nice, as it's very easy to implement, but sometimes it doesn't work, and worse still, some times it can actually raise an Exception (I've certainly seen it raise a InteropServices.SEHException, which is why I looked further and found the source code which makes up this article content).

Note: This article is quite different to both these approaches, as it provides a proper theme switching class namely the EnableThemingInScope class, that has the Application.EnableVisualStyles() method call, as described above, and it does not require the user to mess about with manifest files. So it's kind of got the ease of use of the Application.EnableVisualStyles() option, and the robustness of the manifest option.

Note about the article

I can take no credit for the main class (namely the EnableThemingInScope class) within this article; it is actually a Microsoft generated solution [3]. So kudos to them. However, it took me quite a while to actually find the Microsoft article in the first place, so I thought I would share it with all you good folk at the CodeProject. I just hope this is not construed as plagiarism; I would think not, as most people's work is going to be based on some (albeit sometimes very minimal) MSDN content at some point down the line.

Obviously, this code is somewhat out of date now, as .NET v2.0 provides theme support out of the box, but you never know when you may have to do this again.

Prerequisites

You will need a few things to complete this tutorial:

  • The Microsoft .NET Framework SDK (required)
  • Microsoft Visual Studio .NET 2003 (optional)
  • Microsoft Windows XP (optional)

Controls that support visual themes

The full list of controls that support visual styles are:

  • TextBox
  • RichTextBox
  • HScrollBar
  • VScrollBar
  • ProgressBar
  • TabControl
  • MainMenu
  • ContextMenu
  • ComboBox
  • DataGrid
  • ListBox
  • ListView
  • TreeView
  • DateTimePicker
  • MonthCalendar
  • Splitter
  • TrackBar
  • StatusBar
  • ToolBar
  • TreeView
  • ListView

Most of these controls support theming in their default state. Others (those that derive from ButtonBase, GroupBox, or Label) require that you set the FlatStyle property to System (this will be described shortly).

Common Controls library versions

Recall that:

Windows XP ships with two versions of the Common Controls Library (comctl32.dll) - versions 5.8 and 6.0. v5.8 renders controls in the "Classic" style that you get on Windows NT/2000 and Windows 9x. v6.0 renders controls using the XP Visual Styles look and feel. Since most Windows Forms controls are based on ComCtl32, how they are rendered depends on which version of ComCtl32 is used to do the rendering. By default, v5.8 is used to render the client area of the app, and v6.0 is used to render the non-client area. That is why you see the title bar and window borders automatically render "themed", while the controls (like Button, TextBox, ListView, ComboBox, and so on) have the classic look by default. [4]

ComCtl32.dll Version 6

All applications running on the Windows XP Operating System have a non-client area, which includes the window frame and non-client scrollbars. A visual style is applied to the non-client area by default. This means that the appearance of the non-client area is specified by the visual style that is currently installed. To apply a visual style to common controls in the client area, you must use ComCtl32.dll version 6 or later. Unlike earlier versions of ComCtl32.dll, version 6 is not redistributable. The only way you can use version 6 of the dynamic-link library (DLL) is to use an Operating System that contains it. Windows XP ships with both version 5 and version 6. ComCtl32.dll version 6 contains both the user controls and the common controls. By default, applications use the user controls defined in User32.dll and the common controls defined in ComCtl32.dll version 5.

If you want your application to use visual styles, you must add an application manifest that indicates that ComCtl32.dll version 6 should be used if it is available. Version 6 includes some new controls and new options for other controls, but the biggest change is support for changing the appearance of controls in a window. [2]

Why should I set the FlatStyle property of some control to System to get the themed look and feel?

For those controls that have FlatStyle properties, in all modes except FlatStyle.System, Windows Forms custom-renders the controls. In v1.0 and v1.1, the Windows Forms rendering code is not "theme-aware", so EnableVisualStyles has no effect unless ComCtl32 itself does the rendering. The FlatStyle.System setting does exactly that - tells Windows Forms to allow ComCtl32 to render the control. Unfortunately, in this mode, cool features like setting the BackColor or assigning an image to the control do not work, since to support these, Windows Forms needs to do the rendering. [4]

So now the code

The EnableThemingInScope class from Microsoft [3]:

[ SuppressUnmanagedCodeSecurity ]
public class EnableThemingInScope : IDisposable
{
    #region Instance fields
    //Instance fields
    private uint  cookie;
    private static ACTCTX enableThemingActivationContext;
    private static IntPtr hActCtx;
    private static bool contextCreationSucceeded = false;
    #endregion
    #region Public Methods
    /// <summary>
    ///Windows XP makes use of 2 types of common control comctl32.dll, however
    ///only v6 provides theming facilities using the UxTheme.Dll.
    ///This class attempts to correctly switch the Windows XP
    ///common controls from v5.8 (Non themed to v6.0 themed)
    ///So that comctl32 function calls  can be properly redirected to comctl32 v6.0. 
    /// </summary>
    /// <param name="enable">True to enable Visual Theming</param>
    public EnableThemingInScope(bool enable)
    {
        cookie = 0;
        //If theming is available for this operating system
        if (enable && OSFeature.Feature.IsPresent(OSFeature.Themes))
        {
            //The content must be created 1st
            if (EnsureActivateContextCreated())
            {
                if (!ActivateActCtx(hActCtx, out cookie))
                {
                    // Be sure cookie always zero if activation failed
                    cookie = 0;
                }
            }
        }
    }
    /// <summary>
    /// Deconstrcutor, simply disposes of the EnableThemingInScope object
    /// </summary>
    ~EnableThemingInScope()
    {
        Dispose(false);
    }
    /// <summary>
    /// Provides a Dispose method, to allow the EnableThemingInScope
    /// object to be disposed correctly
    /// </summary>
    void IDisposable.Dispose()
    {
        Dispose(true);
    }
    #endregion
    #region Private Methods
    /// <summary>
    /// Provides a mecahnism to clean up any held resources
    /// </summary>
    /// <param name="disposing">True if the held
    ///          resources should be de-allocated</param>
    private void Dispose(bool disposing)
    {
        if (cookie != 0)
        {
            if (DeactivateActCtx(0, cookie))
            {
                // deactivation succeeded...
                cookie = 0;
            }
        }
    }
    /// <summary>
    /// Attempts to provide active content to the controls that need to be 
    /// Themed
    /// </summary>
    /// <returns>true if the active content could be created</returns>
    private bool EnsureActivateContextCreated()
    {
        //lock on this object type, mark this section as a critical section
        lock (typeof(EnableThemingInScope))
        {
            if (!contextCreationSucceeded)
            {
                // Pull manifest from the .NET Framework install
                // directory
                string assemblyLoc = null;
                FileIOPermission fiop = new FileIOPermission(PermissionState.None);
                fiop.AllFiles = FileIOPermissionAccess.PathDiscovery;
                fiop.Assert();
                try
                {
                    assemblyLoc = typeof(Object).Assembly.Location;
                }
                finally
                {
                    CodeAccessPermission.RevertAssert();
                }
                string manifestLoc = null;
                string installDir = null;
                if (assemblyLoc != null)
                {
                    installDir = Path.GetDirectoryName(assemblyLoc);
                    const string manifestName = "XPThemes.manifest";
                    manifestLoc = Path.Combine(installDir, manifestName);
                }
                if (manifestLoc != null && installDir != null)
                {
                    enableThemingActivationContext = new ACTCTX();
                    enableThemingActivationContext.cbSize = Marshal.SizeOf(typeof(ACTCTX));
                    enableThemingActivationContext.lpSource = manifestLoc;
                    // Set the lpAssemblyDirectory to the install
                    // directory to prevent Win32 Side by Side from
                    // looking for comctl32 in the application
                    // directory, which could cause a bogus dll to be
                    // placed there and open a security hole.
                    enableThemingActivationContext.lpAssemblyDirectory = installDir;
                    enableThemingActivationContext.dwFlags = 
                                    ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;

                    // Note this will fail gracefully if file specified
                    // by manifestLoc doesn't exist.
                    hActCtx = CreateActCtx(ref enableThemingActivationContext);
                    contextCreationSucceeded = (hActCtx != new IntPtr(-1));
                }
            }
            // If we return false, we'll try again on the next call into
            // EnsureActivateContextCreated(), which is fine.
            return contextCreationSucceeded;
        }
    }
    //Declare all the Dll Imports, these are C++ Dll's
    [DllImport("Kernel32.dll")]
    private extern static IntPtr CreateActCtx(ref ACTCTX actctx);
    [DllImport("Kernel32.dll")]
    private extern static bool ActivateActCtx(IntPtr hActCtx, out uint lpCookie);
    [DllImport("Kernel32.dll")]
    private extern static bool DeactivateActCtx(uint dwFlags, uint lpCookie);
    private const int ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID = 0x004;
    //Create a structure to hold Active Content 
    private struct ACTCTX
    {
        public int       cbSize;
        public uint      dwFlags;
        public string    lpSource;
        public ushort    wProcessorArchitecture;
        public ushort    wLangId;
        public string    lpAssemblyDirectory;
        public string    lpResourceName;
        public string    lpApplicationName;
    }
    #endregion
}
#endregion

Then, for the form you want to apply the theming to, it's simply a case of adding the following code to the main method:

[STAThread]
static void Main() 
{
    using( new EnableThemingInScope( true ) )
    {
        Form1 frm1 = new Form1();
        //The CreateControl method forces a handle to be created
        //for the control and its child controls. 
        frm1.CreateControl();
        Application.Run(frm1);
    }
}

Shown below are some example screenshots; the controls to the left have their FlatStyle = System, whilst the ones to the right use FlatStyle = Standard.

I hope this helps someone out there who might still be developing in .NET v1.1, who wants to apply correct theming without the use of a Manifest file.

What do you think?

That's it. I would just like to ask, if you liked the article, please vote for it.

Bibliography / References

History

  • v1.0: 28/11/06.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here