Home > Software engineering >  How to subclass a gui control?
How to subclass a gui control?

Time:08-24

I'm trying to learn how to subclass a GUI control and 'modify' its hdc.

This is my subclass callback:

mygui.h

#include <commctrl.h> // SetWindowSubclass
#pragma comment(lib, "Comctl32.lib")

#include <windows.h> // GDI includes.
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
using namespace DllExports;
#pragma comment (lib,"Gdiplus.lib")



typedef UCHAR GuiControls;
enum GuiControlTypes {
    GUI_CONTROL_BUTTON
};

struct GuiControlOptionsType
{
    int x;
    int y;
    int width;
    int height;
    LPCWSTR text;
    GuiControlTypes controltype;

    bool ERASEDBKGND = false; // Used on the subclass proc.
    HDC dc;
};

class Gui
{
public:
    std::map<HWND, GuiControlOptionsType> control_list;
    HWND GuihWnd;
    HWND A_LasthWnd;
    LRESULT Create();
    LRESULT AddControl(GuiControls aControlType, GuiControlOptionsType opt);
};

LRESULT CALLBACK ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);

mygui.cpp

/* Window Procedure. */
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // TODO
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow)
{
    Gui mygui;
    mygui.Create();
}

LRESULT Gui::Create()
{
    WNDCLASSEX wc{};
    MSG Msg;
    HWND hWnd = nullptr;

    wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; 
    wc.hInstance = 0; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW   1); wc.lpszMenuName = NULL; 
    wc.lpszClassName = L"classname";

    if (!RegisterClassEx(&wc))
        // TODO

    this->GuihWnd = CreateWindowW(
        wc.lpszClassName,
        L"Title",
        WS_EX_COMPOSITED | WS_EX_LAYERED | // Double buffering
        WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
        CW_USEDEFAULT, CW_USEDEFAULT, 500, 200,
        nullptr, nullptr, nullptr, nullptr);

    DWORD err = GetLastError();
    if (this->GuihWnd == NULL)
        // TODO

    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    GuiControlOptionsType opt;
    opt.x = 10; opt.y = 10; opt.width = 100; opt.height = 100; opt.text = L"test";
    this->AddControl(GUI_CONTROL_BUTTON, opt);
    SetWindowSubclass(this->A_LasthWnd, ButtonProc, 1, (DWORD_PTR)this);

    ShowWindow(this->GuihWnd, SW_SHOW);
    UpdateWindow(this->GuihWnd);

    while (GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    return 0; // Msg.wParam;
}

LRESULT Gui::AddControl(GuiControls aControlType, GuiControlOptionsType opt)
{
    switch (aControlType)
    {
    case GUI_CONTROL_BUTTON:
    {
        HWND hWnd = CreateWindow(
            L"BUTTON",  // Predefined class; Unicode assumed 
            opt.text,   // Button text 
            WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  // Styles 
            opt.x,          // x position 
            opt.y,          // y position 
            opt.width,      // Button width
            opt.height,     // Button height
            this->GuihWnd,  // Parent window
            NULL,           // No menu.
            NULL,           //(HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),
            NULL);          // Pointer not needed.

        opt.controltype = GUI_CONTROL_BUTTON;
        this->control_list.emplace(hWnd, opt);
        this->A_LasthWnd = hWnd;
    }
    break;

    default:
        break;
    }
    return 1;
}

LRESULT CALLBACK ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    Gui* pThis = (Gui*)dwRefData;

    switch (uMsg)
    {
    case WM_ERASEBKGND:
    {
        if (pThis->control_list[hWnd].ERASEDBKGND)
            return 1;

        // Create/save the new button dc.
        GpBitmap* pBitmap;
        GdipCreateBitmapFromScan0(pThis->control_list[hWnd].width, pThis->control_list[hWnd].height, 0, PixelFormat32bppPARGB, 0, &pBitmap);

        GpGraphics* g;
        GdipGetImageGraphicsContext(pBitmap, &g);
        GdipGraphicsClear(g, 0xFF2400ff);

        HBITMAP hbm;
        GdipCreateHBITMAPFromBitmap(pBitmap, &hbm, 0);

        HDC dc = CreateCompatibleDC(NULL);
        SelectObject(dc, hbm);

        pThis->control_list[hWnd].dc = dc;

        GdipDisposeImage(pBitmap);
        GdipDeleteGraphics(g);
        DeleteObject(hbm);

        pThis->control_list[hWnd].ERASEDBKGND = 1;
    }
    break;

    case WM_LBUTTONDBLCLK:
    case WM_LBUTTONDOWN:
    {
        InvalidateRect(hWnd, 0, 1);
    }
    break;

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

        BLENDFUNCTION bf;
        bf.SourceConstantAlpha = 255;
        bf.BlendOp = AC_SRC_OVER;
        bf.BlendFlags = 0;
        bf.AlphaFormat = AC_SRC_ALPHA;

        GdiAlphaBlend(hdc, 0, 0, pThis->control_list[hWnd].width, pThis->control_list[hWnd].height,
            pThis->control_list[hWnd].dc, 0, 0, pThis->control_list[hWnd].width, pThis->control_list[hWnd].height, bf);

        EndPaint(hWnd, &ps);
        DeleteObject(hdc);
        return TRUE;
    }
    break;

    }
    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

The problem is... when I click on the button it restores its default hdc, when I minimize/restore it draws with my 'custom' hdc.

image

I tried adding a call to InvalidateRect() under WM_LBUTTONDOWN, but it resulted in the same thing.

I also tried creating the Gui with 'double buffering' adding the styles WS_EX_COMPOSITED | WS_EX_LAYERED.

What am I missing?

CodePudding user response:

BS_OWNERDRAW should be "her" style, I think.

  • Related