WM_LBUTTONUP message only working as a double click - c++

I have a ListView Control with a LVS_REPORT style set. The thing I want is that when I click the row of an item, doesn't matter where it is, even if it's above a subitem, the item gets selected. Currently, the ListView only detects clicks over the first column (item) to make the selection.
To do this, I tried to handle the WM_LBUTTONUP message, so when the user releases the left mouse button on the row rectangle, it selects the corresponding item. It could work, but currently it needs a double click to do the selection.
This is the code that goes in the subclass window procedure:
case WM_LBUTTONUP:
{
long x = GET_X_LPARAM(lParam);
long y = GET_Y_LPARAM(lParam);
RECT rc{};
Message("btn up"); // message box that shows when the left button is released
for (auto& item : lst->m_items)
{
ListView_GetItemRect(lst->handle(), item->index, &rc, LVIR_BOUNDS);
POINTS p;
if (PtInRect(&rc, { x, y })) // checks if clicked point is inside the item rect
{
ListView_SetItemState(lst->m_hwnd, item->index, LVIS_SELECTED, 7);
break;
}
}
return 0;
}
The same code (except the loop part) I put inside an Edit subclass procedure and the Main Window subclass procedure. Both work as intended, that is, when a hit a single click, it shows a message.
Is there a workaround for this? Couldn't find any style to achieve this objective, either. What could be causing this issue? I thought, for some reason, that either some other window could be above the ListView and that could be stopping it to receive new clicks or also it could be sending the click first to the parent window. These are not the case: 1) the control is not behind any other window; 2) if this was the case, the program would show a MessageBox with the message specified in the procedure, which doesn't happen.
Thanks in advance.

Related

Properly handling owner drawn Win32 button hovering

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!
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.
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.

C++ how to display text in child window at real time

This application creates a child window (which is the white box) when I right click anywhere, and destroys the child window after another right click. I have implemented the mechanics to expand and shrink the red rectangle through Direct 2D. I would like the child window to display the width and height of the rectangle at real time, as I make changes to it. I do not interact with the child window at all: it doesn't need an "x" button for closing and stuff; it just prints out a couple lines of data.
Here is what I have in my main window procedure:
case WM_RBUTTONUP:
{
DemoApp *pDemoApp = reinterpret_cast<DemoApp *>(static_cast<LONG_PTR>(
::GetWindowLongPtrW(hwnd, GWLP_USERDATA)));
pDemoApp->showTextBox = !pDemoApp->showTextBox; //showTextBox is a boolean
if (pDemoApp->showTextBox) {
POINTS cursor = MAKEPOINTS(lParam);
pDemoApp->child_hwnd = CreateWindowEx(
WS_EX_TOPMOST,
"LISTBOX",
"I dont need a title here",
WS_CHILDWINDOW,
cursor.x,
cursor.y,
100,
200,
pDemoApp->main_hwnd,
NULL,
HINST_THISCOMPONENT,
pDemoApp
);
ShowWindow(pDemoApp->child_hwnd, SW_SHOWNORMAL);
UpdateWindow(pDemoApp->child_hwnd);
}
else {
DestroyWindow(pDemoApp->child_hwnd);
}
}
break;
How may I go from here? I would like to know:
Is using Direct Write to draw text in the child window my only option? I see the dashed lines in the white box so I assume there must be a way to display plain text.
I used LISTBOX here, which is a predefined windows class name. How do I set a procedure for it? What else predefined class name can better suit my need? Or do I have to register a custom one;
I would like to drag the child window around, how can I set it up so that the system handles dragging for me.
Would popping a dialog box to display text be better than popping a child window?
Thanks.
Since you are using a Win32 LISTBOX control as the child window, then you have a couple of options for displaying the rectangle's dimensions in it:
give the ListBox the LBS_HASSTRINGS style, and add 1-2 items to it. Then, any time you change the rectangle, send LB_DELETESTRING and LB_ADDSTRING messages to the ListBox's HWND to display the updated dimensions as needed. A ListBox does not have a way to update an existing item, so to change an existing item's text, you have to remove the item and then re-add it with the new text.
give the ListBox an LBS_OWNERDRAW... style, and add 1-2 blank item(s) in it. Then have the ListBox's parent window handle WM_MEASUREITEM and WM_DRAWITEM notifications from the ListBox to display the rectangle's current dimensions in the item(s) as needed. Whenever the rectangle is changed, call InvalidateRect() on the ListBox's HWND to trigger a redraw.

