[英]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);
承認它,這薄薄的層是美麗的。
雖然我的回調對我的Button和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 it:
#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 ++技能才能解決這個問題,但要相信我,如果你實現這樣的東西,你會非常高興你做到了。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.