为什么我的DwmExtendFrameIntoClientArea()&#f;窗口没有绘制DWM边框?

时间:2016-12-12 17:33:12

标签: c++ winapi dwm

我一直关注MSDN's guide to using the DWM API to extend the frame into the client area,但重做了,所以一旦我有了工作,我就可以尝试一下。

但是,当我使用Visual Studio 2013 x64 Native Tools命令行中的cl wincompositiontest.cpp构建它时,程序不会绘制窗口边框。标准窗口按钮可以工作:我可以看到Windows 7按钮发光和工具提示,然后单击按钮可以执行各自的操作(因此我可以关闭此窗口)。但没有其他工作:我无法移动或调整窗口边缘的大小,边框不会绘制,而是绘制白色:

Window

DwmExtendFrameIntoClientArea()返回S_OK

发生了什么?这是在VirtualBox上运行的Windows 7 x64上;我把代码交给了一个在真实硬件上运行Windows 7的朋友,他们也得到了相同的结果。感谢。

// 12 december 2016
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows Vista
// unless otherwise stated, all values from Microsoft's sdkddkver.h
// TODO is all of this necessary? how is NTDDI_VERSION used?
// TODO plaform update sp2
#define WINVER          0x0600  /* from Microsoft's winnls.h */
#define _WIN32_WINNT        0x0600
#define _WIN32_WINDOWS  0x0600  /* from Microsoft's pdh.h */
#define _WIN32_IE           0x0700
#define NTDDI_VERSION       0x06000000
#include <windows.h>
#include <commctrl.h>
#include <uxtheme.h>
#include <windowsx.h>
#include <shobjidl.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <usp10.h>
#include <msctf.h>
#include <textstor.h>
#include <olectl.h>
#include <shlwapi.h>
#include <dwmapi.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <inttypes.h>
#include <vector>
#include <map>
#include <string>

#pragma comment(linker, \
    "\"/manifestdependency:type='Win32' " \
    "name='Microsoft.Windows.Common-Controls' " \
    "version='6.0.0.0' " \
    "processorArchitecture='*' " \
    "publicKeyToken='6595b64144ccf1df' " \
    "language='*'\"")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "uxtheme.lib")
#pragma comment(lib, "dwmapi.lib")

#define HR(call) printf("%s -> 0x%I32X\n", #call, call)

LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    RECT r;
    MARGINS margins;
    BOOL dwmHandled;
    LRESULT lResult;

    dwmHandled = DwmDefWindowProc(hwnd, uMsg, wParam, lParam, &lResult);
    switch (uMsg) {
    case WM_CREATE:
        GetWindowRect(hwnd, &r);
        SetWindowPos(hwnd, NULL,
            r.left, r.top,
            r.right - r.left, r.bottom - r.top,
            SWP_FRAMECHANGED);
        // TODO if we pass SWP_NOOWNERZORDER || SWP_NOZORDER, the default frame is not correctly inhibited
        break;
    case WM_ACTIVATE:
        margins.cxLeftWidth = 8;
        margins.cxRightWidth = 8;
        margins.cyBottomHeight = 20;
        margins.cyTopHeight = 27;
        HR(DwmExtendFrameIntoClientArea(hwnd, &margins));
        break;
    case WM_NCCALCSIZE:
        if (wParam != (WPARAM) FALSE)
            return 0;
        break;
    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    }
    if (dwmHandled)
        return lResult;
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}

