Кнопка рисования при обнаружении зависания Winapi

Я пытаюсь создать кнопку и обрабатывать возможные события, например нажатие кнопки и наведение курсора.

Я нашел много полезных статей и до сих пор смог проверить события. У меня проблема с рисованием.

В коде, который я вам представляю, я создал подкласс для кнопки и хочу обрабатывать все события через него, а не через mainwindproc родительского окна. Первоначальная идея заключалась в использовании WM_DRAWITEM или WM_NOTIFY, но эти сообщения отправляются родительскому окну, а не кнопке.

Возвращаясь к проблеме, я хочу, чтобы цвет кнопки менялся при обнаружении наведения, поэтому я использовал переменную isHover для управления цветом, который будет выбран в WM_PAINT.

Однако, хотя значение isHover изменено должным образом в WM_MOUSEHOVER и WM_MOUSELEAVE, в WM_PAINT этого не сделано, и вы можете заметить это по текстам, напечатанным на консоли.

Я не знаю, в чем проблема, и надеюсь, что вы поможете мне ее решить.

Примечание. Я готов опробовать любое предложение, если оно приведет меня к желаемому результату, а именно выполнить все действия, связанные с кнопкой за пределами mainwindowproc, насколько это возможно.

#define UNICODE
#include <windows.h>
#include <CommCtrl.h>
#include <windowsx.h>
#include <iostream>
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK ButtonWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

    
    const wchar_t g_szClassName[] = L"myWindowClass";
    WNDCLASSEX wc = {0};
    HWND hwnd;
    MSG Msg;

    
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = CreateSolidBrush(RGB(30, 30, 30));
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    
    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"فشل تسجيل فئة النافذة", L"خطأ", MB_OK);
        return 1;
    }

    
    hwnd = CreateWindowEx(0, L"MyWindowClass", L"نافذة جديدة",
                          WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
                          500, 350, NULL, NULL, wc.hInstance, NULL);

    if (hwnd == NULL)
    {
        MessageBox(NULL, L"فشل إنشاء النافذة", L"خطأ", MB_OK);
        return 1;
    }
    HWND hButton1 = CreateWindowEx(0, L"BUTTON", L"button1", WS_VISIBLE | WS_CHILD | BS_FLAT,
                                   200, 200, 100, 40, hwnd, (HMENU)101, NULL, NULL);

    if (hButton1 == NULL)
    {
        MessageBox(NULL, L"فشل إنشاء الزر", L"خطأ", MB_OK);
        return 1;
    }

    SetWindowSubclass(hButton1, &ButtonWndProc, 1, 0);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    
    while (Msg.message != WM_QUIT)
    {
        if (GetMessage(&Msg, NULL, 0, 0))
        {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }
    }
    return 0;
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {

    case WM_DESTROY:
    {
        PostQuitMessage(0);
        break;
    }
    case WM_CLOSE:
    {
        
        DestroyWindow(hwnd);
        break;
    }

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

    return 0;
}

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

    bool isTracking;
    bool isHover;
    switch (uMsg)
    {

    case WM_NCDESTROY:
        RemoveWindowSubclass(hWnd, &ButtonWndProc, uIdSubclass);
        break;

    case WM_MOUSEMOVE:
    {
        std::cout << "WM_MOUSEMOVE RECEIVED " << std::endl;        
        std::cout << "hovered =  " << isHover << std::endl; // to check the value of isHover
        if (!isTracking)
        {
            TRACKMOUSEEVENT tme = {sizeof(TRACKMOUSEEVENT)};
            tme.dwFlags = TME_LEAVE | TME_HOVER;
            tme.hwndTrack = hWnd;  // hWnd;
            tme.dwHoverTime = 10; // milliseconds
            isTracking = TrackMouseEvent(&tme);
        }

        return 0 ;
    }
    case WM_MOUSELEAVE:
    {
        std::cout << "(" << GET_X_LPARAM(lParam) << ", " << GET_Y_LPARAM(lParam) << ")" << std::endl;
        isTracking = FALSE;
        isHover = FALSE;
        std::cout << "WM_MOUSELEAVE RECEIVED " << std::endl; 
        std::cout << "hovered =  " << isHover << std::endl; // to check the value of isHover
        InvalidateRect(hWnd, NULL, true);
        UpdateWindow(hWnd);

        return 0;
    }
    case WM_MOUSEHOVER:
    {
        std::cout << "(" << GET_X_LPARAM(lParam) << ", " << GET_Y_LPARAM(lParam) << ")" << std::endl;
        isTracking = TRUE;
        isHover = TRUE;
        std::cout << "WM_MOUSEHOVER RECEIVED " << std::endl;        
        std::cout << "hovered =  " << isHover << std::endl; // to check the value of isHover
        InvalidateRect(hWnd, NULL, true);
        UpdateWindow(hWnd);
        return 0;
    }
    case WM_PAINT:
    {

        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        HBRUSH brush;
        std::cout << "WM_PAINT RECEIVED" << std::endl;
        std::cout << "paint hovered =  " << isHover << std::endl; // to check the value of isHover when receiving WM_PAINT message

        if (isHover)
        {
            brush = CreateSolidBrush(RGB(255, 0, 0));
        }
        else
        {
            brush = CreateSolidBrush(RGB(0, 255, 0));
        }

        FillRect(hdc, &ps.rcPaint, brush);
        DeleteObject(brush);
        EndPaint(hWnd, &ps);

        break;
    }
    }

    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваши переменные являются локальными для ButtonWndProc(), поэтому они будут потеряны и сброшены при каждом вызове ButtonWndProc(). Вам нужно переместить переменные из ButtonWndProc(), чтобы они могли сохраняться между сообщениями.

