Я пытаюсь создать кнопку и обрабатывать возможные события, например нажатие кнопки и наведение курсора.
Я нашел много полезных статей и до сих пор смог проверить события. У меня проблема с рисованием.
В коде, который я вам представляю, я создал подкласс для кнопки и хочу обрабатывать все события через него, а не через 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);
}





Ваши переменные являются локальными для 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.
Что касается WM_DRAWITEM/WM_NOTIFY, есть ли причины или преимущества для его использования, пока я достигаю желаемого результата? Существуют ли сценарии, в которых предпочтительнее использовать WM_DRAWITEM/WM_NOTIFY? Обратите внимание, что в будущем я хотел бы создать класс для кнопки, включающий в себя, помимо собственных функций, собственные переменные. Поэтому я хочу сохранить вещи, чтобы каждый созданный мною элемент управления имел свойства, отличные от остальных, а также свои собственные функции.
WM_DRAWITEM — это предполагаемый способ индивидуального рисования кнопки. Но создание подклассов будет работать нормально, если рисунок владельца не соответствует вашей модели.
Большое спасибо за помощь. Он думал, что размещения переменных вне переключателя (msg) достаточно, но оказалось, что проблема заключалась в том, где переменные были созданы внутри кода.