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:
- 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.
- Use the
Application.EnableVisualStyles()
as shown below:
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
private uint cookie;
private static ACTCTX enableThemingActivationContext;
private static IntPtr hActCtx;
private static bool contextCreationSucceeded = false;
#endregion
#region Public Methods
public EnableThemingInScope(bool enable)
{
cookie = 0;
if (enable && OSFeature.Feature.IsPresent(OSFeature.Themes))
{
if (EnsureActivateContextCreated())
{
if (!ActivateActCtx(hActCtx, out cookie))
{
cookie = 0;
}
}
}
}
~EnableThemingInScope()
{
Dispose(false);
}
void IDisposable.Dispose()
{
Dispose(true);
}
#endregion
#region Private Methods
private void Dispose(bool disposing)
{
if (cookie != 0)
{
if (DeactivateActCtx(0, cookie))
{
cookie = 0;
}
}
}
private bool EnsureActivateContextCreated()
{
lock (typeof(EnableThemingInScope))
{
if (!contextCreationSucceeded)
{
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;
enableThemingActivationContext.lpAssemblyDirectory = installDir;
enableThemingActivationContext.dwFlags =
ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
hActCtx = CreateActCtx(ref enableThemingActivationContext);
contextCreationSucceeded = (hActCtx != new IntPtr(-1));
}
}
return contextCreationSucceeded;
}
}
[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;
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();
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