Why are my programs's tab controls rendering their background in a blocky way, but the standard Window dialogs are not?

我们两清 提交于 2021-02-08 03:30:24

问题


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:

Image

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:

Image edited to show 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):

Test

Both of these render blockily:

Test blockiness

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:

Image

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:

Image edited to show 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:

The two theme parts, normally

The two theme parts, color flipped

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:

  1. How is TABP_BODY drawn on top of TABP_PANE? Is it merely drawn on top? Is it blended somehow?

  2. 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!