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.