Intercepting dialog focus events
В рамках моего мини-проекта
BITS Manager решил сделать удобный и красивый диалог. Основная изюминка - заголовки элементов управления, которые меняют свой внешний вид в зависимости от движения фокуса. Если конкретнее - меняется шрифт у заголовка активного элемента:
В процессе воникла проблема - как перехватить все события от фокуса дочерних элементов управления в одном месте? Добавлять по обработчику на каждый контрол - не очень хорошая идея. Тем более, что код, модифицирующий внешний вид заголовков, вполне обобщенный.
Я увидел следующие решения данной проблемы:
- В глобальном фильтре сообщений приложения ловить
WM_SETFOCUS
иWM_KILLFOCUS
.Проверять принадлежность целевого окна интересующему диалогу и, соответственно, обрабатывать нужным образом.
- Использовать перехват сообщений - Win32 Hooks.
Ловушка
WH_CBT
с кодомHCBT_SETFOCUS
позволяет отловить момент, когда система собирается установить фокус на окно. Это хорошо, но пришлось бы сохранять и обновлять идентификатор окна, которое будет терять фокус. Можно использовать и другие ловушки для перехвата сообщенийWM_SETFOCUS
иWM_KILLFOCUS
. - Воспользоваться тем, что все известные Win32 элементы управления шлют родителю уведомления о некоторых событиях, среди которых
***_SETFOCUS
и***_KILLFOCUS
.
Я остановился на 3-м варианте, первые два мне показались черезчур сложными для такой задачи.
How to intercept dialog focus events in one place
Пришлось слегка
почитать документацию и покопаться в коде MFC. Итак, есть два типа элементов управления. Первый тип использует сообщение WM_NOTIFY
для уведомления родителя, второй тип использует WM_COMMAND
. Оказалось, MFC в любом случае вызывает виртуальный метод BOOL OnCmdMsg(UINT nID, int nCode, void *pExtra, AFX_CMDHANDLERINFO *pHandlerInfo)
. После медитаций у меня получился такой код:
BOOL CJobProfileDlg::OnCmdMsg(UINT nID, int nCode, void *pExtra, AFX_CMDHANDLERINFO *pHandlerInfo) { const MSG &lastSentMsg = AfxGetThreadState()->m_lastSentMsg; LPCTSTR szEditClass = _T("EDIT"); LPCTSTR szComboBoxClass = _T("COMBOBOX"); LPCTSTR szListBoxClass = _T("LISTBOX"); if ( ((nCode == EN_KILLFOCUS) && Utility::CompareWindowClass(reinterpret_cast<HWND>(lastSentMsg.lParam), szEditClass)) || ((nCode == CBN_KILLFOCUS) && Utility::CompareWindowClass(reinterpret_cast<HWND>(lastSentMsg.lParam), szComboBoxClass)) || ((nCode == LBN_KILLFOCUS) && Utility::CompareWindowClass(reinterpret_cast<HWND>(lastSentMsg.lParam), szListBoxClass)) || ((HIWORD(nCode) == WM_NOTIFY)) && (LOWORD(nCode) == LOWORD(NM_KILLFOCUS))) { OnControlLostFocus(nID); } if (((nCode == EN_SETFOCUS) && Utility::CompareWindowClass(reinterpret_cast<HWND>(lastSentMsg.lParam), szEditClass)) || ((nCode == CBN_SETFOCUS) && Utility::CompareWindowClass(reinterpret_cast<HWND>(lastSentMsg.lParam), szComboBoxClass)) || ((nCode == LBN_SETFOCUS) && Utility::CompareWindowClass(reinterpret_cast<HWND>(lastSentMsg.lParam), szListBoxClass)) || ((HIWORD(nCode) == WM_NOTIFY) && (LOWORD(nCode) == LOWORD(NM_SETFOCUS)))) { OnControlGotFocus(nID); } return(__super::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)); }
Здесь я долго пытался понять, почему иногда не работает, и в результате боролся с двумя проблемами:
-
#define CBN_KILLFOCUS 4 #define LBN_SETFOCUS 4
В итоге пришлось добавить еще код проверки класса окна.
-
#define NM_FIRST (0U- 0U) // generic to all controls #define NM_SETFOCUS (NM_FIRST-7) #define NM_KILLFOCUS (NM_FIRST-8)
MFC пакует два значения в одно с помощью
MAKELONG
, в результате чего отрицательные значенияNM_***
после извлечения теряют старшее слово. Нужно при сравнении обрезать оба операнда.
Приведу весь код для потомков:
bool Utility::CompareWindowClass(HWND hwnd, LPCTSTR pszWindowClass) { const int cch = _tcslen(pszWindowClass) + 1; LPTSTR pszClassName = reinterpret_cast<LPTSTR>(_alloca(sizeof(TCHAR) * cch)); if (GetClassName(hwnd, pszClassName, cch)) { if (_tcsncicmp(pszClassName, pszWindowClass, -1) == 0) return(true); } return(false); } void CJobProfileDlg::OnControlGotFocus(UINT nID) { CWnd *pStatic = GetDlgItem(nID)->GetNextWindow(GW_HWNDPREV); if (pStatic && Utility::CompareWindowClass(pStatic->m_hWnd, _T("STATIC"))) pStatic->SetFont(&m_fntStaticActive); } void CJobProfileDlg::OnControlLostFocus(UINT nID) { CWnd *pStatic = GetDlgItem(nID)->GetNextWindow(GW_HWNDPREV); if (pStatic && Utility::CompareWindowClass(pStatic->m_hWnd, _T("STATIC"))) pStatic->SetFont(&m_fntStatic); }
Предполагается, что порядок обхода задан таким образом, что заголовок элемента управления предшествует ему.
0 коммент.:
Отправить комментарий