Создайте экземпляр объекта class/struct для хранения переменных, а затем передайте указатель на этот объект в SetWindowSubclass(), чтобы указатель можно было передать в ButtonWndProc() для каждого полученного сообщения. Это особенно важно, если вы когда-нибудь планируете использовать одну и ту же кнопку ButtonWndProc() более чем для одной кнопки одновременно. Вам понадобится отдельный объект для хранения переменных каждой кнопки.

Кстати: когда вы обновляете значение своих переменных, вам не нужно вызывать UpdateWindow(), чтобы принудительно перерисовать. Вызова InvalidateRect() самого по себе будет достаточно, чтобы сигнализировать ОС о том, что кнопку необходимо перекрасить. Новое сообщение WM_PAINT появится автоматически, когда ОС действительно будет готова перерисовать кнопку.

Например, попробуйте что-то вроде этого:

#define UNICODE
#include <windows.h>
#include <CommCtrl.h>
#include <windowsx.h>
#include <iostream>

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK ButtonWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);

struct ButtonVars
{
    bool isTracking;
    bool isHover; 
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{    
    const wchar_t g_szClassName[] = L"myWindowClass";
    WNDCLASSEX wc = {0};
    HWND hwnd;
    MSG Msg;
    
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = CreateSolidBrush(RGB(30, 30, 30));
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    
    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"فشل تسجيل فئة النافذة", L"خطأ", MB_OK);
        return 1;
    }
    
    hwnd = CreateWindowEx(0, L"MyWindowClass", L"نافذة جديدة",
                          WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
                          500, 350, NULL, NULL, wc.hInstance, NULL);

    if (hwnd == NULL)
    {
        MessageBox(NULL, L"فشل إنشاء النافذة", L"خطأ", MB_OK);
        return 1;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    
    while (Msg.message != WM_QUIT)
    {
        if (GetMessage(&Msg, NULL, 0, 0))
        {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }
    }
    return 0;
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CREATE:
        {
            HWND hButton = CreateWindowEx(0, L"BUTTON", L"button1", WS_VISIBLE | WS_CHILD | BS_FLAT, 200, 200, 100, 40, hwnd, (HMENU)101, NULL, NULL);
            if (hButton == NULL)
                return -1;

            ButtonVars *vars = new ButtonVars;
            vars->isTracking = false;
            vars->isHover = false;

            if (!SetWindowSubclass(hButton, &ButtonWndProc, 1, reinterpret_cast<DWORD_PTR>(vars)))
            {
                delete vars;
                return -1;
            } 

            break;
        }

        case WM_DESTROY:
        {
            PostQuitMessage(0);
            break;
        }

        case WM_CLOSE:
        {        
            DestroyWindow(hwnd);
            break;
        }

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

    return 0;
}

