Кнопка подкласса WinApi - PullRequest
0 голосов
/ 09 февраля 2020

В моем проекте я создал простой класс Button:
button.hpp:

class Button : public Control {
    std::string text;
    int         id;

    std::function<void(void)>&& callback{};

    static inline const char* sClassName = "Button";
    static LRESULT CALLBACK ButtonSubclassProc(HWND, UINT, WPARAM, LPARAM, UINT_PTR, DWORD_PTR);
public:
    Button(HWND parent, POINT position, SIZE size, int id, const std::string& text) noexcept;
    virtual ~Button() noexcept;

    inline void SetCallback(std::function<void(void)>&& callback) noexcept { this->callback = callback; }
    NODISCARD inline int GetId() const noexcept { return id; }
private:
    NODISCARD bool CreateControl(HWND) noexcept override;
    void DestroyControl() noexcept override;
    void Invoke() const noexcept;
};

button. cpp:

#include "button.hpp"

Button::Button(HWND parent, POINT position, SIZE size, int id, const std::string& text) noexcept
    : Control(position, size), id(id), text(text) {

    isCreated = CreateControl(parent);
    SetWindowSubclass(hwnd, &Button::ButtonSubclassProc, id, reinterpret_cast<DWORD_PTR>(this));
}

Button::~Button() noexcept {
    RemoveWindowSubclass(hwnd, &Button::ButtonSubclassProc, id);
    DestroyControl();
}

NODISCARD bool Button::CreateControl(HWND parent) noexcept {
    hwnd = CreateWindow(sClassName, text.c_str(), WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, position.x, position.y, size.cx, size.cy, parent, reinterpret_cast<HMENU>(id), reinterpret_cast<HINSTANCE>(GetWindowLong(parent, GWL_HINSTANCE)), NULL);
    return hwnd != NULL;
}

void Button::DestroyControl() noexcept {
    Control::DestroyControl();
}

void Button::Invoke() const noexcept {
    if (callback) {
        callback();
    }
}

LRESULT CALLBACK Button::ButtonSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR refData) {

    auto button = reinterpret_cast<Button*>(refData);
    if (uMsg == WM_COMMAND) {
        MessageBox(0, std::to_string(id).c_str(), 0, 0);

        button->Invoke();
        return TRUE;
    }

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

вот так я передаю сообщение из процедуры главного окна:

case WM_COMMAND: {
    return SendMessage(reinterpret_cast<HWND>(lParam), WM_COMMAND, wParam, lParam);
}

И это прекрасно работает, когда создается только одна кнопка, но когда я пытаюсь создать больше, не имеет значения, какую кнопку я бы нажал sh, я всегда получаю обратный вызов последней созданной кнопки. Я полагаю, это происходит из-за вызова SetWindowSubclass в счетчике и передачи ему this указателя, но я не знаю, что мне делать. Прочтите о GetWindowSubclass, но я не совсем понимаю, как его использовать.

Кнопки создаются в классе основного окна: mainwnd.hpp

class MainWindow : public NonCopyable, public NonMovable {
    HINSTANCE   handle;
    HWND        hwnd;
    int         width;
    int         height;

    bool isRegistered{ false };
    bool isCreated{ false };

    IRenderer* renderer;
    Button* buttonStart;
    Button* buttonStop;
    static inline const char* sClassName = "MAIN_WINDOW_CLASS";
    static LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
public:
    explicit MainWindow(HINSTANCE handle, int width, int height) noexcept;
    ~MainWindow() noexcept;

    NODISCARD inline bool IsRegistered() const noexcept { return isRegistered; }
    NODISCARD inline bool IsCreated()    const noexcept { return isCreated; }

    NODISCARD bool CheckControls() const noexcept;

    NODISCARD inline int Width()  const noexcept { return width; }
    NODISCARD inline int Height() const noexcept { return height; }

    void Update() noexcept;
private:
    NODISCARD bool RegisterMainClass() const noexcept;
    NODISCARD bool CreateMainWindow() noexcept;

    void UnregisterMainClass() noexcept;
    void DestroyMainWindow() noexcept;

    void CreateControls() noexcept;
    void DestroyControls() noexcept;

    void OnButtonStartPushed();
    void OnButtonStopPushed();
};

mainwnd. cpp

#include "main_window.hpp"

MainWindow::MainWindow(HINSTANCE handle, int width, int height) noexcept 
    : width(width), height(height), handle(handle) {

    isRegistered = RegisterMainClass();
    isCreated    = CreateMainWindow();

    CreateControls();
}

MainWindow::~MainWindow() noexcept {
    DestroyControls();

    DestroyMainWindow();
    UnregisterMainClass();
}

bool MainWindow::RegisterMainClass() const noexcept {
    WNDCLASS wc;
    memset(&wc, 0, sizeof(WNDCLASSA));

    if (!GetClassInfo(handle, sClassName, &wc)) {
        wc.style         = 0;
        wc.hInstance     = handle;
        wc.lpszClassName = sClassName;
        wc.lpfnWndProc   = WindowProc;

        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = sizeof(LONG_PTR);
        wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);

        wc.hIcon         = NULL;
        wc.hCursor       = NULL;
        wc.lpszMenuName  = NULL;
    }

    return RegisterClass(&wc) != 0;
}

