【问题标题】:Struggling to create a functional custom Windows 10 frame decoration following 'Custom Window Frame Using DWM'在“使用 DWM 的自定义窗口框架”之后努力创建功能性自定义 Windows 10 框架装饰
【发布时间】:2020-06-26 16:07:26
【问题描述】:

我会冒着被击落的风险,因为我是个白痴,但我花了几个小时试图按照这个 Windows 教程进行操作,但没有运气:Custom Window Frame Using DWM

我的主要目标是创建一个行为与默认窗口相同的自定义窗口框架(例如,可以通过拖动到屏幕顶部来最大化,具有正常的最小化、最大化、退出按钮)但是,它具有不同的颜色,并且可能在框架内具有一些菜单项。我对使用 wxWidgets 相当满意,但我是使用 Windows API 的绝对初学者(即使是初学者)。不幸的是,我还是被带到了这里,因为它看起来是实现我所追求的唯一途径。

回到上面提到的链接教程,我已经能够使用 DwmExtendFrameIntoClientArea 扩展框架。这会在 Windows 10 上使用灰色画笔产生如下所示的结果:

我的第一个困惑是在尝试按照“移除标准框架”下的说明进行操作时发生的。这是我尝试遵循代码示例时得到的结果:

这看起来与教程(下)中的示例图像非常相似,但现在所有的窗口功能都消失了! IE。单击右上角的三个按钮中的任何一个都不会产生任何影响,并且无法单击、移动或调整窗口大小。

我曾希望在添加示例代码的其余部分后此功能会返回,因为它们包含在我在附录 B 和 C 下链接的页面中(标题为“绘制标题Title' 和 'HitTestNCA Function')。

不仅功能返回,示例代码似乎没有做任何事情......我最终得到了我之前编辑后的窗口(如下图 - 到清楚):

我没有在这里通过代码进行复制,因为它与链接中的代码完全相同,只是背景颜色更改为灰色,并且我添加了一个静态“测试小部件”来给自己一个参考点坐标在做什么。

如果有好心人可以告诉我我做错了什么,或者我的目标是否可以使用我不情愿地选择的方法来实现,我将非常感谢一些建议。

非常感谢!

【问题讨论】:

  • 请阅读How to Ask。您最近有一个问题被关闭,因为您拒绝提供minimal reproducible example。出于同样的原因,这个问题很可能会被关闭。
  • 我已阅读指南,并正在尽我最大的努力遵循它们。我没有“拒绝”提供任何东西,并且已经并将始终尝试将反馈纳入我在这里提出的问题中。在这种特殊情况下,我有点不知所措如何创建一个超出我已经做过的最小示例(通过引用链接),因为我对程序的理解不够好,无法进一步隔离它。值得庆幸的是,另一位用户能够充分解释问题,足以提供非常有用的答案。
  • minimal reproducible example 的指导方针还包括以下内容:“确保在问题本身中包含重现问题所需的所有信息。不要引用外部资源。
  • 考虑到我所问问题的特殊性质,我不认为从字面上阅读引用的段落是最能反映两个指南的总体目的的方法(我已尽力遵从)。很抱歉我的尝试没有达到您的标准。
  • 制作 MCVE 是一项技能。这是一项需要练习的技能。这不是从场外资源复制内容(尽管即使这样也会增加价值,以防场外资源变得不可用)。上面提供的链接提供了您可以执行以达到 MCVE 或至少收敛到最小样本的机械步骤。

标签: c++ winapi dwm


【解决方案1】:

我根据文档创建了一个项目,没有重现这个问题,但是你可以和你的项目对比一下:

#include <windows.h>
#include <stdio.h>
#include <uxtheme.h>
#include <dwmapi.h>
#include <vssym32.h>
#include <windowsx.h>
#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "uxtheme.lib")
#define RECTWIDTH(rc)           (rc.right - rc.left)
#define RECTHEIGHT(rc)          (rc.bottom - rc.top)
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT AppWinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CustomCaptionProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool* pfCallDWP);
void PaintCustomCaption(HWND hWnd, HDC hdc);
LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam);
HWND createmainwindow()
{
    WNDCLASSEXW wcex = { 0 };

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = GetModuleHandle(NULL);
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
    wcex.lpszClassName = L"My_Class";

    RegisterClassExW(&wcex);
    HWND hWnd = CreateWindowW(wcex.lpszClassName, L"Test", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, wcex.hInstance, nullptr);
    if (!hWnd)
    {
        return FALSE;
    }
    HWND staticctrl = CreateWindowW(L"STATIC", L"SETTINGS", SS_LEFT | WS_CHILD,
        8, 27, 500, 300, hWnd, NULL, wcex.hInstance, NULL);
    if (!staticctrl)
    {
        return FALSE;
    }
    ShowWindow(hWnd, SW_NORMAL);
    UpdateWindow(hWnd);
    ShowWindow(staticctrl, SW_NORMAL);
    UpdateWindow(staticctrl);
}