int main(void)
{
    WNDCLASSW wc;
    HWND mainwin;
    MSG msg;

    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);
    wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
    RegisterClassW(&wc);

    mainwin = CreateWindowExW(0,
        L"mainwin", L"Main Window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        400, 400,
        NULL, NULL, GetModuleHandle(NULL), NULL);

    ShowWindow(mainwin, SW_SHOWDEFAULT);
    UpdateWindow(mainwin);

    while (GetMessageW(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    return 0;
}

2 个答案:

答案 0 :(得分:3)

为了与Windows 10兼容,左/右/下边距应为零。

通常您只需要更改标题栏区域。因此,只需更改margins.cyTopHeight

的值

使用AdjustWindowRectEx计算给定窗口样式的默认边框宽度,并将这些值传递给WM_NCCALCSIZE,这将显示默认边框。

仅需{p> DwmDefWindowProc来响应WM_NCHITTESTWM_NCMOUSELEAVE(但可能您设置它的方式对于以后的兼容性更好)

您还必须处理WM_PAINT以便系统按钮正确显示。隐藏系统按钮和绘制自己的按钮可能更容易,因此标题栏完全是自定义的,具有自己的背景颜色。

下面是Windows 10的示例,它应该适用于Windows 7.应用程序应该是DPI,否则边界会出现小的显示问题。

void paint_caption(HWND hWnd, HDC hdc, int caption_height)
{
    RECT rc;
    GetClientRect(hWnd, &rc);
    rc.bottom = caption_height;

    HDC memdc = CreateCompatibleDC(hdc);
    BITMAPINFOHEADER bmpInfoHdr = 
        { sizeof(BITMAPINFOHEADER), rc.right, -rc.bottom, 1, 32 };
    HBITMAP hbitmap = 
        CreateDIBSection(memdc, (BITMAPINFO*)(&bmpInfoHdr), DIB_RGB_COLORS, 0, 0, 0);
    HGDIOBJ oldbitmap = SelectObject(memdc, hbitmap);

    //Note, GDI functions don't support alpha channel, they can't be used here
    //Use GDI+, BufferedPaint, or DrawThemeXXX functions

    BitBlt(hdc, 0, 0, rc.right, caption_height, memdc, 0, 0, SRCCOPY);
    SelectObject(memdc, oldbitmap);
    DeleteObject(hbitmap);
    DeleteDC(memdc);
}

LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    //static MARGINS margins = { -1,-1,100,-1 };
    static MARGINS margins = { 0,0,100,0 };
    static RECT border_thickness = { 0 };

    switch(uMsg) 
    {
    case WM_CREATE:
        if(GetWindowLongPtr(hwnd, GWL_STYLE) & WS_THICKFRAME)
        {
            AdjustWindowRectEx(&border_thickness,
                GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
            border_thickness.left *= -1;
            border_thickness.top *= -1;
        }
        else if(GetWindowLongPtr(hwnd, GWL_STYLE) & WS_BORDER)
        {
            border_thickness = { 1,1,1,1 };
        }

        DwmExtendFrameIntoClientArea(hwnd, &margins);
        SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
            SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);

        break;

    case WM_NCCALCSIZE:
        if(lParam)
        {
            NCCALCSIZE_PARAMS* sz = (NCCALCSIZE_PARAMS*)lParam;
            sz->rgrc[0].left += border_thickness.left;
            sz->rgrc[0].right -= border_thickness.right;
            sz->rgrc[0].bottom -= border_thickness.bottom;
            return 0;
        }
        break;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        //paint caption area
        paint_caption(hwnd, hdc, margins.cyTopHeight);

        EndPaint(hwnd, &ps);
        return 0;
    }

    case WM_NCHITTEST:
    {
        //handle close/minimize/maximize/help button
        LRESULT lResult;
        if (DwmDefWindowProc(hwnd, uMsg, wParam, lParam, &lResult))
            return lResult;

        //do default processing, except change the result for caption area
        lResult = DefWindowProc(hwnd, uMsg, wParam, lParam);
        if(lResult == HTCLIENT)
        {
            POINT pt = { LOWORD(lParam), HIWORD(lParam) };
            ScreenToClient(hwnd, &pt);
            if(pt.y < border_thickness.top) return HTTOP;
            if(pt.y < margins.cyTopHeight)  return HTCAPTION;
        }

        return lResult;
    }

    case WM_NCMOUSELEAVE:
    {
        LRESULT lResult;
        if(DwmDefWindowProc(hwnd, uMsg, wParam, lParam, &lResult))
            return lResult;
        break;
    }

    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    }

    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}

Windows 10中的结果:

enter image description here

赢7:

enter image description here

答案 1 :(得分:1)

好吧,所以我调查了一下,尝试将γnράσκωδ'αείπολλάδιδασκόμε和Barmak Shemirani的建议与我最初的(基于MSDN)的建议结合起来,提出似乎可以处理所有案例的事情。一种面向未来的方式。详尽的测试表明,下面的代码完美地处理了Windows 10的奇怪边框鼠标悬停行为(即使是顶部边框,即使在普通窗口中也只触发蓝色边缘,而不是像其他边缘一样略微关闭)。它看起来在Windows 10,Windows 8.1和Windows 7上是正确的。而且,最大化现在也可以正常工作(或者似乎正常工作;我不确定是否存在我缺少的细微差别)!

与Barmak代码的最大区别在于,我从WM_NCCALCSIZE中提取DefWindowProc()结果,只是过滤掉了最高结果,让我控制了最高优势,让Windows决定了休息应该是。这也意味着我不需要像Barmak那样跟踪border_thickness。另外,它清除了我用WM_PAINT注意到的错误,并将窗口的大小调整为重叠到边框中,但我不知道为什么或如何...

defWindowProcFirst变量控制使用哪种行为。如果将其设置为FALSE,则会出现前巴马克行为,导致Windows 10出现不一致。

还有一些需要注意的事项:

  • 以下内容尚未处理WM_PRINTCLIENT。
  • 以下不会为实际客户区返回HTCLIENT;这不应该太难修复......
  • 未使用DefWindowProcW()的{​​{1}}返回值,这意味着永远不会触及WM_NCCALCSIZErgrc[1]rgrc[2],我们可能会错过在一些优化;我需要弄清楚如何处理这些
  • 还有一堆剩余的TODO

但所有事情都认为这似乎工作正常:)我应该回去测试MSDN代码未经修改但是;我想它会给我类似的结果lppos虽然......

同时感谢!

defWindowProcFirst = FALSE