Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect hovering over a static Win32 control?

Tags:

c++

winapi

I'm having trouble detecting a hover over a static Win32 control.

This is not a duplicate issue because this looks for multiple static controls instead of just looking for a single known handle to a static control at compile time.

While this can be done in seconds in another language, I'm growing a little frustrated after trying things out for some hours. I hope to get an answer here.

First, I created a class called Label. I create a static control window within it. I'll refer to static as label for now on.

// Create the label's handle.
m_handle = CreateWindowEx(NULL, "static", m_text.c_str(),
    WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
    m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (m_handle == NULL)
    return false;

When the mouse hovers over this label, the following method should be called:

void Label::invokeOnMouseHover()
{
    if (m_onMouseOver)
        m_onMouseOver();
}

This will call my method:

void lblName_onMouseOver()
{
    MessageBox::show("Hovering!", "My Console",
        MessageBoxButtons::Ok, MessageBoxIcon::Information);
}

This is how I create it from the top level:

Label lblName("This is a label.", 0, 0);
lblName.setVisible(true);
lblName.OnMouseOver(lblName_onMouseOver); 
frm.add(lblName);

Admit it, this thin layer is beautiful.

While my callbacks are working fine for my Button and Checkbox controls, I noticed that statics are a little different.

So, let's go down a few levels:

This is in the main window's procedure:

case WM_MOUSEMOVE:
{  
    int xPos = LOWORD(lParam);
    int yPos = HIWORD(lParam);

    frm.setMousePos(xPos, yPos);  

    // Get the static's id
    int id = // ?? Which static control id is it out of several?

                // Obtain the control associated with the id.
        X3D::Windows::Control *ctrl = frm.getControls().find(id)->second;
    if (ctrl == NULL)
        return 0;

    // Check if this is a X3D Label control.
    if (typeid(*ctrl) == typeid(X3D::Windows::Label))
    {
        Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);

        int xPos = GET_X_LPARAM(lParam);
        int yPos = GET_Y_LPARAM(lParam);

        if (xPos >= lbl->getX() &&
            yPos >= lbl->getY() &&
            (xPos < (lbl->getX() + lbl->getWidth())) &&
            (yPos < (lbl->getY() + lbl->getHeight())))
        {
            if (lbl != NULL)
                lbl->invokeOnMouseHover();
        }
    }
}
break; 

What I'm trying to do here is detect what label id was detected, and then call Label::invokeOnMouseOver().

Even though I Understand I need to use TRACKMOUSEEVENT at some point, its field member 'HWND' requires the label's handle. But I can't easily say which handle it will be because the collection may contain one or several labels.

Overall, I'm looking for suggestions on how to either restructure this or see if there's an easy solution here. My guess is I'm overthinking this.

Thanks.

Update:

Here is a code update after reading the first answer with the first solution. While this solves the hover issue, I don't see the label's text on execution.

case WM_MOUSEMOVE:
{  
    int xPos = LOWORD(lParam);
    int yPos = HIWORD(lParam);

    // Get the mouse position
    frm.setMousePos(xPos, yPos);  

    // Check for labels
    X3D::Windows::Control *ctrl = (X3D::Windows::Control*)GetWindowLongPtr(hWnd, GWLP_USERDATA);

    if (ctrl)
    {
        // Check if this is a X3D Label control.
        Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
        if (lbl)
            lbl->invokeOnMouseHover();

        return CallWindowProc(lbl->getOldProc(), hWnd, msg, wParam, lParam);
    } 
}
break; 

And the control creation:

// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
    WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
    m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
    return false;

SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)(X3D::Windows::Control*)this);
m_oldProc = (WNDPROC)SetWindowLongPtr(m_handle, GWLP_WNDPROC, (LONG_PTR)&wndProc);

If it's a paint issue, this is what I have in WndProc.

    case WM_PAINT:
    {
        hDC = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);

        return 0;
    }
    break;

Update #2:

The alternative solution fixed the issue.

If anyone has trouble with unresolved external symbols with SetWindowSubclass() in the future, remember to add the following to your project or just #pragma it:

#pragma comment(lib, "comctl32.lib")
like image 933
Bob Avatar asked Jul 04 '18 00:07

Bob


1 Answers

The only identifying info that WM_MOUSEMOVE gives you is the HWND that the mouse is moving over (or the HWND that has captured the mouse). The reported X/Y coordinates are relative to that HWND. That is the HWND that you are looking for, so you don't need to hunt for it, the message gives it to you.

If you need access to an HWND's control ID, you can use GetDlgCtrlID() for that. But note that every HWND has its own window procedure, so control IDs are typically only useful for notification messages, like WM_COMMAND and WM_NOTIFY, which are sent to a control's parent window (and even then, such notifications also carry the child's HWND as well).

When the mouse moves over a particular HWND, WM_MOUSEMOVE is posted to the message procedure of that HWND only (or to the HWND that has captured the mouse). It sounds like you are expecting it to be posted to the control's parent window instead, and that is simply not the case. That is why your WM_MOUSEMOVE handler is not being called. You are handling the message at the wrong level. You need to be prepared to handle messages on a per-control basis instead, using the control's own message procedure.

It would be more efficient to store your Control object's this pointer inside its associated HWND itself, via SetWindowLongPtr(GWLP_USERDATA), SetWindowSubClass(), or SetProp(), and then your message handlers can access the Control* pointer of the message's reported HWND when needed, for example:

// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
    WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
    m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
    return false;
SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)(X3D::Windows::Control*)this);
m_oldproc = (WNDPROC) SetWindowLongPtr(m_handle, GWL_WNDPROC, (LONG_PTR)&MyWndProc);

...

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_MOUSEMOVE:
        {
            ...

            X3D::Windows::Control *ctrl = (X3D::Windows::Control*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
            if (ctrl)
            {
                // Check if this is a X3D Label control.
                Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
                if (lbl)
                    lbl->invokeOnMouseHover();
            }

            break;
        }

        ...
    }

    return CallWindowProc(m_oldproc, hWnd, uMsg, wParam, lParam);
}

Alternatively:

// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
    WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
    m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
    return false;
SetWindowSubclass(m_handle, &MySubclassProc, 1, (DWORD_PTR)(X3D::Windows::Control*)this);

...

LRESULT CALLBACK MySubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uMsg)
    {
        case WM_NCDESTROY:
            RemoveWindowSubclass(hWnd, &MySubclassProc, uIdSubclass);
            break;

        case WM_MOUSEMOVE:
        {
            X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;

            // Check if this is a X3D Label control.
            Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
            if (lbl)
                lbl->invokeOnMouseHover();

            break;
        }

        ...
    }

    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
like image 176
Remy Lebeau Avatar answered Nov 15 '22 03:11

Remy Lebeau