To use the new version 6.0 of the common controls, an application must explicitly request the new version by providing an application manifest. The application manifest can be provided to Windows XP as a discrete file or as a resource attached to the executable. When provided as a discrete file, the manifest file must be located in the same directory as the executable and have the same name as the executable with ".manifest" appended to the name. For example, the manifest file for foobar.exe would be foobar.exe.manifest. Manifest files use XML syntax. The full schema of a manifest file is defined in the latest Windows Platform SDK. Figure 4 shows a manifest file for the application MyApp that requests the new common control library. The first few lines describe the manifest's XML schema. The first assemblyIdentity element describes the application that is consuming the side-by-side assembly. The name attribute specifies the application's name and the version attribute specifies the application's version in a four part version number. The remainder of the attributes are fairly standard, specifying the application type, Win32, the processorArchitecture, x86, and a description tag. The dependency element lists the specific dependencies and their versions that the application wants to use. In Figure 4, MyApp is requesting the common control library version 6.0. The common control library is specified in the single dependentAssembly element listed under the dependency element. The only new attribute in the description of the common control library is the publicKeyToken. This field specifically describes the assembly. You can query the proper values for this assembly and other side-by-side assemblies by examining the assembly manifest files stored in the WinSxS directory under the Windows system directory. Legacy applications can use the new common control library, and therefore acquire the new look of Windows XP as well as the additional functionality of the new common controls by providing a manifest file in the same directory as the application itself. In fact, it's an interesting exercise to create manifest files for legacy applications on your system. Of course, there is a possibility that enabling a third party's applications to use the new common control library will introduce bugs into the applications, so perform this experiment at your own risk. Figure 4 Manifest File for MyApp.exe An application that uses the themed ctls. Figure 5 and Figure 6 show two views of an application I wrote a number of years ago. Figure 5 shows the application without a mainifest file in the application's directory while Figure 6 shows the application launched with a manifest file in its directory to enable the new common control library. Notice how the controls in the window take on the new themed look familiar to users of Windows XP. Figure 6 App with a Manifest File Themes In Figure 6, notice that even using the new common control library, the upper right button in the window doesn't take on the new look of Windows XP. The reason is that the button in question is an owner-drawn button and therefore doesn't pick up the new look by default. In the past, developers only had to worry about updating the look of their owner-drawn controls when a new version of Windows changed the default look of the system controls. Now, with the release of Windows XP, the user can change the look of the controls simply by changing the theme in the control panel. This creates a problem for application-drawn controls since you can't predict what theme the user will choose. Fortunately for programmers who want to create custom or owner-drawn controls, Windows XP does provide functions to render the themes so that their controls fit into the currently selected theme. Drawing custom controls using the theme engine is surprisingly easy. First, query to see if themes are enabled for the application using the IsThemeActive. If this function returns true, open an HTHEME handle to the theme data for the control that most closely matches the type of control using the function OpenThemeData. For example, if your control looks similar to a button, the call would look like this: HTHEME hTheme; if (IsThemeActive()) hTheme = OpenThemeData (hWnd, L"button")); else hTheme = 0; // Need to draw the old fashioned way. The name of the window class can actually be a semicolon-delimited list of multiple window class names. OpenThemeData will search the list of class names to find the first class with theme data. You should also be careful to see if a theme handle is actually returned. Some themes may not have a theme for the window classes requested. If zero is returned, the control must be drawn with standard GDI or GDI+ calls. Note that the string passed to OpenThemeData is hardcoded as a Unicode string. The Theme API has only a few functions that have string parameters, and those only support Unicode strings. Once you have a theme handle, the control is drawn using a combination of the theme drawing functions listed here. For each call to the drawing functions, you pass the part of the control you want to draw and its state, such as normal, hot, or selected. The theme API does the rest. DrawThemeBackground Draws background image for the control DrawThemeEdge Draws the edges of a rectangle DrawThemeIcon Draws the icons used within a control DrawThemeLine Draws a line within a specified rectangle DrawThemeText Draws text with the proper theme font and size For simple controls, all that is necessary to completely render the control is to call DrawThemeBackground and DrawThemeText. The code in Figure 7 draws a control that mimics a standard pushbutton. The neat thing about this code is how short and simple it is. The routine is called WM_ PAINT and it's used for mouse messages such as WM_ LBUTTONDOWN, WM_MOUSEHOVER, and WM_MOUSELEAVE. The DrawControl code isn't concerned with the control colors or font for the text since all this is taken care of by the theme engine. When the control is destroyed, it should call CloseThemeData to close the theme data handle returned from OpenThemeData. You should also monitor for WM_THEMECHANGED messages, which are broadcast when the user changes the theme. When one is received, you should close your current theme handle, query to see if themes are supported for your application, and if so, reopen a theme handle and redraw your themed controls. There are a number of other theme functions that let you query the size and features of various theme components. In addition, an application can dictate whether themes are used to draw its controls, controls within Web pages displayed within the application, and non-client areas by invoking the function SetThemeAppProperties. This function takes three self-explanatory flags: STAP_ALLOW_CONTROLS, STAP_ALLOW_WEBCONTENT, and STAP_ALLOW_NONCLIENT that can be OR'ed combined to enable the new theme look. Themes are enabled by default, so you only need to call this function to disable the selected portion of the theme by not passing one of the flags. Themes are a double-edged sword for Windows XP-based apps. The fact that the user can change the themes adds a bit of work for the developer, but the handy functionality of the theme API makes drawing controls in the style of the current theme fairly easy. Figure 7 //覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 // DrawControl - Draws the control // LRESULT DrawControl (HWND hWnd, HDC hdc, HTHEME hTheme, int nState, int nFocus) { RECT rect; WCHAR szText[128]; HRESULT hr; HDC hdcInt; // If no theme, draw the control manually. if (hTheme == NULL) return DrawTheOldWay (hWnd, hdc); // Sometimes we're called with an HDC, sometimes not. if (hdc) hdcInt = hdc; else hdcInt = GetDC (hWnd); // Get the client rectangle for the control GetClientRect (hWnd, &rect); // Draw the background. States include PBS_NORMAL, _HOT, and _PRESSED hr = DrawThemeBackground (hTheme, hdcInt, BP_PUSHBUTTON, nState, &rect, &rect); // Draw the window text GetWindowText (hWnd, szText, dim (szText)); hr = DrawThemeText (hTheme, hdcInt, BP_PUSHBUTTON, PBS_NORMAL, szText, -1, DT_CENTER|DT_SINGLELINE|DT_VCENTER, 0, &rect); // If button has the focus, draw the dotted rect inside the button. if (nFocus) { rect.left += 3; rect.top += 3; rect.right -= 3; rect.bottom -= 3; DrawFocusRect (hdcInt, &rect); } if (!hdc) ReleaseDC (hWnd, hdcInt); return 0; }