System Delphi

Title: Support for Windows Visual Styles (Themes) API in your controls
Question: Microsoft have introduced new Visual Styles API to provide extremely new look-n-feel in Windows XP.
Here I'll try to describe how to use its features inside Your controls.
Answer:
Support for Windows Visual Styles (Themes) API in your controls
Copyright Akzhan Abdulin, November 2001, Revision 1.10.
Translation Eugene Mayevski (EldoS, http://www.eldos.org/), December 2001.
Introduction
In Operating Systems (OS) developed by Microsoft, starting from Microsoft Windows XP, so-called visual styles appeared. They define the look-and-feel of control elements (controls) and other windows (windows) present in user interface.
Unlike previous versions of Microsoft OS, in Windows XP control elements can have not only customizable color scheme and size, but also different methods of drawing their parts (parts).
The methods themselves have been packed to the separate module with mst extension. This module is included to visual style shipping package. Windows XP installation includes only one visual style -- Luna.
Each visual style can be the basement for several different themes (themes).
Naturally Visual Styles API supports drawing of all control elements of Windows API. Moreover, it is necessary to emphasize that creation of control elements which have visual presentation different from standard controls, now is quite difficult.
Unfortunately Visual Styles API is a part of operating system and this doesn't let one use styles' advantages on earlier operating systems. Also even in the case of operating system with visual styles the user can choose not to use them.
So you will need to build your control including code blocks for painting it when the client doesn't have visual styles support or visual styles are not defined in current user session. It's important to say, that the user can turn visual styles on or off during his work with the application that utilizes your control.
The above notes don't let us call Visual Styles API a project that was properly thought over. However it was Visual Styles API use that brought a second breath to Windows XP look-and-feel. It is necessary to admit that aesthetically Luna style exceeds all previous works of Microsoft
Applications that use nonstandard control will look foreign in new environment, and this can cause user's antipathy. And on the contrary, creative use of Visual Styles API features in your controls can attract the user. Quality content doesn't guarantee success (as well as outer look doesn't), but combination of these features, in my opinion, doubles an effect.
And this is why support for Visual Styles API is especially important for developers of freeware and shareware applications.
This article uses both information published in MSDN and experience of your colleagues.
In particular, I studied Visual Styles API for further use in ElPack components suite developed by EldoS, whose target development platform is Borland Delphi/C++Builder/Kylix family. Most of the code blocks used in the article are taken with slight modifications from ElPack.
Most of ElPack 3.0 and above components support Visual Styles API and can be used in your applications to give them a modern look. Besides this, studying of the source code of this package might be an interesting and useful exercise.
Standard control elements
Visual Styles API is supported by new generation of common controls library (ComCtl32.dll version 6 and above). It is necessary to emphasize, that new generation of this library implements both common control elements like treeview and list view (common controls), and standard control elements such as edit, listbox, combobox. Obviously, this makes the library incompatible with previous operating systems versions in which support for standard controls is implemented by user.dll.
For compatibility with older applications several changes have been done in dynamic library loader algorithm. Particularly if your application is not marked to be used with ComCtl32.dll version 6, it will be able to use only previous generation of common controls library. So standard control elements of your application won't use Visual Styles API.
You can mark your application as designed with support for ComCtl32.dll using a so-called manifest (manifest). Manifest is an XML document that describes requirements of the module in use of environment.
Detailed description of manifests is out of scope of this article, so I will only publish the manifest that declares necessity to use ComCtl32.dll version 6 (see comments ):
ATTENTION: this codeshown in delphi3000 incorrectly. Look at right code on http://www.akzhan.midi.ru/devcorner/articles/Windows%20Visual%20Styles%20(Themes)%20API%20Support%20in%20Your%20controls%20Eng.htm


name="Microsoft.Windows.Shell.shell32" /
Windows Shell


version="6.0.0.0" publicKeyToken="6595b64144ccf1df" language="*"
processorArchitecture="*" /



You can assign the manifest to your application in two ways:
Place the manifest file near your application's executable module. The name of the manifest file must consist of the name of the executable module (including its extension) and "manifest" extension. So the name of the manifest file for "Project1.exe" project will be "Project1.exe.manifest".
Place the manifest as a resource of executable module (for application - CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest", see comments ). The compiled resource file with the manifest is included as additional material to this article.
If the manifest file for the module exists, it will be used even when the module contains a manifest in resource.
Nonstandard control elements
Every time when we need to create our own control element and not extend functionality of existing control,we need to re-implement support for Visual Styles API.
All methods of Visual Styles API are implemented in uxtheme.dll dynamic library. This library includes a theme manager that implements code for changing themes and notifying about such changes. Also the proxy module is included, which routes the invocations of theme methods to the corresponding entry points of the visual style module that has mst extension.
Development tool
Below I will illustrate this article with samples that use Borland Delphi/VCL, and developers that use other development tools will read these samples without efforts and rewrite them for their development tool like ATL/WTL or MFC, cause the samples use pure Win32 API/Visual Styles API.
C++
If you want to use C++, you will need an updated Platform SDK (not earlier than June 2001). Visual Styles API is declared in the following header files:
uxtheme.h - API call declarations;
tmschema.h - constants and declarations of the data types defined in Visual Styles API.
When linking you might need to use uxtheme.lib import library. However taking into account necessity to support previous operating systems you might also choose to use late binding based on LoadLibrary.
In additional materials chapter of this article you will find C++ classes that simplify building of control elements using C++ compatible with Visual Styles API. These classes were developed by Vladimir Romanov.
First class provides dynamic loader for theme manager. By the way the author recommends to pay attention at how dynamic loading (use and redefinition of macros) is implemented -- this method is quite easy to use.
Second class (in fact class template) contains abstracts of higher level as well as message handler, and is designed to be used with ATL/WTL
/////////////////////////////////////////////////////////////////////////////
// CXpTheme - message handlers for theme support
// Chain to CXpTheme message map.
// Example:
// class CMyButton : public CWindowImpl,
// public CXpTheme
//
// public:
// BEGIN_MSG_MAP(CMyButton)
// // your handlers...
// CHAIN_MSG_MAP_ALT(CXpTheme, 1)
// END_MSG_MAP()
// // other stuff...
// };
// Also you must call CXpTheme::SubclassWindow() from CMyButton::SubclassWindow()
Also Vladimir Romanov has provided the complete source code of the control element that reproduces behavior of standard Windows button (themed pushbutton).
Delphi
If you want to work with Delphi, you need fresh Delphi-JEDI Complete Win32 API Header Conversion (newer than July 2001). Currently it's available as ftp://delphi-jedi.org/api/Win32API.zip. Visual Styles API is defined in the following modules:
JwaUxTheme.pas - call declarations for Visual Styles API.
JwaTmSchema.pas - declarations of constants and data types, defined in Visual Styles API.
JwaWinUser.pas - WM_THEMECHANGED Windows message declaration.
However due to known reasons (compatibility of your components with previous operating systems) you might need to use late binding. Marcel van Brakel adds optional late binding (can be turned on or off using compiler directive) to these modules in January 2001 headers.
Besides this, I will enumerate some important constants defined in Platform SDK:
const
// resource type for manifest
RT_MANIFEST = 24;
// id of manifest for application
CREATEPROCESS_MANIFEST_RESOURCE_ID = 1;
// id for static binding of the module
ISOLATIONAWARE_MANIFEST_RESOURCE_ID = 2;
// id for dynamic binding of the module
ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID = 3;
// the message sent when the theme changes.
WM_THEMECHANGED = $031A;
Igor Kokarev has provided me with source code samples. Also I have included Visual Styles Explorer with source code to the additional materials. Visual style scheme has been defined not completely, and you can complete it yourself, for example by adding something to XML document, that describes it. It is possible that in future I will enhance this application functionality.
Main concepts
Checking the environment
When we design the components to be used not only in environment that supports Visual Styles API, we will need to implement check for environment type and also implement dynamic loading of theme manager if the environment is theme-aware
The following code supposes that Visual Styles API might be present in Windows NT family of operating systems with version 5.1 and above (Windows XP):
var
ThemesAvailable : Boolean;
implementation
uses
SysUtils
;
const
themelib = 'uxtheme.dll';
var
hThemeLib: HINST;
initialization
{$ifdef MSWINDOWS}
if (Win32Platform = VER_PLATFORM_WIN32_NT) and
(((Win32MajorVersion = 5) and (Win32MinorVersion = 1)) or
(Win32MajorVersion 5)) then
begin
hThemeLib := LoadLibrary(themelib);
if hThemeLib 0 then
begin
IsThemeActive := GetProcAddress(hThemeLib, 'IsThemeActive');
// other actions
ThemesAvailable := True;
end;
end;
{$endif}
finalization
ThemesAvailable := false;
end.
Handling errors
To notify the caller about the error most of Visual Styles API calls use the return value of type HRESULT (already known to all who dealt with COM) You can use SUCCEEDED(hr) and FAILED(hr) macros to check success of the method call.
In addition to return value, all Visual Styles API methods use Win32 API SetLastError routine to notify about errors. You can use GetLastError routine to check this code. All error information is thread-dependent.
Delphi/C++Builder offers you nice methods OleCheck(hr) and RaiseLastWin32Error(), that raise an error when the code that indicates failure is returned. The text of exception is obtained using FormatMessage method. Visual C++ offers the same functionality.
In early releases of Visual Styles API we were offered additional methods to get information about errors. For example in case of error you could use the method GetThemeLastErrorContext. To create the error message you could use FormatThemeMessage. Currently these methods are not exported by name, but it is likely that they are available to import by ordinal.
So you may like to create the method ThemeCheck, that throws exception when Visual Styles API fails (in most cases OleCheck method is enough). show ThemeCheck source code .
unit ThemeSupport;
interface
uses
SysUtils;
type
EThemeException = class(Exception);
function ThemeCheck(hr: HRESULT): HRESULT;
implementation
uses
Windows,
ComObj,
ElUxTheme;
function ThemeCheck(hr: HRESULT): HRESULT;
var
LangId: DWORD;
tecx: TThemeErrorContext;
err: WideString;
begin
if FAILED(hr) then
begin
if SUCCEEDED(GetThemeLastErrorContext(tecx)) then
begin
LangId := ConvertDefaultLocale(LOCALE_USER_DEFAULT);
SetLength(err, 255); // try to get 255-char len string
if SUCCEEDED(FormatThemeMessage(LangId, tecx, PWideChar(err), Length(err))) then
begin
raise EThemeException.Create(err); // raises and exits
end;
end;
OleError(hr); // use system error message formatting services
end;
Result := hr; // if succeeded
end;
end.
Theme data handles
Visual Styles API is handle-oriented, the same as the most of other Microsoft operating systems services are.
Theme data handle (referred as theme handle) is bound by strictly defined theme and class of standard/common Windows control elements.
type
HTheme = THandle;
If you develop the control element that uses parts of different standard controls (for example a Page Control that draws scroll buttons when the tabs don't fit into view) , you will need to open several theme handles, as Page theme handle can't draw scroll buttons and you will need to use the handle of Scrollbar theme.
To acquire and revoke the theme handles one uses OpenThemeData and CloseThemeData methods. If OpenThemeData returns empty value (0 in Delphi and NULL in C++) , you need to use your own code for painting the control element.
The last handle acquired with OpenThemeData for the given window can be queried using GetWindowTheme. However I recommend to avoid use of this method.
Of course you can query handles when you use them, but as the themes are used mainly when drawing control elements, such decision can't be called advisable.
Usually theme handles are requested in response to WM_CREATE message and freed on WM_DESTROY message arrival. Such practice is desirable cause calculation of the client area of your window via WM_NCCALCSIZE usually requires theme handle presence.
Classes, elements and their states
Visual Styles API includes support for standard and common classes of control windows. If you are acquainted with Windows API, you remember that the window class is identified by the string of characters. Visual Styles API prompts You about window class which parts will be drawn for Your control.
For each class of windows Visual Styles API defines few elements those placed on control surface like flat tiles. In fact you can imagine control element appearance as the mosaic of relatively small base elements. Elements are identified by non-negative index. In TmSchema module all elements are defined via enumerations, and their identifiers have the look of xxxP_yyy, where xxx refers to class of the element, and yyy - to the element defined in the class. Sample: - BP_GROUPBOX identifies, that defines "controls group", as an element that belongs to "button" controls class.
Main (root, base) element always has the index 0. This element often has the name "filler". Usually this element is used to draw the background of the non-client area of the control (unless talking about complex backgrounds) in response to WM_NCPAINT message.
The control element can contain several additional elements. For example the status bar ("status") can contain SP_PANE, SP_GRIPPERPANE and SP_GRIPPER elements. Each class of controls defines its own elements. Their indices for different classes can overlap.
Each element must have some state. For example, scroll button of the scroll bar can be in normal, disabled, pressed and "hot" states (see comments ). Each state is identified by a non-negative index. State 0 is used for information queries. States are usually defined the same for different classes and elements (if defined). TmSchema defines all states via enumerations and their identifiers have the look of xxxS_yyy, where xxx refers to the element, and yyy - to state of the element. A common sample - PBS_DISABLED identifier that defines "Forbidden" state for BP_PUSHBUTTON element, that belongs to the "button" controls class.
You can find out whether the presence of any of the class elements in some state is defined using IsThemePartDefined method. Pay attention to XXXS_NORMAL state code. States XXXS_HOT and similar are assumed to be also defined regardless of what IsThemePartDefined returns. XXXS_DISABLED state might be not defined and to check for it's presence one needs to call IsThemePartDefined. To study these details of Visual Styles API you can use Visual Styles (Themes) Explorer that is included to additional materials of this article.
Besides that you can determine the elements of the class element using GetThemePartSize method. For each element the minimal (TS_MIN), optimal (TS_TRUE) and drawable (TS_DRAW) sizes are defined. Drawable size is based on the size of the paint area passed to the method. Minimal size defines when the element is still drawn. Optimal size is the size that defined by theme designers as desirable.
This call is very useful when for example we need to define, where to place the size grip for our status bar control.
Painting
Painting of the elements using Visual Styles API is very simple:
If the element contains transparent or semi-transparent elements (as reported by IsThemeBackgroundPartiallyTransparent), first the control's parent must be drawn using DrawThemeParentBackground. when necessary you can get the region, which precisely specifies the bounds, where the element draws itself, using GetThemeBackgroundRegion. This might be useful for further use when calling SetWindowRgn.
The element itself is drawn using DrawThemeBackground. The element text is drawn using DrawThemeText similar to system DrawText. Image from imagelists are drawn using DrawThemeIcon. These methods reflect the state of the element when possible, modifying the image drawn.
You also have a possibility to get the detailed information about the font being used in theme (GetThemeTextExtent, GetThemeTextMetrics).
There are several supplementary methods for retrieving miscellaneous information.
Client and non-client areas of the window
It is necessary to mention that Visual Styles API doesn't distinguish client and non-client areas of the window. At the same time when using Visual Styles API you need to do necessary painting of the client and non-client areas of the window using Visual Styles API.
A number of the window elements must be drawn without intersection with contents that they surround.
For example, tab pane (page) in page control (set of pages) has the clearly defined border. All control elements that are placed inside this page should not cross the border. In fact this means that the border should be taken into account when calculating the size of the client area.
Visual Styles API offers GetThemeBackgroundContentRect method, which lets you get the location of the client area of the window. It is important to take into account that the top, bottom, left and right margins are not symmetric. Typical use case:
procedure TElTabSheet.WMNCCalcSize(var Message: TWMNCCalcSize);var R, R1: TRect;begin if IsThemeApplied then begin if PageControl.ShowBorder then begin inherited; R := Message.CalcSize_Params.rgrc[0]; if Succeeded(
GetThemeBackgroundContentRect(TabTheme, Canvas.Handle, TABP_PANE, 0, R, R1)
) then begin Message.CalcSize_Params.rgrc[0] := R1; end; end; end else inherited;end;
Of course, we need to redefine painting of the non-client area of the window:
procedure TElTabSheet.WMNCPaint(var Message: TMessage);
var
RC,
R1,
R2,
RW : TRect;
DC : HDC;
begin
if not IsThemeApplied then
begin
Inherited;
end
else
begin
// try to get the context bound by the clipping region
DC := GetDCEx(Handle, HRGN(Msg.wParam), DCX_WINDOW or DCX_INTERSECTRGN); if DC = 0 then begin
// Failed. get the whole DC. DC := GetWindowDC(Handle); end;
// Obtained the client area in client coordinates Windows.GetClientRect(Handle, RC);
// Obtained the window area in screen coordinates
GetWindowRect(Handle, RW);
// Obtained the window area in client coordinates.
MapWindowPoints(0, Handle, RW, 2);
// Obtained the client area in non-client coordinates OffsetRect(RC, -RW.Left, -RW.Top);
// excluded the client area from the clipping region - we don't paint there
ExcludeClipRect(DC, RC.Left, RC.Top, RC.Right, RC.Bottom);
// Obtained non-client area in client coordinates OffsetRect(RW, -RW.Left, -RW.Top);
R2 := RW;
// TElTabSheet specific skipped
if IsThemeBackgroundPartiallyTransparent(TabTheme, DC, TABP_PANE, 0) then
begin
DrawParentThemeBackground(Handle, DC, RW);
end;
DrawThemeBackground(TabTheme, DC, TABP_PANE, 0, RW, @R2);
ReleaseDC(Handle, DC);
end;
end;
Client area painting must be also altered to draw only the part of the filler, which is visible in the client area. We might use the algorithm that supplements the one above (calculations using GetWindowRect, MapWindowPoints and GetClientRect) , but here we demonstrate one more choice -- use of GetThemeBackgroundContentRect:
procedure TElTabSheet.Paint;
var R, Rect,
R1 : TRect;
R2: TRect;
ACtl : TWinControl;
BgRect : TRect;
begin
R := ClientRect;
if IsThemeApplied then
begin
R2 := BoundsRect;
OffsetRect(R2, -R2.Left, -R2.Top);
GetThemeBackgroundContentRect(TabTheme, Canvas.Handle, TABP_PANE, 0, R2, R1);
R2.Left := - R1.Left;
R2.Top := - R1.Top;
R2.Right := R.Right + R2.Right - R1.Right - R2.Left + R1.Left;
R2.Bottom := R.Bottom + R2.Bottom - R1.Bottom - R2.Top + R1.Top;
R1 := Canvas.ClipRect;
DrawThemeBackground(TabTheme, Canvas.Handle, TABP_PANE, 0, R2, @R1);
exit;
end;
Besides this, we have GetThemeBackgroundExtent method, that lets us having the client area calculate the size of the whole area including non-client area. Using it the above code can be rewritten to be more simple. I leave it for you as an exercise.
In case of transparency/semi-transparency of the filler element we need to paint the part of the parent window that is covered with our control.
One can check transparency/semi-transparency of the theme element using IsThemeBackgroundPartiallyTransparent method. One can draw the part of the parent window covered by our component using DrawThemeParentBackground.
It makes sense to move the above to WM_ERASEBKGND message handler (note that the similar work should be done in WM_NCPAINT message handler too for non-client area). By the way painting of the filler element can also be placed in this handler.
{$ifndef CLX_USED}
procedure TElXPThemedControl.WMEraseBkgnd(var Message: TWMEraseBkgnd);
var
RC: TRect;
RW: TRect;
begin
{$ifdef VCL_4_USED}
if IsThemeApplied() then
begin
RC := ClientRect;
GetThemeBackgroundExtent(Theme, Message.DC, 0, 0, RC, RW);
if IsThemeBackgroundPartiallyTransparent(Theme, 0, 0) then
begin
DrawThemeParentBackground(Handle, Message.DC, RC);
end;
DrawThemeBackground(Handle, Message.DC, 0, 0, RW, @RC);
Message.Result := 1;
end
else
{$endif}
begin
Inherited;
end;
end;
{$endif}
Some control elements don't require painting of the background using filler element (for example, CheckBox in transparent mode). In such cases the handler should be redefined.
The above source code can be improved to draw the filler according to current control state. For example, when the window is (not Enabled) and painting for disabled part is defined (IsThemePartDefined), such painting should be done using "Disabled" state (for most elements the code of this state is 4).
ElPack components suite uses another approach. WM_ERASEBKGND message handler is empty and all painting occurs in WM_NCPAINT and WM_PAINT event handlers. Painting is done not to target device context directly, but using double-buffering via intermediate memory context (TBitmap, PixelFormat := pfDevice). This allows to avoid image flickering.
Visual Styles API also offers a method HitTestThemeBackground to define whether the point belongs to some element of the window class theme element. The returned value belongs to the set of values returned by WM_NCHITTEST message handler.
Theme manager
The whole desktop of the operating system can be assigned a single scheme based on preferences of the user that works in current session (global theme). We can check whether the theme is assigned using IsThemeActive method. If the global theme is not assigned, the visual style is also not defined and one should draw the control without using Visual Styles API.
Each of the started applications can either support themes, or support them incompletely or not support at all. So there is a concept of theme assigned to application (application theme). You can check whether the application has a theme assigned using IsAppThemed method.
Moreover, we can specify the theme for each window (and control element). In fact the control deals only with this theme (control theme). When global theme is present you always have possibility to work with themes even when the application has no theme assigned. Common controls always take into account whether the application theme is defined, but in my control elements I always use visual styles even when there is no application theme.
Window theme can be changed using SetWindowTheme method. So if you want to disable use of themes for the window specified by hwnd, you can write something like
SetWindowTheme (hwnd, ' ', ' ');
And when you need to reset the default behavior, it's enough to call
SetWindowTheme (hwnd, nil, nil);
Each time, when the theme manager needs to change the theme for your control it sends WM_THEMECHANGED message to this control.
So when this message is received, our control must free the theme handles and request new theme handles. Besides that as the theme change usually means change of control size, in particular, position of the client area, it is necessary to notify the control about the need to recalculate the client area.
I will show the useful details of one of basic ElPack classes, which implements the most of necessary functionality (this component is compatible with both VCL and CLX):
type
TElXPThemedControl = class(TCustomControl)
private
FUseXPThemes: Boolean;
FTheme: HTheme;
protected
procedure SetUseXPThemes(const Value: Boolean); virtual;
function GetThemedClassName: WideString; virtual; abstract;
// some declarations skipped
{$ifdef MSWINDOWS}
{$ifndef CLX_USED}
procedure WMThemeChanged(var Message: TMessage); message WM_THEMECHANGED;
// some declarations skipped
{$endif}
procedure FreeThemeHandle; dynamic;
procedure CreateThemeHandle; dynamic;
{$endif}
property UseXPThemes: Boolean read FUseXPThemes write SetUseXPThemes default True;
public
constructor Create(AOwner : TComponent); override;
function IsThemeApplied: Boolean;
property Theme: HTheme read FTheme;
end;
constructor TElXPThemedControl.Create(AOwner: TComponent);
begin
inherited;
FUseXPThemes := True;
end;
procedure TElXPThemedControl.CreateThemeHandle;
begin
if ThemesAvailable then
{$ifndef CLX_USED}
FTheme := OpenThemeData(Handle, PWideChar(GetThemedClassName()))
{$else}
{$ifdef MSWINDOWS}
FTheme := OpenThemeData(QWidget_winID(Handle), PWideChar(GetThemedClassName()))
{$endif}
{$endif}
else
FTheme := 0;
end;
procedure TElXPThemedControl.FreeThemeHandle;
begin
{$ifdef MSWINDOWS}
if ThemesAvailable then
CloseThemeData(FTheme);
{$endif}
FTheme := 0;
end;
function TElXPThemedControl.IsThemeApplied: Boolean;
begin
Result := UseXPThemes and (FTheme 0);
end;
procedure TElXPThemedControl.SetUseXPThemes(const Value: Boolean);
begin
if FUseXPThemes Value then
begin
FUseXPThemes := Value;
{$ifdef MSWINDOWS}
if ThemesAvailable and HandleAllocated then
begin
if FUseXPThemes then
begin
CreateThemeHandle;
end
else
begin
FreeThemeHandle;
end;
end;
{$endif}
end;
end;
procedure TElXPThemedControl.WMThemeChanged(var Message: TMessage);
begin
if ThemesAvailable and UseXPThemes then
begin
FreeThemeHandle;
CreateThemeHandle;
SetWindowPos(
Handle,
0,
0, 0, 0, 0,
SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER
);
RedrawWindow(Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_ERASE);
end;
Message.Result := 1;
end;
It should not be a hard task to extend this component yourselves, by adding WM_CREATE and WM_DESTROY handlers. UseXPThemes property allows to control use of themes by the application.
The above information is enough to start to correctly use Visual Styles API in your products.
Details
Low-level API
Besides the mentioned high-level calls, Visual Styles API offers low-level API. Using low-level functions it is possible to obtain different information about properties such as size, position, color etc., used by the visual style to draw some theme element. Some low-level API methods work correctly even when no global style is defined, and return necessary system information.
Unfortunately the structure of this API level has been implemented not perfectly. For example, in standard theme the following call for GroupBox element will work:
GetThemeColor(Theme, BP_GROUPBOX, 0, TMT_TEXTCOLOR, AColor);
At the same time the same call for CheckBox element won't work.
Control framing
Visual Styles API offers DrawThemeEdge method to draw the frames similarly to DrawEdge method. Unfortunately, it's painting doesn't fit into theme concept.
To draw control frame it is recommended to use the filler of "edit" class in necessary state (normal or disabled).
Drawing of nonstandard elements
Of course, Visual Styles API has no information about nonstandard control elements. Such well-known controls as OutlookBar and Splitter also fall into this category.
In this case the developer must choose the suitable solution himself using decoration elements of standard controls.
For example, as the splitter itself can be placed to the same class of elements as the window border, it's painting can be done using WP_FRAME element of "window" class. This is how splitter in Windows Media Player 8 is done.
At the same time some authors might choose to draw splitter using BP_PUSHBUTTON element of "button" class, treating it as more common to users.
Possibility of such variant readings is one of the most important problems of existing Visual Styles API. Moreover, this is the evidence of incompleteness of this implementation. It is possible that in future Microsoft in some way will improve the situation.
Also, let's suppose that we decided to place one more additional button to the standard Windows control -- Scrollbar. Here we will face one more problem of the existing Visual Styles API.
The problem is that Visual Styles API splits controls to large flat parts. It is not possible to draw the background of the scroll button over the scrolling area and complement it with some picture over this background, as it is possible to only draw the button in whole (scroll buttons already include some image on the button background). In these cases we had to make compromises that can cause unexpected consequences with future themes from Microsoft. For example, we drew background for the button in scrollbar as a thumb button. If new Microsoft theme will introduce some very complicated in it's design element for the thumb button, our compromise will be void.
Such improvidence of Visual Styles API creators I also attribute to incompleteness of the product. The idea is good, but the implementation of theme manager, in my opinion, was carried out by unqualified team in condensed terms.
Dynamic libraries
As mentioned above, new generation of Microsoft operating systems starting from Windows XP, implements new way of loading dynamic libraries, based on use of manifests. For example, now main application can load new common controls library, and additional modules will stay with the old one. This behavior is possible due to concept of isolation of module versions.
As a result of this lots of changes were added to Platform SDK, including concept of module, compatible with isolation policy. Such module provides a loader with the manifest, that defines the rules of isolation policy, defined for each module. The author of such module has to link the resource that contains the manifest, to the module.
When the dynamic library is imported statically, the manifest with ISOLATIONAWARE_MANIFEST_RESOURCE_ID code is used. When loading the dynamic library via LoadLibrary(Ex) (dynamic import of the library) the manifest with ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID code is used.
In particular, presence of the manifest is necessary if you write a dynamically loaded module that is loaded with host module rundll32, that uses new common controls library.
Besides this, for the module compatible with isolation policy the number of API calls such as IsolationAwareImageList_Create has been redefined. For this API to be redefined you need (for C/C++) to define the preprocessor directive :
#define ISOLATION_AWARE_ENABLED 1
before including windows.h.
VCL incompatibility fixes
Unfortunately, Borland VCL library (up to Delphi 6 Update 1 inclusively) contains seveeral problems that make it hard to use visual styles with standard and common controls in Windows. Mike Lischke has created a component Windows XP Theme Manager, which almost solves these problems for much control elements including TListView. Subclassing is used to avoid incompatible code in VCL and possibly painting of the control element. Another solutions can be to use ElPack controls which is not based on Windows standard control elements and thus is free of the mentioned problems at all.
Additional materials
C++ classes for dynamic loading of the theme manager and support for Visual Styles API in ATL/WTL components and a sample of WTL-application, written using these classes. Download .
Manifest as a resource and Delphi modules to link the manifest, also a sample of the module for dynamic loading of the theme manager. Download .
Sample application to explore Visual Styles API and it's source code in Delphi. Source codes also include a sample of working with Microsoft XML 2.0. I must note that I have not defined all constants for all elements and all states, but you can easily extend the application or create and complement the XML document, that defines the scheme of the visual style. Download .
References
Large part of information is available in MSDN online.
http://windowsxp.devx.com/downloads/default.asp
http://msdn.microsoft.com/library/en-us/dnwxp/html/xptheming.asp
http://msdn.microsoft.com/library/en-us/dnwxp/html/xpvisualstyles.asp
http://www.lischke-online.de/ThemeManager.html
It is also wise to periodically visit my developer's corner.
If you find a mistake in the article send an e-mail.
Thanks
This article would be impossible without participation of SWRUS developers community.
Special thanks to
Vladimir Romanov (ReGet Software, http://www.regetsoft.com/), that started to actively study Visual Styles (Themes) API for further use in his ATL/WTL-applications and published his notes and source code.
Igor Kokarev (WnSoft, http://wnsoft.com/), that started to actively study Visual Styles (Themes) API for further use in his Delphi applications and published his notes and source code. In particular the resource file is his creature.
Alexey Popov (Ghost Install, http://www.ginstall.com/), in this case for his information about differences in Whistler RC1 and RC2, and for his installation authoring tool Ghost Installer.
EldoS Group (EldoS, http://www.eldos.org/), for their ElPack components collection and for extending support of visual styles.
Mikhail Chernyshev (AllNetic CHMV Software, http://www.allnetic.com/), for their useful comments.
The Future Sound Of London (FSOL), Staind and Papa Roach bands for good music during authoring this article.
--------------------------------------------------------------------------------
Comments
It is important to note, that the format of manifest in Whistler RC1 is not compatible with Windows XP Release manifest format. In this article I target Windows XP and subsequent operating systems. Alexey Popov, the author of Ghost Installer, has developed a manifest that is compatible with both Whistler RC1, RC2, and Windows XP.
If you create the manifest to be included into one of your modules yourself, don't use carriage returns in XML-document. The manifest must be located in one line.
The element is in "hot" state, when it receives WM_MOUSEMOVE messages (mouse cursor is over the window and mouse messages are not captured).