如何检测将鼠标悬停在静态Win32控件上?

时间:2018-07-04 00:33:42

标签: c++ winapi

我无法检测到在静态Win32控件上的悬停。

这不是重复的问题,因为这将查找多个静态控件,而不是在编译时查找静态控件的单个已知句柄。

尽管这可以用另一种语言在几秒钟内完成,但是尝试了几个小时后,我感到有些沮丧。我希望在这里得到答案。

首先,我创建了一个名为Label的类。我在其中创建一个静态控制窗口。我现在将静态称为标签。

// 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;

当鼠标悬停在此标签上时,应调用以下方法:

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

这将调用我的方法:

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

这是我从顶层创建它的方式:

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

承认,这薄层很漂亮。

尽管我的按钮和Checkbox控件的回调工作正常,但我注意到静态变量有些不同。

因此,让我们分为几个层次:

这在主窗口的过程中:

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; 

我在这里要做的是检测检测到的标签ID,然后调用Label :: invokeOnMouseOver()。

即使我知道我需要在某个时候使用TRACKMOUSEEVENT,但其字段成员“ HWND”仍需要标签的句柄。但是我不能轻易说出它将是哪个处理,因为该集合可能包含一个或多个标签。

总体而言,我正在寻找有关如何进行重组或在此处查看是否有简单解决方案的建议。我的猜测是我对此太想了。

谢谢。

更新

在阅读第一个解决方案的第一个答案后,这里是代码更新。虽然这解决了悬停问题,但执行时看不到标签的文本。

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; 

以及控件的创建:

// 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);

如果是油漆问题,这就是我在WndProc中遇到的问题。

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

        return 0;
    }
    break;

更新#2:

替代解决方案解决了该问题。

如果将来有人用SetWindowSubclass()遇到无法解析的外部符号时遇到麻烦,请记住将以下内容添加到您的项目中,或者只是#pragma它:

#pragma comment(lib, "comctl32.lib")

2 个答案:

答案 0 :(得分:3)

WM_MOUSEMOVE提供给您的唯一识别信息是鼠标移过的HWND(或具有captured the mouseHWND)。报告的X / Y坐标相对于HWND。这就是您要寻找的HWND,因此您无需寻找它,消息便将其提供给您。

如果您需要访问HWND的控件ID,则可以使用GetDlgCtrlID()。但是请注意,每个HWND都有自己的窗口过程,因此控件ID通常仅对通知消息有用,例如WM_COMMANDWM_NOTIFY,这些消息发送到控件的父窗口(即使这样,此类通知也将带有孩子的HWND)。

当鼠标移到特定的HWND上时,WM_MOUSEMOVE仅发布到 HWND的消息过程(或{{1} }(已捕获鼠标)。听起来您希望将其发布到控件的 parent 窗口中,而事实并非如此。这就是为什么未调用您的HWND处理函数的原因。您正在错误级别上处理该消息。您需要准备使用控件自己的消息过程在每个控件的基础上处理消息。

通过WM_MOUSEMOVEControl或{{1}将this对象的HWND指针存储在与其关联的SetWindowLongPtr(GWLP_USERDATA)本身中会更有效。 }},然后您的消息处理程序可以在需要时访问消息报告的SetWindowSubClass()的{​​{1}}指针,例如:

SetProp()

或者:

Control*

答案 1 :(得分:0)

这篇文章没有回答您提出的问题,但您应该阅读它。这很重要,在上面我颇为刻薄的评论之后,本着帮助的精神发布了它。我希望你能找到它。

该类库将遇到麻烦。像这样的代码(使用dynamic_cast):

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;
}

几乎总是错误的。

为什么?好吧,假设您想将鼠标悬停在其他控件上?你现在要做什么?这些家伙就像兔子一样繁殖,所以不要那样做。

相反,对于库可以理解的消息(例如此消息),请在基类中声明一个相应的 virtual 方法,如果派生类对处理该消息感兴趣,则可以重写该方法。 然后,您已经有了扎实的设计基础(这是非常基础的东西)。

因此,在这种情况下,您将:

class Control                  // Base class
{
    ...
    virtual void onMouseHover (...) { }
    ...
};

然后:

class Label : public Control   // Derived class
{
    ...
    virtual void onMouseHover (...) override { ...  }
    ...
};

现在,我要说第二点:您将发现,特别是对于对话框,您的应用程序将要处理许多基类不理解(或关心)的消息。

您将如何执行此操作?您是否要为应用程序(或为此在类库中实现的特定控件类型)感兴趣的每条新消息向基类添加代码?那不是一个非常诱人的前景。

现在MFC使用称为message map的东西来处理此问题,它实际上是消息ID及其对应的命令处理程序的表,您可以将它们与(从您自己的情况)最终从{{1}派生的任何对象相关联},我建议您做类似的事情。

但是由于STL的神奇之处,您可以做得更好。我的类库中有类似的内容(我建议您将基类实际上称为Control):

Window

typedef INT_PTR (Window::*MESSAGE_HANDLER) (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // Register a message handler void Window::RegisterMessageHandler (UINT uMsg, MESSAGE_HANDLER handler); 的实际作用是使用RegisterMessageHandler()作为键,将handler添加到与std::unordered_map对象关联的Window上。然后,当该消息随后进入时,可以将其分派到正确的处理程序,而基类无需知道任何有关消息含义的信息,而这正是您所需要的。 >

因此,您可以在类uMsg中声明以下内容(未经测试的代码,用记事本编写):

Control

然后class Control // Base class { ... std::unordered_map <UINT, MESSAGE_HANDLER> m_message_map; ... }; 可能看起来像这样:

RegisterMessageHandler()

void Window::RegisterMessageHandler (UINT uMsg, MESSAGE_HANDLER handler) { m_message_map.emplace (uMsg, handler); } 可能看起来像这样:

MySubclassProc()

我自己的类库实际上比这更复杂(我从简单的内容开始,但随着时间的推移对其进行了修饰),但这是基本思想。您可能需要掌握一些C ++技能才能做到这一点,但是请相信我,如果您实现这样的事情,您会很高兴的。