void main()
{
    HWND hWnd = createmainwindow();
    MSG msg;
    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, 0, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    bool fCallDWP = true;
    BOOL fDwmEnabled = FALSE;
    LRESULT lRet = 0;
    HRESULT hr = S_OK;

    // Winproc worker for custom frame issues.
    hr = DwmIsCompositionEnabled(&fDwmEnabled);
    if (SUCCEEDED(hr))
    {
        lRet = CustomCaptionProc(hWnd, message, wParam, lParam, &fCallDWP);
    }

    // Winproc worker for the rest of the application.
    if (fCallDWP)
    {
        lRet = AppWinProc(hWnd, message, wParam, lParam);
    }
    return lRet;
}

//
// Message handler for the application.
//
LRESULT AppWinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;
    HRESULT hr;
    LRESULT result = 0;

    switch (message)
    {
    case WM_CREATE:
    {}
    break;
    case WM_COMMAND:
        wmId = LOWORD(wParam);
        wmEvent = HIWORD(wParam);

        // Parse the menu selections:
        switch (wmId)
        {
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    case WM_PAINT:
    {
        hdc = BeginPaint(hWnd, &ps);
        PaintCustomCaption(hWnd, hdc);

        // Add any drawing code here...

        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
// Message handler for handling the custom caption messages.
//
LRESULT CustomCaptionProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool* pfCallDWP)
{
    LRESULT lRet = 0;
    HRESULT hr = S_OK;
    bool fCallDWP = true; // Pass on to DefWindowProc?

    fCallDWP = !DwmDefWindowProc(hWnd, message, wParam, lParam, &lRet);

    // Handle window creation.
    if (message == WM_CREATE)
    {
        RECT rcClient;
        GetWindowRect(hWnd, &rcClient);

        // Inform application of the frame change.
        SetWindowPos(hWnd,
            NULL,
            rcClient.left, rcClient.top,
            RECTWIDTH(rcClient), RECTHEIGHT(rcClient),
            SWP_FRAMECHANGED);

        fCallDWP = true;
        lRet = 0;
    }

    // Handle window activation.
    if (message == WM_ACTIVATE)
    {
        // Extend the frame into the client area.
        MARGINS margins;

        margins.cxLeftWidth = 8;      // 8
        margins.cxRightWidth = 8;    // 8
        margins.cyBottomHeight = 20; // 20
        margins.cyTopHeight = 27;       // 27

        hr = DwmExtendFrameIntoClientArea(hWnd, &margins);

        if (!SUCCEEDED(hr))
        {
            // Handle error.
        }

        fCallDWP = true;
        lRet = 0;
    }

    if (message == WM_PAINT)
    {
        HDC hdc;
        {
            PAINTSTRUCT ps;
            hdc = BeginPaint(hWnd, &ps);
            PaintCustomCaption(hWnd, hdc);
            EndPaint(hWnd, &ps);
        }

        fCallDWP = true;
        lRet = 0;
    }

    // Handle the non-client size message.
    if ((message == WM_NCCALCSIZE) && (wParam == TRUE))
    {
        // Calculate new NCCALCSIZE_PARAMS based on custom NCA inset.
        NCCALCSIZE_PARAMS* pncsp = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);

        pncsp->rgrc[0].left = pncsp->rgrc[0].left + 0;
        pncsp->rgrc[0].top = pncsp->rgrc[0].top + 0;
        pncsp->rgrc[0].right = pncsp->rgrc[0].right - 0;
        pncsp->rgrc[0].bottom = pncsp->rgrc[0].bottom - 0;

        lRet = 0;

        // No need to pass the message on to the DefWindowProc.
        fCallDWP = false;
    }

    //Handle hit testing in the NCA if not handled by DwmDefWindowProc.
    if ((message == WM_NCHITTEST) && (lRet == 0))
    {
        lRet = HitTestNCA(hWnd, wParam, lParam);

        if (lRet != HTNOWHERE)
        {
            fCallDWP = false;
        }
    }

    *pfCallDWP = fCallDWP;

    return lRet;
}
// Paint the title on the custom frame.
void PaintCustomCaption(HWND hWnd, HDC hdc)
{
    RECT rcClient;
    GetClientRect(hWnd, &rcClient);

    HTHEME hTheme = OpenThemeData(NULL, L"CompositedWindow::Window");
    if (hTheme)
    {
        HDC hdcPaint = CreateCompatibleDC(hdc);
        if (hdcPaint)
        {
            int cx = RECTWIDTH(rcClient);
            int cy = RECTHEIGHT(rcClient);

            // Define the BITMAPINFO structure used to draw text.
            // Note that biHeight is negative. This is done because
            // DrawThemeTextEx() needs the bitmap to be in top-to-bottom
            // order.
            BITMAPINFO dib = { 0 };
            dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
            dib.bmiHeader.biWidth = cx;
            dib.bmiHeader.biHeight = -cy;
            dib.bmiHeader.biPlanes = 1;
            dib.bmiHeader.biBitCount = 32;
            dib.bmiHeader.biCompression = BI_RGB;
            HBITMAP hbm = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0);
            if (hbm)
            {
                HBITMAP hbmOld = (HBITMAP)SelectObject(hdcPaint, hbm);

                // Setup the theme drawing options.
                DTTOPTS DttOpts = { sizeof(DTTOPTS) };
                DttOpts.dwFlags = DTT_COMPOSITED | DTT_GLOWSIZE;
                DttOpts.iGlowSize = 15;

                // Select a font.
                LOGFONT lgFont;
                HFONT hFontOld = NULL;
                if (SUCCEEDED(GetThemeSysFont(hTheme, TMT_CAPTIONFONT, &lgFont)))
                {
                    HFONT hFont = CreateFontIndirect(&lgFont);
                    hFontOld = (HFONT)SelectObject(hdcPaint, hFont);
                }

                // Draw the title.
                RECT rcPaint = rcClient;
                rcPaint.top += 8;
                rcPaint.right -= 125;
                rcPaint.left += 8;
                rcPaint.bottom = 50;
                DrawThemeTextEx(hTheme,
                    hdcPaint,
                    0, 0,
                    L"Test",
                    -1,
                    DT_LEFT | DT_WORD_ELLIPSIS,
                    &rcPaint,
                    &DttOpts);

                // Blit text to the frame.
                BitBlt(hdc, 0, 0, cx, cy, hdcPaint, 0, 0, SRCCOPY);

                SelectObject(hdcPaint, hbmOld);
                if (hFontOld)
                {
                    SelectObject(hdcPaint, hFontOld);
                }
                DeleteObject(hbm);
            }
            DeleteDC(hdcPaint);
        }
        CloseThemeData(hTheme);
    }
}
// Hit test the frame for resizing and moving.
LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    // Get the point coordinates for the hit test.
    POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

    // Get the window rectangle.
    RECT rcWindow;
    GetWindowRect(hWnd, &rcWindow);

    // Get the frame rectangle, adjusted for the style without a caption.
    RECT rcFrame = { 0 };
    AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL);

    // Determine if the hit test is for resizing. Default middle (1,1).
    USHORT uRow = 1;
    USHORT uCol = 1;
    bool fOnResizeBorder = false;

    // Determine if the point is at the top or bottom of the window.
    if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + 27)
    {
        fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top));
        uRow = 0;
    }
    else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - 20)
    {
        uRow = 2;
    }

    // Determine if the point is at the left or right of the window.
    if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + 8)
    {
        uCol = 0; // left side
    }
    else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - 8)
    {
        uCol = 2; // right side
    }

    // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
    LRESULT hitTests[3][3] =
    {
        { HTTOPLEFT,    fOnResizeBorder ? HTTOP : HTCAPTION,    HTTOPRIGHT },
        { HTLEFT,       HTNOWHERE,     HTRIGHT },
        { HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
    };

    return hitTests[uRow][uCol];
}

