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

How to detect hovering over a static Win32 control?

本文关键字:悬停 静态 Win32 控件 鼠标 何检测 检测      更新时间:2023-10-16

我在检测到静态 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);

承认吧,这薄薄的一层很漂亮。

虽然我的回调适用于我的按钮和复选框控件,但我注意到静态有点不同。

因此,让我们降低几个级别:

这是在主窗口的过程中:

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")

WM_MOUSEMOVE为您提供的唯一识别信息是鼠标移动的HWND(或捕获鼠标的HWND)。 报告的 X/Y 坐标相对于该HWND。 这就是您正在寻找的HWND,因此您无需寻找它,消息会给您。

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

当鼠标移动到特定HWND上时,WM_MOUSEMOVE仅发布到HWND的消息过程(或捕获鼠标的HWND)。 听起来您希望将其发布到控件的窗口,但事实并非如此。 这就是不调用WM_MOUSEMOVE处理程序的原因。 您正在错误的级别处理邮件。 您需要准备好使用控件自己的消息过程来处理每个控件的消息。

通过SetWindowLongPtr(GWLP_USERDATA)SetWindowSubClass()SetProp()Control对象的this指针存储在其关联的HWND内会更有效,然后消息处理程序可以在需要时访问消息报告HWNDControl*指针,例如:

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

或者:

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

这篇文章没有回答你提出的问题,但你应该阅读它。 这很重要,在我上面相当酸的评论之后,它是本着乐于助人的精神发布的。 我希望你找到它。

这个类库会遇到麻烦。 像这样的代码(使用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;
}

几乎总是错的。

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

相反,对于库理解的消息(例如此消息),请在基类中声明相应的虚拟方法,如果派生类有兴趣处理该消息,则可以重写该方法。然后你就有了坚实设计的基础(这是非常基本的东西)。

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

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

然后:

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

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

你打算怎么做? 是否要为应用程序(或类库中实现的特定类型的控件)感兴趣的每条新消息向基类添加代码? 这不是一个很有吸引力的前景。

现在,MFC 使用它称为消息映射的东西来处理这个问题,它本质上是一个消息 ID 及其相应命令处理程序的表,您可以将其与(在您的情况下)最终派生自Control的任何对象相关联,我建议您做类似的事情。

但是由于STL的魔力,你可以做得更好。 我的类库中有这样的东西(我的基类实际上被称为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()实际做的是使用uMsg作为键将handler添加到与Window对象关联的std::unordered_map中。 然后,当该消息随后传入时,可以将其调度到正确的处理程序,而基类不知道消息的含义,这就是您需要的。

因此,您可以在类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()可能看起来像这样:

LRESULT CALLBACK MySubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;
auto handler ctrl->m_message_map.find (uMsg);
if (handler != ctrl->m_message_map.end ())
return handler.second (hWnd, uMsg, wParam, lParam);
...
}

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