I'm trying to create a Win32 application (with no other dependencies for GUI) that has a set of tabs and controls within those tabs. I have created the following reasonably minimal example following Raymond Chen's guidelines, which seem to contradict Microsoft's documentation, but either way it doesn't seem to affect drawing:
#include <Windows.h>
#include <windowsx.h>
#include <CommCtrl.h>
#include <uxtheme.h>
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY: {
PostQuitMessage(0);
return 0;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// All painting occurs here, between BeginPaint and EndPaint.
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW));
EndPaint(hwnd, &ps);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int main(int argc, char **argv) {
HINSTANCE hinst = GetModuleHandle(NULL);
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icex);
LOGFONT lf;
GetObject (GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &lf);
HFONT hfont = CreateFont (lf.lfHeight, lf.lfWidth,
lf.lfEscapement, lf.lfOrientation, lf.lfWeight,
lf.lfItalic, lf.lfUnderline, lf.lfStrikeOut, lf.lfCharSet,
lf.lfOutPrecision, lf.lfClipPrecision, lf.lfQuality,
lf.lfPitchAndFamily, lf.lfFaceName);
const wchar_t CLASS_NAME[] = L"Window Class";
WNDCLASS wc = { 0,
wndproc,
0,
0,
hinst,
NULL,
NULL,
NULL,
NULL,
(LPCSTR)CLASS_NAME
};
RegisterClass(&wc);
HWND window_hwnd = CreateWindow((LPCSTR)CLASS_NAME, "Tabs Example", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, hinst, NULL);
SendMessage(window_hwnd, WM_SETFONT, (WPARAM)hfont, TRUE);
HWND tab_hwnd = CreateWindow(WC_TABCONTROL, "", WS_CHILD | WS_VISIBLE, 0, 0, 640, 480, window_hwnd, NULL, hinst, NULL);
SendMessage(tab_hwnd, WM_SETFONT, (WPARAM)hfont, TRUE);
TCITEM item;
item.mask = TCIF_TEXT | TCIF_IMAGE;
item.iImage = -1;
item.pszText = "One!";
TabCtrl_InsertItem(tab_hwnd, 0, &item);
item.pszText = "Two!";
TabCtrl_InsertItem(tab_hwnd, 1, &item);
item.pszText = "Three!";
TabCtrl_InsertItem(tab_hwnd, 2, &item);
HWND groupbox_hwnd = CreateWindow(WC_BUTTON, "Groupbox", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_GROUPBOX, 10, 32, 600, 400, window_hwnd, NULL, hinst, NULL);
SendMessage(groupbox_hwnd, WM_SETFONT, (WPARAM)hfont, TRUE);
HWND text_hwnd = CreateWindow(WC_STATIC, "Static Text", WS_CHILD | WS_VISIBLE | SS_CENTER, 100, 100, 100, 100, window_hwnd, NULL, hinst, NULL);
SendMessage(text_hwnd, WM_SETFONT, (WPARAM)hfont, TRUE);
ShowWindow(window_hwnd, SW_NORMAL);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
The problem is that, when it is run, it draws the tab content incorrectly. Each control in the tab has a background with the window background color:
(note the incorrect background behind the "Groupbox" text and "Static Text")
I have tried using a transparent background using WM_CTLCOLORSTATIC
like so:
case WM_CTLCOLORSTATIC: {
HDC hEdit = (HDC)wParam;
SetBkMode(hEdit, TRANSPARENT);
SetTextColor(hEdit, RGB(0, 0, 0));
return GetStockObject(HOLLOW_BRUSH);
}
But that results in incorrect rendering of the groupbox:
(note the groupbox divider being drawn behind the "Groupbox" text)
Also, I have tried reordering the tab control to the bottom, which makes the Groupbox display incorrectly:
SetWindowPos(tab_hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
(note the grey background under the whole groupbox)
For most controls WM_PRINTCLIENT
will do.
case WM_PRINTCLIENT:
{
// Paint area outside tabs control here if needed.
...
// Now paint tabs area
RECT r1, r2;
GetWindowRect(hwnd, &r1);
GetWindowRect(tags_hwnd, &r2);
POINT old;
SetWindowOrgEx((HDC)wparam, r1.left - r2.left, r1.top - r2.top, &old);
LRESULT result = SendMessage(tabs_hwnd, WM_PRINTCLIENT, wparam, lparam);
SetWindowOrgEx((HDC)wparam, old.x, old.y, 0);
return result;
}
WM_PRINTCLIENT
should paint whole client area, so there is a reason that child dialog or helper window are often used for this.
Unfortunately STATIC control doesn't call WM_PRINTWINDOW
, WM_CTLCOLORSTATIC
can be used instead.
We can delegate some work to tabs control, but it will not work correctly for gradient or bitmap backround..
case WM_CTLCOLORSTATIC:
return SendMessage(tabs_hwnd, WM_CTLCOLORSTATIC, wparam, lparam);
Edit:
Removed simplified (and controversial) implementation for gradient and or bitmapped background.
Full version needs following steps:
WM_PRINTCLIENT
.Screenshot:
This program doesn't use dialog templates. Controls are created by code and automaticly layed out by geometry manager in resizable window.
Usung XP because it has tabs with gradient background.
EDIT: Full Approach In Context:
For the program presented in the question, the WndProc function can be replaced with the following (note the global variables, as well) to show correct output (I did not have an XP environment to fully confirm correct rendering with gradients, but it seems fine on 11):
HWND tab_hwnd;
HBRUSH hBrush = NULL;
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY: {
PostQuitMessage(0);
return 0;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// All painting occurs here, between BeginPaint and EndPaint.
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW));
EndPaint(hwnd, &ps);
return 0;
}
case WM_CTLCOLOR:
case WM_CTLCOLORBTN:
case WM_CTLCOLORSTATIC: {
// create a brush that copies the tab's body
// NOTE: this brush would have to be recreated when the window is resized, this is just an example
if (!hBrush) {
RECT rc;
GetWindowRect(tab_hwnd, &rc);
HDC hdc = GetDC(tab_hwnd);
HDC hdc_new = CreateCompatibleDC(hdc); // create a new device context to draw our tab into
HBITMAP hbmp = CreateCompatibleBitmap(hdc, rc.right - rc.left, rc.bottom - rc.top); // create a new bitmap to draw the tab into
HBITMAP hbmp_old = (HBITMAP)(SelectObject(hdc_new, hbmp)); // replace the device context's bitmap with our new bitmap
SendMessage(tab_hwnd, WM_PRINTCLIENT, hdc_new, (LPARAM)(PRF_ERASEBKGND | PRF_CLIENT | PRF_NONCLIENT)); // draw the tab into our bitmap
hBrush = CreatePatternBrush(hbmp); // create a brush from the bitmap
SelectObject(hdc_new, hbmp_old); // replace the bitmap in the device context
DeleteObject(hbmp);
DeleteDC(hdc_new);
ReleaseDC(tab_hwnd, hdc);
}
// use our previously created brush as a background for the control
RECT rc2;
HDC hEdit = (HDC)wParam;
SetBkMode(hEdit, TRANSPARENT);
GetWindowRect(hwnd, &rc2); // get control's position
MapWindowPoints(NULL, tab_hwnd, (LPPOINT)(&rc2), 2); // convert coordinates into tab's space
SetBrushOrgEx(hEdit, rc2.left, rc2.top, NULL); // set brush origin to our control's position
return hBrush;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}