那么,

窗口不能被点击、移动或调整大小。

如果没有实现处理字幕按钮点击测试和帧大小调整/移动的逻辑,我们就无法期待。

您将获得背景颜色默认的黑色(BITMAPINFO dib = { 0 };)。但屏幕截图仍然是灰色的。 (这可能是因为该函数不起作用(失败?)。或者,当您之前测试代码时,您已经注释了 B, C ,然后当您添加 B, C 时,没有取消注释它)。

【讨论】:

  • 您的系统似乎可以在我的系统上运行!虽然它没有显示标题“测试”,但它会正确调整大小和处理。我还不确定为什么我的根本不起作用。不过,您对 dib.bmiColors[0] 的事情是正确的。我发现我出于某种原因将 dib.bmiHeader.biBitCount 设置为 0 ......在我将其更正为 1 之后,我看到了你所描述的黑色。奇怪的是,我仍然无法在上面绘制标题或激活任何移动或调整大小的能力(根据你的例子)。我将花费几个小时进行故障排除并恢复。非常感谢比较资源!
  • 我发现标题画的问题,在我将biBitCount设置为32后,它对我有用。
猜你喜欢
  • 2019-12-18
  • 2015-09-27
  • 1970-01-01
  • 2013-01-06
  • 2012-08-18
  • 2010-10-01
  • 1970-01-01
  • 2012-04-14
  • 1970-01-01
相关资源
最近更新 更多