如何检测将鼠标悬停在静态 Win32 控件上
How to detect hovering over a static Win32 control?
我在检测到静态 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_COMMAND
和WM_NOTIFY
)有用(即使这样,此类通知也会携带子窗口的HWND
)。
当鼠标移动到特定HWND
上时,WM_MOUSEMOVE
仅发布到该HWND
的消息过程(或捕获鼠标的HWND
)。 听起来您希望将其发布到控件的父窗口,但事实并非如此。 这就是不调用WM_MOUSEMOVE
处理程序的原因。 您正在错误的级别处理邮件。 您需要准备好使用控件自己的消息过程来处理每个控件的消息。
通过SetWindowLongPtr(GWLP_USERDATA)
、SetWindowSubClass()
或SetProp()
将Control
对象的this
指针存储在其关联的HWND
内会更有效,然后消息处理程序可以在需要时访问消息报告HWND
的Control*
指针,例如:
// 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++技能才能做到这一点,但相信我,如果你实现了这样的事情,你会很高兴你做到了。
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- #为""定义宏;静态";针对不同的上下文
- cmake如何在fedora工作站中找到boost静态库包
- 静态数据成员的问题-修复链接错误会导致编译器错误
- 将公共但非静态的成员函数与ALGLIB集成
- cmake在我的项目中所需的所有静态库都不成功
- C++从另一个类访问公共静态向量的正确方法是什么
- 基于boost的程序的静态链接——zlib问题
- 在静态库中嵌入类方法
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- 如何在C++中获得"静态纯虚拟"功能?
- 私有类型的静态常量成员
- 使用gcc从静态链接的文件中查找可选符号
- 在 .h 文件中的类中声明静态变量和在.cpp文件中声明"global"变量有什么区别
- 如何在C++中使用非静态成员函数作为回调函数
- 将静态库链接到不带-fPIC的共享库中
- 静态结构和一个定义规则
- 为什么虚函数不能是静态的和全局的?
- 如何检测将鼠标悬停在静态 Win32 控件上
- 为什么将鼠标悬停在静态 Win32 控件上会增加内存并删除我的 GUI?