0
votes

I'm going to risk being shot down for being an idiot but I've sunk hours into trying to follow this windows tutorial with no luck: Custom Window Frame Using DWM

My main goal is to create a custom window frame that behaves in the same was as the default one (for example, can be maximised by being dragged to the top of the screen, has the normal minimise, maximise, exit buttons) but one that sports a different colour and perhaps some menu items within the frame. I'm fairly comfortable using wxWidgets but I am an absolute beginner (if even a beginner) using the Windows API. Unfortunately I've been led here anyway because it looks like the only way to achieve what I'm after.

Going back to the abovementioned linked tutorial, I've able to extend the frame using DwmExtendFrameIntoClientArea. This yields a result that looks like this on Windows 10 using a grey brush:

enter image description here

My first point of confusion happens when trying to follow the instructions under "Removing the Standard Frame". This is what I get when I try and follow the code example:

enter image description here

This looks similar enough to the example image in the tutorial (below) but all of the window functionality is now gone! I.e. clicking any of the top-right three buttons doesn't nothing and the window cannot be clicked, moved or resized.

Example shown in the link 'Custom Window Frame Using DWM' above

I had hoped that this functionality would return once I'd added in the rest of the example code, which I won't reproduce as they are contained in the page I linked under Appendix B and C (titled 'Painting the Caption Title' and 'HitTestNCA Function' respectively).

Not only did the functionality not return, the example code didn't seem to do anything... and I ended up with the window as it was after my previous edit (pictured below - to be clear):

enter image description here

I haven't reproduced by code here because it's exactly the same as the code found in the link except the background colour is changed to grey and I've added a static 'Test widget' to give myself a point of reference for what the coordinates were doing.

If any kind soul could please tell me what I am doing wrong, or whether my goals are even achievable using the methods I've reluctantly chosen, I would greatly appreciate some advice.

Many thanks!

1
Please read How to Ask. You've had a question closed recently, because you refused to produce a minimal reproducible example. This question is likely going to get closed for the same reason.IInspectable
I've read the guidelines and am trying to my best to follow them. I have not 'refused' to provide anything and have and will always tried to incorporate feedback into questions I ask here. In this particular case, I'm at a bit of a loss how to create a minimal example beyond what I've already done (by reference to the link) because I don't understand the program well enough to isolate it any further. Thankfully, another user was able to sufficiently interpret the question enough to provide a very helpful answer.Howtousernamepls
The guidelines for a minimal reproducible example also include the following: "Make sure all information necessary to reproduce the problem is included in the question itself". Don't reference external resources.IInspectable
Considering the particular nature of the question I have asked, I don't think a literal reading of the quoted passage is the approach that best reflects the overall purpose of both guidelines (which I have tried my best to comply with). I apologise that my attempt has fallen short of your standards.Howtousernamepls
Producing an MCVE is a skill. It's a skill that requires practice. This isn't about duplicating contents from an off-site resource (although even that adds value, in case the off-site resource becomes unavailable). The link supplied above provides mechanical steps you can perform to arrive at an MCVE, or at least converge towards a minimal sample.IInspectable

1 Answers

1
votes

I created a project according to the documentation and did not reproduce this issue, but you can compare it with your project:

#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];
}

Then,

the window cannot be clicked, moved or resized.

We cannot expect that without implementing logic to handle caption button hit testing and frame resizing/moving.

You will get the black of the background color default(BITMAPINFO dib = { 0 };). But the screen shot was still gray. (This may be because the function didn't work (failed?). Or, when you test the code before, you've commented B, C , and then when you add B, C, didn't to uncomment it).