bool MainWindow::CreateMainWindow() noexcept {
    if (IsRegistered()) {
        hwnd = CreateWindow(sClassName, NULL, WS_VISIBLE | WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, handle, this);
    }
    return hwnd != NULL;
}

void MainWindow::UnregisterMainClass() noexcept {
    UnregisterClass(sClassName, handle);
    isRegistered = false;
}

void MainWindow::DestroyMainWindow() noexcept {
    if (hwnd) {
        DestroyWindow(hwnd);
        hwnd = NULL;
    }

    isCreated = false;
}

void MainWindow::CreateControls() noexcept {
    this->renderer = new Canvas(hwnd, { 10,10 }, { 200,200 });
    buttonStart = new Button(hwnd, { 300,50 }, { 100,40 }, 145, "Start");
    buttonStart->SetCallback(std::bind(&MainWindow::OnButtonStartPushed, this));

    buttonStop = new Button(hwnd, { 300,150 }, { 100,40 }, 168, "Stop");
    buttonStop->SetCallback(std::bind(&MainWindow::OnButtonStopPushed, this));
}

void MainWindow::DestroyControls() noexcept {
    delete renderer;
    delete buttonStart;
    delete buttonStop;
}

bool MainWindow::CheckControls() const noexcept {

    return renderer != 0;
}

void MainWindow::Update() noexcept {
    renderer->Redraw();
}

void MainWindow::OnButtonStartPushed() {
    MessageBox(0, "Start", 0, 0);
}

void MainWindow::OnButtonStopPushed() {
    MessageBox(0, "Stop", 0, 0);
}

LRESULT CALLBACK MainWindow::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    if (uMsg == WM_NCCREATE) {
        MainWindow* window = reinterpret_cast<MainWindow*>(lParam);
        SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(window));
        return TRUE;
    }

    auto window = reinterpret_cast<MainWindow*>(GetWindowLongPtr(hwnd, GWL_USERDATA));
    if (window == nullptr) { 
        return FALSE; 
    }

    switch (uMsg) {
    case WM_DESTROY: {
        PostQuitMessage(0);
        return 0;
    }

    case WM_COMMAND: {
        return SendMessage(reinterpret_cast<HWND>(lParam), WM_COMMAND, wParam, lParam);
    }

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return TRUE;
}

Сообщение L oop:

MSG message;
memset(&message, 0, sizeof(MSG));

while (true) {
    if (PeekMessage(&message, 0, 0, 0, PM_REMOVE) != 0) {
        if (message.message == WM_QUIT) {
            break;
        }

        TranslateMessage(&message);
        DispatchMessage(&message);
    } else {
        mainWindow.Update();
    }
}
...