LRESULT CALLBACK ButtonWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    ButtonVars *vars = reinterpret_cast<ButtonVars*>(dwRefData);

    switch (uMsg)
    {
        case WM_NCDESTROY:
        {
            RemoveWindowSubclass(hWnd, &ButtonWndProc, uIdSubclass);
            delete vars;
            return DefSubclassProc(hWnd, uMsg, wParam, lParam);
        }

        case WM_MOUSEMOVE:
        {
            std::cout << "WM_MOUSEMOVE RECEIVED " << std::endl;        
            std::cout << "hovered =  " << vars->isHover << std::endl; // to check the value of isHover
            if (!vars->isTracking)
            {
                TRACKMOUSEEVENT tme = {sizeof(TRACKMOUSEEVENT)};
                tme.dwFlags = TME_LEAVE | TME_HOVER;
                tme.hwndTrack = hWnd;  // hWnd;
                tme.dwHoverTime = 10; // milliseconds
                vars->isTracking = TrackMouseEvent(&tme);
            }

            break;
        }

        case WM_MOUSELEAVE:
        {
            std::cout << "(" << GET_X_LPARAM(lParam) << ", " << GET_Y_LPARAM(lParam) << ")" << std::endl;
            vars->isTracking = false;
            vars->isHover = false;
            std::cout << "WM_MOUSELEAVE RECEIVED " << std::endl; 
            std::cout << "hovered =  " << vars->isHover << std::endl; // to check the value of isHover
            InvalidateRect(hWnd, NULL, true);
            break;
        }

        case WM_MOUSEHOVER:
        {
            std::cout << "(" << GET_X_LPARAM(lParam) << ", " << GET_Y_LPARAM(lParam) << ")" << std::endl;
            vars->isTracking = true;
            vars->isHover = true;
            std::cout << "WM_MOUSEHOVER RECEIVED " << std::endl;        
            std::cout << "hovered =  " << vars->isHover << std::endl; // to check the value of isHover
            InvalidateRect(hWnd, NULL, true);
            break;
        }

        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            HBRUSH brush;
            std::cout << "WM_PAINT RECEIVED" << std::endl;
            std::cout << "paint hovered =  " << vars->isHover << std::endl; // to check the value of isHover when receiving WM_PAINT message

            if (vars->isHover)
            {
                brush = CreateSolidBrush(RGB(255, 0, 0));
            }
            else
            {
                brush = CreateSolidBrush(RGB(0, 255, 0));
            }

            FillRect(hdc, &ps.rcPaint, brush);
            DeleteObject(brush);
            EndPaint(hWnd, &ps);

            break;
        }

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

    return 0;
}

Что касается проблемы WM_DRAWITEM/WM_NOTIFY, это сообщение содержит HWND дочернего элемента управления, который его выдал. Вы можете сделать так, чтобы родительский WndProc отражал сообщение обратно в исходный дочерний элемент управления, если вы хотите, чтобы дочерний WndProc обрабатывал свои собственные уведомления. То же самое и с WM_COMMAND.

Большое спасибо за помощь. Он думал, что размещения переменных вне переключателя (msg) достаточно, но оказалось, что проблема заключалась в том, где переменные были созданы внутри кода.

stepic Game 07.04.2024 19:38

Что касается WM_DRAWITEM/WM_NOTIFY, есть ли причины или преимущества для его использования, пока я достигаю желаемого результата? Существуют ли сценарии, в которых предпочтительнее использовать WM_DRAWITEM/WM_NOTIFY? Обратите внимание, что в будущем я хотел бы создать класс для кнопки, включающий в себя, помимо собственных функций, собственные переменные. Поэтому я хочу сохранить вещи, чтобы каждый созданный мною элемент управления имел свойства, отличные от остальных, а также свои собственные функции.

stepic Game 07.04.2024 19:39
WM_DRAWITEM — это предполагаемый способ индивидуального рисования кнопки. Но создание подклассов будет работать нормально, если рисунок владельца не соответствует вашей модели.
Remy Lebeau 07.04.2024 21:46

Другие вопросы по теме