I want to add multiple color themes to my Win32 application, this means that I have to manually handle all the control drawing manually by using the BS_OWNERDRAW
style flag. I then handle all the drawing in the WM_DRAWITEM
message through the LPDRAWITEMSTRUCT
structure stored in the lParam
. Here's the problem though, by introducing owner drawing I also have to handle click and hover events, which in and of itself isn't a problem but it becomes one because it turns out that the LPDRAWITEMSTRUCT
has no message being sent to indicate whether or not a control is being hovered. The itemState
member do have messages like ODS_SELECTED
, ODS_FOCUS
and so on, but no flag for hovering only.
That being said, there does exist a flag named ODS_HOTLIGHT
which according to MSDN should do the job, the problem however is that this flag never occurs for buttons, only menu items. I've tried using the SetWindowSubclass
function by giving each control it's separate WindowProc callback where you can track the mouse leaving and entering the control. This works, but that also means that I need to transition all drawing commands over to the subclass procedure, which seems rather stupid to me, since the WM_DRAWITEM
is intended for custom drawing in the first place.
You could always send WM_DRAWITEM
manually via SendMessage
but that also means that I need to provide an LPDRAWITEMSTRUCT
manually which hasn't worked out that great. In essence I do not know what the best approach is to react to these hover events and draw them accordingly, the LPDRAWITEMSTRUCT
does not provide such a flag and thus I have no idea what other approach to use. How should I tackle this?
The way I'm currently handling the WM_DRAWITEM
message (buttons only):
case WM_DRAWITEM: {
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
HPEN borderPen = CreatePen(PS_INSIDEFRAME, 0, RGB(0, 0, 0));
HGDIOBJ oldPen = SelectObject(pDIS->hDC, borderPen);
HGDIOBJ oldBrush;
if (pDIS->itemState & ODS_SELECTED) {
oldBrush = SelectObject(pDIS->hDC, buttonHoverBrush);
}
else {
oldBrush = SelectObject(pDIS->hDC, buttonDefaultBrush);
}
// Rounded button
RoundRect(pDIS->hDC, pDIS->rcItem.left, pDIS->rcItem.top, pDIS->rcItem.right, pDIS->rcItem.bottom, 5, 5);
//Clean up
SelectObject(pDIS->hDC, oldPen);
SelectObject(pDIS->hDC, oldBrush);
DeleteObject(borderPen);
// Calculate button dimensions
int buttonWidth = pDIS->rcItem.right - pDIS->rcItem.left;
int buttonHeight = pDIS->rcItem.bottom - pDIS->rcItem.top;
WCHAR staticText[128];
int len = SendMessage(pDIS->hwndItem, WM_GETTEXT, ARRAYSIZE(staticText), (LPARAM)staticText);
HFONT buttonFont = (HFONT)SendMessage(pDIS->hwndItem, WM_GETFONT, 0, 0);
SIZE buttonDim;
HFONT oldFont = (HFONT)SelectObject(pDIS->hDC, buttonFont);
GetTextExtentPoint32(pDIS->hDC, staticText, len, &buttonDim);
SetTextColor(pDIS->hDC, RGB(255, 255, 255));
SetBkMode(pDIS->hDC, TRANSPARENT);
TextOut(pDIS->hDC, buttonWidth / 2 - buttonDim.cx / 2, buttonHeight / 2 - buttonDim.cy / 2, staticText, len);
wasHandled = TRUE;
result = TRUE;
break;
}
The code listed above creates a button with a given background color and centers text within it. It also changes color to the hover rush when clicked. What I want to happen is that the button changes its color immediately upon hover and not upon click. Thank you in advance!
CodePudding user response:
I've tried using the
SetWindowSubclass
function by giving each control it's separateWindowProc
callback where you can track the mouse leaving and entering the control. This works, but that also means that I need to transition all drawing commands over to the subclass procedure, which seems rather stupid to me, since theWM_DRAWITEM
is intended for custom drawing in the first place.
Unfortunately, that is precisely what you will likely need to do.
For instance, you can have the button's subclass procedure handle the WM_MOUSEMOVE
message to detect when the mouse is over the button, and handle the WM_MOUSELEAVE
message to detect when the mouse moves out of the button. The message handler can use WM_MOUSEMOVE
to call TrackMouseEvent()
to trigger WM_MOUSELEAVE
and WM_MOUSEHOVER
messages. It can then use WM_MOUSEHOVER
to set a flag and invalidate the button to trigger a repaint, and it can use WM_MOUSELEAVE
to clear the flag and invalidate the button to trigger a repaint.
Inside the button's normal draw handler (which DOES NOT need to be moved to the subclass procedure. BTW), if the flag is set then draw the button as hovered, otherwise draw it as non-hovered.
The only gotcha with this approach is if you have multiple buttons then you will need multiple flags, one per button. But you can use (Get|Set)WindowLongPtr(GWL_USERDATA)
or (Get|Set)Prop()
to store/retrieve each button's flag inside/from the button's HWND
directly Or, you can just maintain your own std::map
(or other lookup table) of HWND
-to-flag associations.
Another option would be to have the subclass procedure use the WM_MOUSE(MOVE|HOVER|LEAVE)
messages to send simulated WM_DRAWITEM
messages to the button, specifying/omitting the ODS_HOTLIGHT
style as needed so the drawing code can look for that style. You might need the subclass procedure to intercept WM_PAINT
messages to make that work, though. Not sure about that.
Otherwise, you could simply have your WM_DRAWITEM
handler grab the mouse's current coordinates and see if they fall within the button's client area, and then draw accordingly.