Introduction
The Managed UxTheme assembly is a .NET wrapper around Windows XP's Theme API.
It can be used safely from C#, on any Windows platform that supports the .NET
framework. In addition to exposing the UxTheme API, it also exposes the static
data in TmSchema.h (part of the Platform SDK) that is used to define what
window classes can be themed, the parts of those classes, and the states that
each part may have a custom look for.
Background
Windows XP uses a C-style DLL to expose its theme functionality named
UxTheme.dll (the "Ux" stands for User eXperience). At my day job, I am
working on a couple of .NET custom controls written in C#. Of course one of the
requirements for these controls is that they use Windows XP themes when
appropriate, and mirror the user's current theme settings.
The problem with UxTheme.dll is that it is only available on Windows
XP, so any P/Invoke code that tries to use it directly will fail on other
versions of Windows. Pierre
Arnaud has written a C++ wrapper
DLL that can be safely called via P/Invoke on any .NET capable version of
Windows. His implementation uses David Y. Zhao's nice little C++ wrapper class
that dynamically links to UxTheme.dll, while also providing safe
fail-thru implementations of each method, if it can't load the UxTheme
library.
I like Pierre's solution and started out by using it. It didn't however
provide me with the level of functionality that I need from UxTheme.dll.
My next thought was to extend his work and continue using P/Invoke from C#. I
have however been looking for an excuse to do something more than the "Hello
World!" in Managed C++, and this seemed like the perfect opportunity.
This implementation also uses David's wrapper class (as do almost all of the
examples of XP theme related code on CodeProject. It really is a nice piece of
work!).
Implementation
There are two discrete parts of this assembly from the outside perspective.
The first is the UxTheme
class. This class provides a managed
wrapper around an HTHEME
handle. It exposes all of the methods on
the UxTheme DLL that takes an HTHEME
as instance method. Methods
that do not take a theme handle are exposed as static. Any thing it is possible
to do using the DLL's API should be possible through this class.
Parallel to this is an object hierarchy that wraps the property table data
created by TmSchema.h and Schemadefs.h. These two files are part
of the platform SDK and define the data model for what parts of the Windows UI
can be theme-ed.
This object hierarchy starts with the ThemeInfo
class. This
class contains some simple meta-data about the current theme, but also contains
a collection of WindowThemes
. A WindowTheme
represents
data about the parts of a particular window class. The WindowTheme
class has a collection of ThemeParts
, and
ThemePartStates
.
A ThemePart
represents a discrete part of a window class. The
down button for instance, is part of the ScrollBar
window
class.
Each WindowTheme
and ThemePart
can have 0 to n
states. ThemePartStates
are things like normal
,
disabled
, or hot
. The WindowTheme
class
has an UxTheme
property that can be used to get an instance of the
UxTheme
class specific for the window class. ThemePart
and ThemePartStates
also contain some instance methods so that they
can be rendered directly into a graphics context.
The UxTheme
class can be used without the ThemeInfo
hierarchy, but the hierarchy does put a more OO face on the whole thing.
Using the code
Using the code is pretty straightforward. All of the classes are in the
namespace System.Windows.Forms.Themes
. The only public ally
creatable class is ThemeInfo
. To drill your way down through the
window class, their parts and states, create an instance of
ThemeInfo
and start looking through its collection of
WindowThemes
on down.
You can get an instance of UxTheme
, either from an instance of
WindowTheme
, or by calling either of the static methods
UxTheme::OpenTheme
or UxTheme::GetWindowTheme
.
Make sure that you look at UxTheme::IsAppThemed
in you code
before trying to use any of the other methods of UxTheme
. This will
tell you whether the current OS supports themes and if so whether it is
currently themed. And don't forget, that this can change throughout the lifetime
of your application, because the user can turn off themes at any time.
The assembly does have a strong name, so you can put it in the GAC if you
want.
The demo project contains a re-implementation of David's Theme Explorer
application, written in C# and using the managed API. It also include a
slight re-work of the TabPage
compatible controls that Pierre has made available. They are included merely
as a demonstration of how this assembly can be used from custom control
implementations.
Points of interest
Managed C++ DLLs have some interesting constraints about entry points. Since
this DLL uses the C-Runtime it is a mixed mode DLL. It is compiled with the /NOENTRY linker
flag. Assemblies linked with this flag do no have an explicit entry point,
preventing the initialization of any static data aside from simple, integral
types. It would be nice to be able to use the CVisualStylesXp
class
as a static instance, but because the runtime does not initialize it, this isn't
possible.
Each instance of UxTheme
creates a member pointer to an instance
of the CVisualStylesXp
class. UxThemes
's static
methods create an instance of this class locally. The result of this is a lot of
calls to LoadLibrary
and FreeLibrary
. Luckily Windows
keeps a reference count on dynamically loaded DLLs, so this shouldn't pose a
significant performance hit.
Be that as may, it is something that bugs me about my implementation. If
anybody's got a good way to load and free the UxTheme DLL just once, and avoid
creating so many instances of the C++ wrapper class I'd love to hear it. MSDN
recommends implementing explicit Initialize
and
Deinitialize
methods, but I don't like forcing that on users of
this assembly.
History
- Version 1 - Initial release
- 08/26/2003
- Bug fixes
- Some reorganization in the API