Morshins'ka
У меня в машине в дверном отсеке лежит 2 бутылки Моршинской. Одна зеленая 0,5 литра и другая белая 0,75 литра (бутылочка с соской). Я заметил, что вода в зеленой бутылке замерзла, а в белой не замерзла при -7. Больше белую не пью!
У меня в машине в дверном отсеке лежит 2 бутылки Моршинской. Одна зеленая 0,5 литра и другая белая 0,75 литра (бутылочка с соской). Я заметил, что вода в зеленой бутылке замерзла, а в белой не замерзла при -7. Больше белую не пью!
Очередная версия BITS Manager. Теперь с возможностью посмотреть список файлов задачи. Добавил Caption Bar, в котором сейчас отображается версия службы Background Intelligent Transfer Service.
Download BITS Manager v1.02.0001
Next version: BITS Manager v1.02.0002.
Очередная версия BITS Manager. Теперь с возможностью посмотреть профиль задачи, изменять некоторые параметры и характеристики задачи, закреплять задачи
. Также визуальное барахло - иконки, тултипы. Список файлов задачи пока что не отображается. Заблокированы некоторые элементы управления в окене Job profile. To be continued, короче. Забыл, добавлена обработка ошибок!
Download BITS Manager v1.02.0000
Next version: BITS Manager v1.02.0001.
Как многим, наверное, известно, при получении фокуса элемент управления EDIT
выделяет весь текст и выполняет скроллиорвание таким образом, что на экране обязательно виден конец выделения. Если почитать внимательно документацию по сообщению EM_SETSEL
, можно сделать вывод, что это происходит по следующей причине: The control displays a flashing caret at the end position regardless of the relative values of start and end
. А что делать, если я хочу показать начало строки, а не ее конец? И зачем вообще по умолчанию показывать хвост длинной строки? Тем более, что убить это чертово умолчание
не так то просто...
Гугл, как обычно, ничего полезного в качестве решения не предложил. Везде написано, что это технически невозможно и т.д. А я для себя решение нашел.
// Set caret to the end, clear the selection Edit_SetSel(pFocusedWnd->m_hWnd, INT_MAX, INT_MAX); // Send Shift+Home or just Home if Shift is at pressed state INPUT inputs[4] = { 0 }; BOOST_FOREACH(INPUT &input, inputs) { input.type = INPUT_KEYBOARD; } // {SHIFT inputs[0].ki.wVk = VK_SHIFT; // {HOME inputs[1].ki.wVk = VK_HOME; // HOME} inputs[2].ki.wVk = VK_HOME; inputs[2].ki.dwFlags = KEYEVENTF_KEYUP; // SHIFT} inputs[3].ki.wVk = VK_SHIFT; inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; bool shiftPressed = (GetKeyState(VK_SHIFT) < 0); SendInput(_countof(inputs) - int(shiftPressed) * 2, &inputs[int(shiftPressed)], sizeof(INPUT));
Этот код нужно выполнить сразу после получения элементом управления фокуса. Логика простая - переместить каретку в конец строки и выделить ее нажатием комбинации клавиш Shift+Home
. Только так можно выделить весь текст и поставить каретку в его начало! Здесь есть маленький нюанс - перемещение фокуса может осуществляться в обратном порядке и при этом клавиша Shift
находится в нажатом состоянии. Поэтому нужно делать проверку и не нажимать ее программно.
В рамках моего мини-проекта
BITS Manager решил сделать удобный и красивый диалог. Основная изюминка - заголовки элементов управления, которые меняют свой внешний вид в зависимости от движения фокуса. Если конкретнее - меняется шрифт у заголовка активного элемента:
В процессе воникла проблема - как перехватить все события от фокуса дочерних элементов управления в одном месте? Добавлять по обработчику на каждый контрол - не очень хорошая идея. Тем более, что код, модифицирующий внешний вид заголовков, вполне обобщенный.
Я увидел следующие решения данной проблемы:
WM_SETFOCUS
и WM_KILLFOCUS
.
Проверять принадлежность целевого окна интересующему диалогу и, соответственно, обрабатывать нужным образом.
Ловушка WH_CBT
с кодом HCBT_SETFOCUS
позволяет отловить момент, когда система собирается установить фокус на окно. Это хорошо, но пришлось бы сохранять и обновлять идентификатор окна, которое будет терять фокус. Можно использовать и другие ловушки для перехвата сообщений WM_SETFOCUS
и WM_KILLFOCUS
.
***_SETFOCUS
и ***_KILLFOCUS
.Я остановился на 3-м варианте, первые два мне показались черезчур сложными для такой задачи.
Пришлось слегка
почитать документацию и покопаться в коде 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); }
Предполагается, что порядок обхода задан таким образом, что заголовок элемента управления предшествует ему.