问题
tl;dr for those who read the old question: new circumstances have caused me to look a little deeper and I've found that this affects bare Tab controls on their own; I've adjusted the question to compensate. If I should remove the old question text entirely, please let me know.
Here is a screenshot from a program I'm working on to test a wrapper library I'm also working on:
If you look closely, the window on the right appears blocky, while the window on the left (the standard Windows Explorer file properties property sheet) looks smooth. At first I thought this was just my video card, but other people see it too, even going so far as to adjust the image colors to demonstrate the blockiness:
Recent bugs have led me to produce another program to test tab controls on their own. In the following screenshot, the picture on the left is a real tab control, and the picture on the right is the tab control rendered with WM_PRINTCLIENT
(the red is from where the tab control called my own WM_PRINTCLIENT
):
Both of these render blockily:
Why is the tab control in my own software behaving like this but not Windows's own tabs?
This is Windows XP because it has the gradient to test. I need to target XP and newer, though I might drop XP soon anyway. I don't want to hide a bug in my code or setup behind a minimum system requirement.
This test program is below. Run it passing comctl6
as an argument. Note that in the interest of brevity and being a little program to test things it does no error checking.
Thanks!
// 18 may 2015
// based on wintabparentwinebug.c 3 may 2015
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define CINTERFACE
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include <windows.h>
#include <commctrl.h>
#include <stdint.h>
#include <uxtheme.h>
#include <string.h>
#include <wchar.h>
#include <windowsx.h>
#include <vsstyle.h>
#include <vssym32.h>
#include <stdarg.h>
#include <oleacc.h>
#include <stdio.h>
void die(char *s)
{
// TODO
}
void initCommonControls(BOOL);
HWND mainwin;
HWND tab;
#define BGCOLOR RGB(0x0A, 0x24, 0x6A)
#define PCOLOR RGB(0x6A, 0x24, 0x0A)
LRESULT CALLBACK wndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
NMHDR *nm = (NMHDR *) lParam;
PAINTSTRUCT ps;
HDC dc;
POINT prev;
RECT r;
HBRUSH b;
switch (uMsg) {
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_PAINT:
dc = BeginPaint(hwnd, &ps);
SetWindowOrgEx(dc, -240, -20, &prev);
SendMessage(tab, WM_PRINTCLIENT, (WPARAM) dc, PRF_CLIENT);
SetWindowOrgEx(dc, prev.x, prev.y, NULL);
EndPaint(hwnd, &ps);
{COLORREF r;
r=GetSysColor(COLOR_ACTIVECAPTION);
printf("%I32X\n", r);}
return 0;
case WM_PRINTCLIENT:
// the tab control sends this to draw the background of the area where the tab buttons are
b = CreateSolidBrush(PCOLOR);
GetClientRect(hwnd, &r);
FillRect((HDC) wParam, &r, b);
DeleteObject(b);
return 0;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
static void makeWindows(void)
{
mainwin = CreateWindowExW(0,
L"mainwin", L"Full Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
500, 500,
NULL, NULL, GetModuleHandle(NULL), NULL);
// create the tab as a child of the empty window...
tab = CreateWindowExW(0,
WC_TABCONTROLW, L"",
TCS_TOOLTIPS | WS_TABSTOP | WS_CHILD | WS_VISIBLE,
20, 20, 200, 440,
mainwin, (HMENU) 100, GetModuleHandle(NULL), NULL);
}
void addTab(WCHAR *name)
{
TCITEMW item;
LRESULT n;
n = SendMessageW(tab, TCM_GETITEMCOUNT, 0, 0);
ZeroMemory(&item, sizeof (TCITEMW));
item.mask = TCIF_TEXT;
item.pszText = name;
SendMessageW(tab, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item));
}
int main(int argc, char *argv[])
{
WNDCLASSW wc;
MSG msg;
HBRUSH b;
initCommonControls(argc > 1 && strcmp(argv[1], "comctl6") == 0);
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
// if printing client doesn't print the tab background, this color will bleed through instead
b = CreateSolidBrush(BGCOLOR);
wc.hbrBackground = b;
RegisterClassW(&wc);
makeWindows();
addTab(L"Page 1");
addTab(L"Page 2");
ShowWindow(mainwin, SW_SHOWDEFAULT);
UpdateWindow(mainwin);
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
static const char manifest[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n version=\"1.0.0.0\"\n processorArchitecture=\"*\"\n name=\"CompanyName.ProductName.YourApplication\"\n type=\"win32\"\n/>\n<description>Your application description here.</description>\n<dependency>\n <dependentAssembly>\n <assemblyIdentity\n type=\"win32\"\n name=\"Microsoft.Windows.Common-Controls\"\n version=\"6.0.0.0\"\n processorArchitecture=\"*\"\n publicKeyToken=\"6595b64144ccf1df\"\n language=\"*\"\n />\n </dependentAssembly>\n</dependency>\n</assembly>\n";
static ULONG_PTR comctlManifestCookie;
static HMODULE comctl32;
void initCommonControls(BOOL comctl6)
{
WCHAR temppath[MAX_PATH + 1];
WCHAR filename[MAX_PATH + 1];
HANDLE file;
DWORD nExpected, nGot;
ACTCTX actctx;
HANDLE ac;
INITCOMMONCONTROLSEX icc;
FARPROC f;
// this is listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason
BOOL (*WINAPI ficc)(const LPINITCOMMONCONTROLSEX);
if (comctl6) {
if (GetTempPathW(MAX_PATH + 1, temppath) == 0)
die("getting temporary path for writing manifest file");
if (GetTempFileNameW(temppath, L"manifest", 0, filename) == 0)
die("getting temporary filename for writing manifest file");
file = CreateFileW(filename, GENERIC_WRITE,
0, // don't share while writing
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == NULL)
die("creating manifest file");
nExpected = (sizeof manifest / sizeof manifest[0]) - 1; // - 1 to omit the terminating null character)
if (WriteFile(file, manifest, nExpected, &nGot, NULL) == 0)
die("writing manifest file");
if (nGot != nExpected)
die("short write to manifest file");
if (CloseHandle(file) == 0)
die("closing manifest file (this IS an error here because not doing so will prevent Windows from being able to use the manifest file in an activation context)");
ZeroMemory(&actctx, sizeof (ACTCTX));
actctx.cbSize = sizeof (ACTCTX);
actctx.dwFlags = ACTCTX_FLAG_SET_PROCESS_DEFAULT;
actctx.lpSource = filename;
ac = CreateActCtx(&actctx);
if (ac == INVALID_HANDLE_VALUE)
die("creating activation context for synthesized manifest file");
if (ActivateActCtx(ac, &comctlManifestCookie) == FALSE)
die("activating activation context for synthesized manifest file");
}
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
icc.dwICC = ICC_TAB_CLASSES;
comctl32 = LoadLibraryW(L"comctl32.dll");
if (comctl32 == NULL)
die("loading comctl32.dll");
f = GetProcAddress(comctl32, "InitCommonControlsEx");
if (f == NULL)
die("loading InitCommonControlsEx()");
ficc = (BOOL (*WINAPI)(const LPINITCOMMONCONTROLSEX)) f;
if ((*ficc)(&icc) == FALSE)
die("initializing Common Controls (comctl32.dll)");
}
Original question:
Title: Why are CreateCompatibleDC(), CreateCompatibleBitmap(), and WM_PRINTCLIENT rendering the parent tab control background in a blocky way?
My container window clss draws, in its WM_PAINT
, whatever WM_PRINTCLIENT
draws for its parent (that isn't itself a container). So for instance, if its background is a themed tab control, it will draw the themed tab control background as its painted content. The window on the right in the following image is an example:
If you look closely, the window on the right appears blocky, while the window on the left (the standard Windows Explorer file properties property sheet) looks smooth. At first I thought this was just my video card, but other people see it too, even going so far as to adjust the image colors to demonstrate the blockiness:
The essence of the code that paints is simple:
// not actual code, just algorithm to demonstrate what I'm *thinking* is supposed to happen
dc = BeginPaint(hwnd, &ps);
parent = GetAncestor(hwnd, GA_PARENT);
GetClientRect(parent, &r);
cdc = CreateCompatibleDC(dc);
bitmap = CreateCompatibleBitmap(dc, r.right - r.left, r.bottom - r.top);
SelectObject(cdc, pd->bitmap);
SendMessageW(parent, WM_PRINTCLIENT, (WPARAM) cdc, PRF_CLIENT);
updateRect = ps.rcPaint;
parentRect = updateRect;
MapWindowRect(hwnd, parent, &parentRect);
BitBlt(dc, updateRect.left, updateRect.top, updateRect.right - updateRect.left, updateRect.bottom - updateRect.top,
cdc, parentRect.left, parentRect.top,
SRCCOPY);
I don't see anything here that would affect the quality of the painted image, unless there's something about compatible bitmaps that I'm missing? Can anyone else explain? I really don't know what to do about this, other than reimplement the drawing for every possible parent of a container and hope that helps.
Thanks.
Here is the full code for the container:
// 26 april 2015
#include "uipriv_windows.h"
#define containerClass L"libui_uiContainerClass"
HWND initialParent;
struct container {
HWND hwnd;
uiContainer *parent;
int hidden;
HBRUSH brush;
};
static HWND realParent(HWND hwnd)
{
HWND parent;
int class;
parent = hwnd;
for (;;) {
parent = GetAncestor(parent, GA_PARENT);
// skip groupboxes; they're (supposed to be) transparent
// skip uiContainers; they don't draw anything
class = windowClassOf(parent, L"button", containerClass, NULL);
if (class != 0 && class != 1)
break;
}
return parent;
}
struct parentDraw {
HDC cdc;
HBITMAP bitmap;
HBITMAP prevbitmap;
};
static void parentDraw(HDC dc, HWND parent, struct parentDraw *pd)
{
RECT r;
if (GetClientRect(parent, &r) == 0)
logLastError("error getting parent's client rect in parentDraw()");
pd->cdc = CreateCompatibleDC(dc);
if (pd->cdc == NULL)
logLastError("error creating compatible DC in parentDraw()");
pd->bitmap = CreateCompatibleBitmap(dc, r.right - r.left, r.bottom - r.top);
if (pd->bitmap == NULL)
logLastError("error creating compatible bitmap in parentDraw()");
pd->prevbitmap = SelectObject(pd->cdc, pd->bitmap);
if (pd->prevbitmap == NULL)
logLastError("error selecting bitmap into compatible DC in parentDraw()");
SendMessageW(parent, WM_PRINTCLIENT, (WPARAM) (pd->cdc), PRF_CLIENT);
}
static void endParentDraw(struct parentDraw *pd)
{
if (SelectObject(pd->cdc, pd->prevbitmap) != pd->bitmap)
logLastError("error selecting previous bitmap back into compatible DC in endParentDraw()");
if (DeleteObject(pd->bitmap) == 0)
logLastError("error deleting compatible bitmap in endParentDraw()");
if (DeleteDC(pd->cdc) == 0)
logLastError("error deleting compatible DC in endParentDraw()");
}
// see http://www.codeproject.com/Articles/5978/Correctly-drawn-themed-dialogs-in-WinXP
static HBRUSH getControlBackgroundBrush(HWND hwnd, HDC dc)
{
HWND parent;
RECT hwndScreenRect;
struct parentDraw pd;
HBRUSH brush;
parent = realParent(hwnd);
parentDraw(dc, parent, &pd);
brush = CreatePatternBrush(pd.bitmap);
if (brush == NULL)
logLastError("error creating pattern brush in getControlBackgroundBrush()");
endParentDraw(&pd);
// now figure out where the control is relative to the parent so we can align the brush properly
if (GetWindowRect(hwnd, &hwndScreenRect) == 0)
logLastError("error getting control window rect in getControlBackgroundBrush()");
// this will be in screen coordinates; convert to parent coordinates
mapWindowRect(NULL, parent, &hwndScreenRect);
if (SetBrushOrgEx(dc, -hwndScreenRect.left, -hwndScreenRect.top, NULL) == 0)
logLastError("error setting brush origin in getControlBackgroundBrush()");
return brush;
}
static void paintContainerBackground(HWND hwnd, HDC dc, RECT *paintRect)
{
HWND parent;
RECT paintRectParent;
struct parentDraw pd;
parent = realParent(hwnd);
parentDraw(dc, parent, &pd);
paintRectParent = *paintRect;
mapWindowRect(hwnd, parent, &paintRectParent);
if (BitBlt(dc, paintRect->left, paintRect->top, paintRect->right - paintRect->left, paintRect->bottom - paintRect->top,
pd.cdc, paintRectParent.left, paintRectParent.top,
SRCCOPY) == 0)
logLastError("error drawing parent background over uiContainer in paintContainerBackground()");
endParentDraw(&pd);
}
// from https://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing and https://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx
// this X value is really only for buttons but I don't see a better one :/
#define winXPadding 4
#define winYPadding 4
// abort the resize if something fails and we don't have what we need to do a resize
static HRESULT resize(uiContainer *cc, RECT *r)
{
struct container *c = (struct container *) (uiControl(cc)->Internal);
uiSizing d;
uiSizingSys sys;
HDC dc;
HFONT prevfont;
TEXTMETRICW tm;
SIZE size;
dc = GetDC(c->hwnd);
if (dc == NULL)
return logLastError("error getting DC in resize()");
prevfont = (HFONT) SelectObject(dc, hMessageFont);
if (prevfont == NULL)
return logLastError("error loading control font into device context in resize()");
ZeroMemory(&tm, sizeof (TEXTMETRICW));
if (GetTextMetricsW(dc, &tm) == 0)
return logLastError("error getting text metrics in resize()");
if (GetTextExtentPoint32W(dc, L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &size) == 0)
return logLastError("error getting text extent point in resize()");
sys.baseX = (int) ((size.cx / 26 + 1) / 2);
sys.baseY = (int) tm.tmHeight;
sys.internalLeading = tm.tmInternalLeading;
if (SelectObject(dc, prevfont) != hMessageFont)
return logLastError("error restoring previous font into device context in resize()");
if (ReleaseDC(c->hwnd, dc) == 0)
return logLastError("error releasing DC in resize()");
d.xPadding = uiDlgUnitsToX(winXPadding, sys.baseX);
d.yPadding = uiDlgUnitsToY(winYPadding, sys.baseY);
d.sys = &sys;
uiContainerResizeChildren(cc, r->left, r->top, r->right - r->left, r->bottom - r->top, &d);
return S_OK;
}
static LRESULT CALLBACK containerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
uiContainer *cc;
struct container *c;
CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;
HWND control;
NMHDR *nm = (NMHDR *) lParam;
WINDOWPOS *wp = (WINDOWPOS *) lParam;
RECT r;
HDC dc;
PAINTSTRUCT ps;
cc = uiContainer(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
if (cc == NULL)
if (uMsg == WM_NCCREATE)
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) (cs->lpCreateParams));
// DO NOT RETURN DEFWINDOWPROC() HERE
// see the next block of comments as to why
// instead, we simply check if c == NULL again later
switch (uMsg) {
// these must always be run, even on the initial parent
// why? http://blogs.msdn.com/b/oldnewthing/archive/2010/03/16/9979112.aspx
case WM_COMMAND:
// bounce back to the control in question
// except if to the initial parent, in which case act as if the message was ignored
control = (HWND) lParam;
if (control != NULL && IsChild(initialParent, control) == 0)
return SendMessageW(control, msgCOMMAND, wParam, lParam);
break; // fall through to DefWindowProcW()
case WM_NOTIFY:
// same as WM_COMMAND
control = nm->hwndFrom;
if (control != NULL && IsChild(initialParent, control) == 0)
return SendMessageW(control, msgNOTIFY, wParam, lParam);
break;
// these are only run if c is not NULL
case WM_CTLCOLORSTATIC:
case WM_CTLCOLORBTN:
if (cc == NULL)
break;
c = (struct container *) (uiControl(cc)->Internal);
if (c->brush != NULL)
if (DeleteObject(c->brush) == 0)
logLastError("error deleting old background brush in containerWndProc()");
/*TODO // read-only TextFields and Textboxes are exempt
// this is because read-only edit controls count under WM_CTLCOLORSTATIC
if (windowClassOf((HWND) lParam, L"edit", NULL) == 0)
if (textfieldReadOnly((HWND) lParam))
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
*/ if (SetBkMode((HDC) wParam, TRANSPARENT) == 0)
logLastError("error setting transparent background mode to controls in containerWndProc()");
c->brush = getControlBackgroundBrush((HWND) lParam, (HDC) wParam);
return (LRESULT) (c->brush);
case WM_PAINT:
if (cc == NULL)
break;
c = (struct container *) (uiControl(cc)->Internal);
dc = BeginPaint(c->hwnd, &ps);
if (dc == NULL)
logLastError("error beginning container paint in containerWndProc()");
r = ps.rcPaint;
paintContainerBackground(c->hwnd, dc, &r);
EndPaint(c->hwnd, &ps);
return 0;
// tab controls use this to draw the background of the tab area
case WM_PRINTCLIENT:
if (cc == NULL)
break;
c = (struct container *) (uiControl(cc)->Internal);
if (GetClientRect(c->hwnd, &r) == 0)
logLastError("error getting client rect in containerWndProc()");
paintContainerBackground(c->hwnd, (HDC) wParam, &r);
return 0;
case WM_ERASEBKGND:
// avoid some flicker
// we draw the whole update area anyway
return 1;
case WM_WINDOWPOSCHANGED:
if ((wp->flags & SWP_NOSIZE) != 0)
break;
// fall through
case msgUpdateChild:
if (cc == NULL)
break;
c = (struct container *) (uiControl(cc)->Internal);
if (GetClientRect(c->hwnd, &r) == 0)
logLastError("error getting client rect for resize in containerWndProc()");
resize(cc, &r);
return 0;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
const char *initContainer(HICON hDefaultIcon, HCURSOR hDefaultCursor)
{
WNDCLASSW wc;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = containerClass;
wc.lpfnWndProc = containerWndProc;
wc.hInstance = hInstance;
wc.hIcon = hDefaultIcon;
wc.hCursor = hDefaultCursor;
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
if (RegisterClassW(&wc) == 0)
return "registering uiContainer window class";
initialParent = CreateWindowExW(0,
containerClass, L"",
WS_OVERLAPPEDWINDOW,
0, 0,
100, 100,
NULL, NULL, hInstance, NULL);
if (initialParent == NULL)
return "creating initial parent window";
// just to be safe, disable the initial parent so it can't be interacted with accidentally
// if this causes issues for our controls, we can remove it
EnableWindow(initialParent, FALSE);
return NULL;
}
// subclasses override this and call back here when all children are destroyed
static void containerDestroy(uiControl *cc)
{
struct container *c = (struct container *) (cc->Internal);
if (c->parent != NULL)
complain("attempt to destroy uiContainer %p while it has a parent", cc);
if (DestroyWindow(c->hwnd) == 0)
logLastError("error destroying uiContainer window in containerDestroy()");
uiFree(c);
}
static uintptr_t containerHandle(uiControl *cc)
{
struct container *c = (struct container *) (cc->Internal);
return (uintptr_t) (c->hwnd);
}
static void containerSetParent(uiControl *cc, uiContainer *parent)
{
struct container *c = (struct container *) (cc->Internal);
uiContainer *oldparent;
HWND newparent;
oldparent = c->parent;
c->parent = parent;
newparent = initialParent;
if (c->parent != NULL)
newparent = (HWND) uiControlHandle(uiControl(c->parent));
if (SetParent(c->hwnd, newparent) == 0)
logLastError("error changing uiContainer parent in containerSetParent()");
if (oldparent != NULL)
uiContainerUpdate(oldparent);
if (c->parent != NULL)
uiContainerUpdate(c->parent);
}
static void containerResize(uiControl *cc, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d)
{
struct container *c = (struct container *) (cc->Internal);
if (MoveWindow(c->hwnd, x, y, width, height, TRUE) == 0)
logLastError("error resizing uiContainer in containerResize()");
}
static int containerVisible(uiControl *cc)
{
struct container *c = (struct container *) (cc->Internal);
return !c->hidden;
}
static void containerShow(uiControl *cc)
{
struct container *c = (struct container *) (cc->Internal);
ShowWindow(c->hwnd, SW_SHOW);
// hidden controls don't count in boxes and grids
c->hidden = 0;
if (c->parent != NULL)
uiContainerUpdate(c->parent);
}
static void containerHide(uiControl *cc)
{
struct container *c = (struct container *) (cc->Internal);
ShowWindow(c->hwnd, SW_HIDE);
c->hidden = 1;
if (c->parent != NULL)
uiContainerUpdate(c->parent);
}
static void containerEnable(uiControl *cc)
{
struct container *c = (struct container *) (cc->Internal);
uiControlSysFuncParams p;
EnableWindow(c->hwnd, TRUE);
p.Func = uiWindowsSysFuncContainerEnable;
uiControlSysFunc(cc, &p);
}
static void containerDisable(uiControl *cc)
{
struct container *c = (struct container *) (cc->Internal);
uiControlSysFuncParams p;
EnableWindow(c->hwnd, FALSE);
p.Func = uiWindowsSysFuncContainerDisable;
uiControlSysFunc(cc, &p);
}
static void containerUpdate(uiContainer *cc)
{
struct container *c = (struct container *) (uiControl(cc)->Internal);
SendMessageW(c->hwnd, msgUpdateChild, 0, 0);
}
void uiMakeContainer(uiContainer *cc)
{
struct container *c;
c = uiNew(struct container);
c->hwnd = CreateWindowExW(WS_EX_CONTROLPARENT,
containerClass, L"",
WS_CHILD | WS_VISIBLE,
0, 0,
100, 100,
initialParent, NULL, hInstance, cc);
if (c->hwnd == NULL)
logLastError("error creating uiContainer window in uiMakeContainer()");
uiControl(cc)->Internal = c;
uiControl(cc)->Destroy = containerDestroy;
uiControl(cc)->Handle = containerHandle;
uiControl(cc)->SetParent = containerSetParent;
// PreferredSize() is provided by subclasses
uiControl(cc)->Resize = containerResize;
uiControl(cc)->Visible = containerVisible;
uiControl(cc)->Show = containerShow;
uiControl(cc)->Hide = containerHide;
uiControl(cc)->Enable = containerEnable;
uiControl(cc)->Disable = containerDisable;
// ResizeChildren() is provided by subclasses
uiContainer(cc)->Update = containerUpdate;
}
回答1:
Okay I think I've mostly figured it out.
The problem is there are two different theme parts at play here, TABP_PANE
and TABP_BODY
. TABP_PANE
is the background the tab control itself draws, and TABP_BODY
is what I assume is the actual tab background.
Compare:
I'm guessing what Microsoft expected you to do, and what I guess the property sheet control does, is have each tab page be a WC_DIALOG
dialog box and that you call the EnableThemeDialogTexture()
function to have the TABP_BODY
texture drawn on top.
But if you look closely at the screenshot of the Explorer property sheet in the original question, our TABP_BODY
is still not quite the same. So that leaves two questions:
How is
TABP_BODY
drawn on top ofTABP_PANE
? Is it merely drawn on top? Is it blended somehow?How is the theme dialog texture drawn? Is it tiled with a minimum width
TABP_BODY
? That's what it looks like to me, anyway...
So this is a partial answer, at least. I'm willing to investigate further, though I'm not sure when.
Source of pictured program:
// 20 may 2015
// based on wintabprintclient.c 18 may 2015
// based on wintabparentwinebug.c 3 may 2015
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define CINTERFACE
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include <windows.h>
#include <commctrl.h>
#include <stdint.h>
#include <uxtheme.h>
#include <string.h>
#include <wchar.h>
#include <windowsx.h>
#include <vsstyle.h>
#include <vssym32.h>
#include <stdarg.h>
#include <oleacc.h>
#include <stdio.h>
void die(char *s)
{
// TODO
}
HWND mainwin;
#define BGCOLOR RGB(0x0A, 0x24, 0x6A)
#define PCOLOR RGB(0x6A, 0x24, 0x0A)
LRESULT CALLBACK wndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
NMHDR *nm = (NMHDR *) lParam;
PAINTSTRUCT ps;
HDC dc;
POINT pt;
RECT r;
HTHEME theme;
switch (uMsg) {
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_PAINT:
dc = BeginPaint(hwnd, &ps);
#define X 20
#define Y 20
#define X2 240
#define YT 40
#define WIDTH 200
#define HEIGHT 440
theme = OpenThemeData(hwnd, L"tab");
r.left = X;
r.top = Y;
r.right = r.left + WIDTH;
r.bottom = YT - 5;
DrawTextW(dc, L"TABP_PANE", -1, &r, DT_LEFT | DT_TOP);
r.left = X;
r.top = YT;
r.right = r.left + WIDTH;
r.bottom = r.top + HEIGHT;
DrawThemeBackground(theme, dc,
TABP_PANE, 0,
&r, NULL);
r.left = X2;
r.top = Y;
r.right = r.left + WIDTH;
r.bottom = YT - 5;
DrawTextW(dc, L"TABP_BODY", -1, &r, DT_LEFT | DT_TOP);
r.left = X2;
r.top = YT;
r.right = r.left + WIDTH;
r.bottom = r.top + HEIGHT;
DrawThemeBackground(theme, dc,
TABP_BODY, 0,
&r, NULL);
CloseThemeData(theme);
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
static void makeWindows(void)
{
mainwin = CreateWindowExW(0,
L"mainwin", L"Full Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
500, 500,
NULL, NULL, GetModuleHandle(NULL), NULL);
}
int main(int argc, char *argv[])
{
WNDCLASSW wc;
MSG msg;
HBRUSH b;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
// if printing client doesn't print the tab background, this color will bleed through instead
b = CreateSolidBrush(BGCOLOR);
wc.hbrBackground = b;
RegisterClassW(&wc);
makeWindows();
ShowWindow(mainwin, SW_SHOWDEFAULT);
UpdateWindow(mainwin);
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
来源:https://stackoverflow.com/questions/30087540/why-are-my-programss-tab-controls-rendering-their-background-in-a-blocky-way-b