Dragging the console window with ReadConsoleInput()

As you can see from the code snippet below, I've implemented a way to drag the Windows command prompt by its client area.
The problem with this code is that if the user follows these steps:
Unfocus console window
Focus console window by clicking and dragging it (without releasing)
Drag the window so that the cursor escapes window area (this can mean moving the cursor too quickly, or out of the set bounds (second monitor), or over the taskbar/other always-on-top window)
The console window will stop following the cursor, until it's moved to be inside the window again.
The fact that this does not happen when the console window is already in-focus at step 1, is what's really weird to me. I've tried debugging this for so many hours now, I just can't do it anymore. I'd appreciate any help regarding this.
// Continuously read input
while(ReadConsoleInput(hIn, &ir, 1, &nr))
{
switch(ir.EventType)
{
// Left mouse button down that either focuses or unfocuses console window
case FOCUS_EVENT:
// Left mouse button down that focuses console window
if(ir.Event.FocusEvent.bSetFocus)
{
GetCursorPos(&firstPos);
ScreenToClient(hWnd, &firstPos);
}
break;
case MOUSE_EVENT:
// Mouse did something inside console window
switch(ir.Event.MouseEvent.dwButtonState)
{
// Left mouse button down or up
case FROM_LEFT_1ST_BUTTON_PRESSED:
// Left mouse down or up, no drag
if(!ir.Event.MouseEvent.dwEventFlags)
{
GetCursorPos(&firstPos);
ScreenToClient(hWnd, &firstPos);
}
// Left button down, and mouse move. -> drag
if(ir.Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
{
GetCursorPos(&currentRelativeToScreen);
// Calculate window position while dragging
// |
// v
if(currentRelativeToScreen.x - firstPos.x > scrnSz.right - ca.right)
wndPos.X = scrnSz.right - ca.right;
else if(currentRelativeToScreen.x - firstPos.x < 0)
wndPos.X = 0;
else
wndPos.X = currentRelativeToScreen.x - firstPos.x;
if(currentRelativeToScreen.y - firstPos.y > scrnSz.bottom - ca.bottom)
wndPos.Y = scrnSz.bottom - ca.bottom;
else if(currentRelativeToScreen.y - firstPos.y < 0)
wndPos.Y = 0;
else
wndPos.Y = currentRelativeToScreen.y - firstPos.y;
// End window position calculations
SetWindowPos(hWnd, 0, wndPos.X, wndPos.Y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
break;
default:
break;
}
break;
default:
break;
}
Drag the window so that the cursor escapes window area [...] The console window will stop following the cursor, until it's moved to be inside the window again.
This is the expected behavior, as documented at https://msdn.microsoft.com/en-us/library/windows/desktop/ms684239.aspx (emphasis added).
Mouse events are generated whenever the user moves the mouse, or presses or releases one of the mouse buttons. Mouse events are placed in a console's input buffer only when the console group has the keyboard focus and the cursor is within the borders of the console's window.
[ EDIT] To answer the followup comment about this.
this does not happen when the console window is already in-focus at step 1
Though not obvious from the official documentation, the console window appears to capture the mouse when clicked (and thus track it even when moved outside the console window) only if it already had the focus at the time of the click. For a console window out of focus, the 1st click gives it the focus (without globally capturing the mouse, so it only receives MOUSE_MOVE notifications while the cursor is within its client area), while the 2nd click enters exclusive capture mode (and receives all MOUSE_MOVE notifications regardless of cursor position).
This can be verified with a standard console window set to quick-edit mode. If the window has the input focus, then click-and-drag selects text in the console, even if the mouse is moved outside the window. However, if the window does not have the focus. the 1st click just gives it the focus, but does not enter any capture mode and dragging does not select any text.
on mouse move do:
if(button==leftButton){
ReleaseCapture();
SenMessage(hWnd,WM_NCLBUTTON_DOWN,HTCAPTION,lParam);
}

How can I selectively make QWidget accept focus from a mouse click?

I'm creating an application that provides a visual representation of nodes and lines connecting them together. Both nodes and lines are represented by custom QWidgets, WidgetNode and WidgetLine, say.
I've implemented WidgetLine as a transparent widget that is large enough to contain the start and end point of the line, and a custom function to draw the line itself.
I would like it so that if the user clicks on or right next to the line then the WidgetLine receives focus, but if they click further away from the line (but still over the rectangular area covered by WidgetLine's geometry) then the click is completely ignored by WidgetLine and passed on to the widget below.
I first tried doing this with a custom focusInEvent() function on WidgetLine, but found the mouse clicks weren't propagating below. I then tried setting the focus policy to Qt::NoFocus and using a custom mousePressEvent() using setFocus() to manually set the focus when appropriate, but the mouse events are still not being propagated to widgets above even when I call ignore() on them.
Finally, I've tried installing an event filter to reject mouse events, with this event filter function
bool WidgetLineFilter::eventFilter(QObject* object, QEvent* event)
{
assert(object == mCord);
if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* e = dynamic_cast<QMouseEvent*>(event);
assert(e);
if (e)
{
QPoint mouseRelativeToParent = mCord->mapToParent(e->pos());
// calculate distance of mouse click to patch cord
QLineF line(mCord->line());
float distance = distanceFromPointToLine(QVector2D(line.p1()), QVector2D(line.p2()), QVector2D(mouseRelativeToParent));
qDebug() << distance;
const float distanceThreshold = 2.f;
if (distance < distanceThreshold)
{
qDebug() << "consuming mouse click for focus";
mCord->setFocus(Qt::MouseFocusReason);
return true;
}
else{
qDebug() << "mousepressevent too far for focus";
return QObject::eventFilter(object, event);
}
}
}
return false;
}
But this still does not propagate the mouse events to the parent on the "mousepressevent too far for focus" case. I've also tried returning false and true from here, and calling ignore on e but the widgets below are not receiving the click.
(NB the above approaches do work in the sense that WidgetLine only gets focus at the right time, it's just the widgets below that aren't receiving the press events when it doesn't get focus.)
Any ideas on how to fix this?
Store the mouse pos in a global var. Have all of your widgets have enter/leave events like the following and use that to check what widget you're in / near when you run the func.
void QGLWidget::enterEvent(QEvent *)
{
setFocus();
}
void QGLWidget::leaveEvent(QEvent *)
{
clearFocus();
}
In the end, I created an event filter to selectively intercept mouse events and installed it on the base window and recursively on every single child widget of the base window (installing it on new child widgets when they are created). This filter calls the base window with each mouse press event which then iterates through each WidgetLine, testing if they should be selected by this mouse press and setting focus on them if they should. If they all test false then the filter releases the event, otherwise the filter consumes it.
WidgetLine's are then set to be transparent for mouse events with
setAttribute(Qt::WA_TransparentForMouseEvents);
It's messier than it ought to be to achieve this but does the trick.

Raphael - mouse events

How would you determine what mouse buttons was clicked on a mouse event?
I would like to make the distinction between left and right click.
I am not sure about the click event (though it may be worth a try) but if you use the mousedown event which should be good enough, then you can check the "which" property of the event.
The which property is an integer that determine the mouse button, and the values are mapped like...
1 = Left Button
2 = Middle Button
3 = Right Button
Example code...
element.mousedown(ElementMouseDown);
function ElementMouseDown(e) {
switch(e.which){
case 1://left button
break;
case 2://middle button
break;
case 3://right button
break;
}
}