Hacking the Overall Background Color of a Windows Tab Control (Hideous)

by Glenn Slayden. please contact me for corrections or comments
2003-11-03

Q: How do you change the background color of a Windows tab control? I don't mean the background of the individual tabs but rather the overall background behind the control, such as the empty tab space (figure a.).

A: The short answer is, you can't. This part of the painting is integrated into the WM_PAINT processing with the rest of the control, so there's no message you can subclass to affect only the background painting. That area will always be painted using the COLOR_BTNFACE color, with the exceptions as follows: I found some evidence that people have gotten this to work by using property sheets instead of the tab control, which suggests that the (internal) tab control code is not exactly independent of wrapping bodies, as it should be. Similar observations have been made regarding XP "theming" support and the tab control (offite link: PN Devlog). Intercepting the WM_ERASEBKGND message is of no use with this control.

After having pulled out most of the stops regarding subclassing, to no avail, I had to resort to a much more hideous solution, as follows: ... WNDPROC g_pfnTab; HBRUSH g_hbrBkgnd = CreateSolidBrush(0); // desired background brush ... LRESULT CALLBACK SubclassTabProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam) { if (uMsg==WM_PAINT) { LRESULT l = CallWindowProc(g_pfnTab,hwnd,uMsg,wParam,lParam); HDC hdc = GetDC(hwnd); COLORREF cr = GetSysColor(COLOR_BTNFACE); SelectObject(hdc,g_hbrBkgnd); RECT r; GetClientRect(hwnd,&r); ExtFloodFill(hdc,r.right-3,3,cr,FLOODFILLSURFACE); SelectObject(hdc,GetStockObject(WHITE_BRUSH)); ReleaseDC(hwnd,hdc); return l; } return CallWindowProc(g_pfnTab,hwnd,uMsg,wParam,lParam); } ... HWND hwnd_tab = CreateWindow(WC_TABCONTROL,L"",WS_CHILD,10,10,300,300,hWnd,(HMENU)ID_TABCTRL,g_hInst,0); g_pfnTab = (WNDPROC)SetWindowLong(hwnd_tab,GWL_WNDPROC,(long)SubclassTabProc); ... As you can see from figure b., this solution is not perfect, but it was easy and I'm moving on. No, I'm not proud to post this. It's really sad. I originally was going to try to get CallWindowProc to do its painting into a memory DC, in order to eliminate any flicker, but then I realized that there's no way to replace the DC which the internal WM_PAINT processing will fetch when it calls BeginPaint. And I theorized that the XP GDI caching hopefully would take care of any flicker, and by my observation, it does a good enough job. Pronounced flicker is visible when drag-resizing the control.

If somebody finds out a better way to do this, please contact me. Glenn out.
2007-11-02 UPDATE -----Original Message----- From: ****** Sent: Saturday, November 03, 2007 5:24 PM To: glenn@glennslayden.com Subject: Re:Tab Control I coded something like that and it's working ;] first: WNDPROC tabctl = reinterpret_cast<WNDPROC>(SetWindowLong(htab, GWL_WNDPROC, reinterpret_cast<long>(TabProc))); in tabctl proc: switch (imsg) { case WM_ERASEBKGND: GetClientRect(hwnd, &rect); FillRect(((HDC)wParam), &rect, CreateSolidBrush(RGB(255,255,255))); UpdateWindow(hwnd); return TRUE; }; and in parent window on WM_DRAWITEM: TCITEM tabitem; TCHAR buff[30] = {0}; HWND htab = GetDlgItem(hwnd, IDC_TABCTL); if ( htab == dis->hwndItem ) { FillRect(dis->hDC, &dis->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW)); SetBkMode(dis->hDC, TRANSPARENT); memset(&tabitem, 0, sizeof(TCITEM)); tabitem.mask = TCIF_TEXT; tabitem.pszText = buff; tabitem.cchTextMax = 30; SendMessage(htab1, TCM_GETITEM, static_cast<WPARAM>(dis->itemID), reinterpret_cast<LPARAM>(&tabitem)); TextOut(dis->hDC, (dis->rcItem.left + 6), (dis->rcItem.top + 2), tabitem.pszText, lstrlen(tabitem.pszText)); Thanks See you...