Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

Morshins'ka

У меня в машине в дверном отсеке лежит 2 бутылки Моршинской. Одна зеленая 0,5 литра и другая белая 0,75 литра (бутылочка с соской). Я заметил, что вода в зеленой бутылке замерзла, а в белой не замерзла при -7. Больше белую не пью!

Microsoft GraveYard

Наткнулся на забавный мувик...

BITS Manager v1.02.0001

Очередная версия BITS Manager. Теперь с возможностью посмотреть список файлов задачи. Добавил Caption Bar, в котором сейчас отображается версия службы Background Intelligent Transfer Service.

BITS Manager v1.02.0001

Download BITS Manager v1.02.0001

Next version: BITS Manager v1.02.0002.

BITS Manager v1.02.0000

Очередная версия BITS Manager. Теперь с возможностью посмотреть профиль задачи, изменять некоторые параметры и характеристики задачи, закреплять задачи. Также визуальное барахло - иконки, тултипы. Список файлов задачи пока что не отображается. Заблокированы некоторые элементы управления в окене Job profile. To be continued, короче. Забыл, добавлена обработка ошибок!

BITS Manager v1.02.0000

Download BITS Manager v1.02.0000

Next version: BITS Manager v1.02.0001.

Set caret to the beginning of EDIT control when focused

Как многим, наверное, известно, при получении фокуса элемент управления 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 находится в нажатом состоянии. Поэтому нужно делать проверку и не нажимать ее программно.

Intercepting dialog focus events

В рамках моего мини-проекта BITS Manager решил сделать удобный и красивый диалог. Основная изюминка - заголовки элементов управления, которые меняют свой внешний вид в зависимости от движения фокуса. Если конкретнее - меняется шрифт у заголовка активного элемента:
MFC dialog example

В процессе воникла проблема - как перехватить все события от фокуса дочерних элементов управления в одном месте? Добавлять по обработчику на каждый контрол - не очень хорошая идея. Тем более, что код, модифицирующий внешний вид заголовков, вполне обобщенный.

Я увидел следующие решения данной проблемы:

  1. В глобальном фильтре сообщений приложения ловить WM_SETFOCUS и WM_KILLFOCUS.

    Проверять принадлежность целевого окна интересующему диалогу и, соответственно, обрабатывать нужным образом.

  2. Использовать перехват сообщений - Win32 Hooks.

    Ловушка WH_CBT с кодом HCBT_SETFOCUS позволяет отловить момент, когда система собирается установить фокус на окно. Это хорошо, но пришлось бы сохранять и обновлять идентификатор окна, которое будет терять фокус. Можно использовать и другие ловушки для перехвата сообщений WM_SETFOCUS и WM_KILLFOCUS.

  3. Воспользоваться тем, что все известные 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));
}

Здесь я долго пытался понять, почему иногда не работает, и в результате боролся с двумя проблемами:

  1. #define CBN_KILLFOCUS       4
    #define LBN_SETFOCUS        4
    

    В итоге пришлось добавить еще код проверки класса окна.

  2. #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);
}

Предполагается, что порядок обхода задан таким образом, что заголовок элемента управления предшествует ему.

Copyright 2007-2011 Chabster