Using Theme APIs to draw the border of a control
In this article, we will take a look at using the Theme APIs to draw the border of a control using the current visual style.
Each window class is responsible for defining its appearance, which includes the client and non-client portions of the window. A window’s border is typically part of its non-client area. Windows will handle drawing the border of a window when non-client window messages such as WM_NCPAINT are passed to the DefWindowProc function.
However, DefWindowProc will use the “classic” border appearance for child windows. Controls themselves are responsible for drawing their borders if the appearance defined for the current visual style is desired.
In Windows XP and later, COMCTL32.DLL version 6 provides an implementation of each of the standard Windows controls (Edit, Button, etc.) and common controls (List View, Tree View, etc.) that are rendered using the current visual style, if one is currently active. It does so by calling the Theme APIs when rendering the contents of the control.
The following code demonstrates using the Theme API to draw the border of an edit control in various states.
int cy = 60;
RECT rc = {10, 10, 300, cy};
int nPartId = EP_EDITBORDER_NOSCROLL;
HTHEME hTheme = OpenThemeData(hWnd, L"EDIT");
if (hTheme)
{
DrawThemeBackground(hTheme, hdc, nPartId, EPSN_DISABLED, &rc, NULL);
OffsetRect(&rc, 0, cy);
DrawThemeBackground(hTheme, hdc, nPartId, EPSN_FOCUSED, &rc, NULL);
OffsetRect(&rc, 0, cy);
DrawThemeBackground(hTheme, hdc, nPartId, EPSN_HOT, &rc, NULL);
OffsetRect(&rc, 0, cy);
DrawThemeBackground(hTheme, hdc, nPartId, EPSN_NORMAL, &rc, NULL);
CloseThemeData(hTheme);
}
The following code demonstrates using the Theme API to draw the border of a rich edit control using the Edit theme class. This code can be added to a Win32 Windows project created in Visual Studio 2010 and Visual Studio 2012 using the following steps:
1. Add the following to stdafx.h:
#include <Richedit.h>
#include <CommCtrl.h>
#include <Uxtheme.h>
#include <vsstyle.h>
#include <vssym32.h>
2. Replace the implementation of the InitInstance function with the following:
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HINSTANCE hInstRE = LoadLibrary(L"MSFTEDIT.DLL");
HWND hWnd = CreateWindowEx(WS_EX_APPWINDOW,
szWindowClass,
szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
NULL, NULL, hInstance, NULL);
DWORD dwStyle = WS_CHILD |
WS_VISIBLE |
WS_VSCROLL |
WS_HSCROLL |
ES_MULTILINE;
HWND hWndRE = CreateWindowEx(WS_EX_CLIENTEDGE,
MSFTEDIT_CLASS,
L"Rich Edit Control",
dwStyle,
10, 10, 300, 300,
hWnd, NULL, hInstRE, NULL);
SetWindowSubclass(hWndRE, RichEditSubclassProc, 1, NULL);
SendMessage(hWndRE, WM_THEMECHANGED, 0, 0);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
3. Add the following functions to the project:
void DrawThemedFrame(HWND hWnd, HRGN hrgnUpdate, HTHEME hTheme)
{
int idPart = EP_EDITBORDER_HVSCROLL;
int idState = EPSHV_NORMAL;
int cxEdge = GetSystemMetrics(SM_CXEDGE);
int cyEdge = GetSystemMetrics(SM_CYEDGE);
if (GetFocus() == hWnd)
{
idState = EPSHV_FOCUSED;
}
if (!IsWindowEnabled(hWnd))
{
idState = EPSHV_DISABLED;
}
HDC hDC = GetWindowDC(hWnd);
if (hDC)
{
//
// Get the border size from the theme
int cxBorder, cyBorder;
GetThemeInt(hTheme, idPart, idState, TMT_BORDERSIZE, &cxBorder);
cyBorder = cxBorder;
//
// Get the window coordinates
RECT rcWindow;
GetWindowRect(hWnd, &rcWindow);
//
// Create an update region without the client edge to pass to
// DefWindowProc
InflateRect(&rcWindow, -cxEdge, -cyEdge);
HRGN hrgn = CreateRectRgnIndirect(&rcWindow);
if (hrgnUpdate)
{
CombineRgn(hrgn, hrgnUpdate, hrgn, RGN_AND);
}
//
// Zero origin the rect
OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top);
//
// Clip our drawing to the non-client edge
OffsetRect(&rcWindow, cxEdge, cyEdge);
ExcludeClipRect(hDC,
rcWindow.left,
rcWindow.top,
rcWindow.right,
rcWindow.bottom);
InflateRect(&rcWindow, cxEdge, cyEdge);
DrawThemeBackground(hTheme, hDC, idPart, idState, &rcWindow, 0);
//
// Fill with the control's brush since the theme border may not be as
// thick as the client edge
if (cxBorder < cxEdge && cyBorder < cyEdge)
{
InflateRect(&rcWindow, cxBorder-cxEdge, cyBorder-cyEdge);
FillRect(hDC, &rcWindow, GetSysColorBrush(COLOR_WINDOW));
}
//
// Let DefWindowProc to the rest, excluding the border we just painted
DefWindowProc(hWnd, WM_NCPAINT, (WPARAM)hrgn, 0);
DeleteObject(hrgn);
ReleaseDC(hWnd, hDC);
}
}
LRESULT CALLBACK RichEditSubclassProc(HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData)
{
HTHEME hTheme = (HTHEME)GetWindowLongPtr(hWnd, GWLP_USERDATA);
switch (uMsg)
{
case WM_DESTROY:
RemoveWindowSubclass(hWnd, RichEditSubclassProc, 1);
CloseThemeData(hTheme);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)NULL);
break;
case WM_SYSCOLORCHANGE:
case WM_SETFOCUS:
case WM_KILLFOCUS:
case WM_ENABLE:
if (hTheme)
DrawThemedFrame(hWnd, NULL, hTheme);
break;
case WM_THEMECHANGED:
if (hTheme)
{
CloseThemeData(hTheme);
hTheme = NULL;
}
hTheme = OpenThemeData(hWnd, L"EDIT");
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)hTheme);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_NCPAINT:
if (hTheme)
{
DrawThemedFrame(hWnd, (HRGN)wParam, hTheme);
return 0;
}
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
4. Add uxtheme.lib and comctl32.lib to the import libraries in the project’s Linker settings.
The resulting project creates a Rich Edit control that is drawn using the theme data for edit controls when themes are active. Please note this sample demonstrates the minimum code needed to draw a themed border for a control and does not perform any error handling.
Comments
- Anonymous
July 24, 2013
Good write up. I'm glad there is still native / Win32 code postings coming from Microsoft.