Поделиться через


TN021: маршрутизация команд и сообщений

Замечание

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

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

Дополнительные сведения об архитектуре, описанной здесь, см. в Visual C++, особенно различия между сообщениями Windows, уведомлениями элементов управления и командами. В этой заметке предполагается, что вы очень знакомы с проблемами, описанными в печатной документации, и рассматриваются только самые сложные темы.

Функции маршрутизации команд и диспетчеризации MFC 1.0 развиваются до архитектуры MFC 2.0

В Windows есть сообщение WM_COMMAND, используемое для уведомлений о командах меню, клавишах быстрого доступа и уведомлениях диалоговых элементов управления.

MFC 1.0 строился на этом, позволяя обработчику команд (например, OnFileNew) в производном CWnd классе вызываться в ответ на определенные WM_COMMAND. Это объединяется с использованием структуры данных, называемой картой сообщений, и образует очень пространственно-эффективный командный механизм.

MFC 1.0 также предоставляет дополнительные функции для разделения уведомлений элементов управления от сообщений команд. Команды представлены 16-разрядным идентификатором, иногда называемым идентификатором команды. Команды обычно начинаются от CFrameWnd (то есть, выбор в меню или переведенный акселератор) и перенаправляются в различные другие окна.

MFC 1.0 использовал маршрутизацию команд в ограниченном смысле для реализации нескольких интерфейсов документов (MDI). Окно рамки MDI делегирует команды своему активному дочернему окну MDI.

Эта функция была обобщена и расширена в MFC 2.0, чтобы разрешить обработку команд более широким диапазоном объектов (а не только объектами окна). Она предоставляет более формальную и расширяемую архитектуру для маршрутизации сообщений и повторно использует целевую маршрутизацию команд для не только обработки команд, но и для обновления объектов пользовательского интерфейса (таких как элементы меню и кнопки панели инструментов), чтобы отразить текущую доступность команды.

Идентификаторы команд

Описание процесса маршрутизации и привязки команд см. в Visual C++. Техническое примечание 20 содержит сведения об именовании идентификаторов.

Для идентификаторов команд используется универсальный префикс "ID_". Идентификаторы команд > = 0x8000. Строка сообщения или строка состояния отобразит строку описания команды, если существует ресурс STRINGTABLE с теми же идентификаторами, что и идентификатор команды.

В ресурсах приложения идентификатор команды может отображаться в нескольких местах:

  • В одном ресурсе STRINGTABLE, который имеет тот же идентификатор, что и запрос строки сообщения.

  • В возможно многих ресурсах MENU, присоединенных к элементам меню, которые вызывают ту же команду.

  • (ADVANCED) в диалоговой кнопке для команды GOSUB.

В исходном коде приложения идентификатор команды может отображаться в нескольких местах:

  • В файле RESOURCE.H (или в другом основном заголовочном файле символов) определите идентификаторы команд, специфичные для приложения.

  • ВОЗМОЖНО, в массиве идентификаторов, используемом для создания панели инструментов.

  • В макросе ON_COMMAND.

  • ВОЗМОЖНО, в макросе ON_UPDATE_COMMAND_UI.

В настоящее время единственная реализация в MFC, требующая, чтобы идентификаторы команд были >= 0x8000, это реализация диалогов/команд GOSUB.

Команды GOSUB, использование архитектуры команд в диалоговых окнах

Архитектура маршрутизации и включения команд хорошо работает с окнами фреймов, элементами меню, кнопками панели инструментов, кнопками панели диалогов, другими панелями управления и другими элементами пользовательского интерфейса, предназначенными для обновления команд по требованию и маршрутизации команд или идентификаторов элементов управления до основного целевого объекта команды (обычно основного окна фрейма). Основной целевой объект команды может направлять уведомления команды или управления в другие целевые объекты команд соответствующим образом.

Диалоговое окно (модальное или немодальное) может использовать некоторые возможности архитектуры команд, если назначить идентификатор управления диалогового окна соответствующему идентификатору команды. Поддержка диалогов не является автоматической, поэтому может потребоваться написать дополнительный код.

Обратите внимание, что для правильной работы всех этих функций идентификаторы команд должны быть >= 0x8000. Так как многие диалоги могут направляться в один кадр, общие команды должны иметь идентификатор >= 0x8000, а идентификатор без общего доступа в определенном диалоговом окне должен быть <= 0x7FFF.

Вы можете поместить обычную кнопку в модальное диалоговое окно с IDC кнопки, настроенным на соответствующий идентификатор команды. Когда пользователь выбирает кнопку, владелец диалогового окна (обычно главное окно кадра) получает команду так же, как и любая другая команда. Это называется командой GOSUB, так как обычно используется для создания другого диалогового окна (GOSUB первого диалога).

Вы также можете вызвать функцию CWnd::UpdateDialogControls в вашем диалоговом окне и передать ей адрес главного окна приложения. Эта функция включает или отключает элементы управления диалоговым окном в зависимости от того, есть ли в кадре обработчики команд. Эта функция вызывается автоматически для панели управления в цикле простоя приложения, но ее необходимо вызвать непосредственно для обычных диалогов, которые вы хотите использовать для этой функции.

При вызове функции ON_UPDATE_COMMAND_UI

Поддержание состояния включенности или проверки всех элементов меню программы постоянно может быть вычислительно дорогой проблемой. Распространенный способ заключается в включении и проверке элементов меню только в том случае, если пользователь выбирает POPUP. Реализация CFrameWnd в MFC 2.0 обрабатывает сообщение WM_INITMENUPOPUP и использует архитектуру командной маршрутизации для определения состояний меню через обработчики ON_UPDATE_COMMAND_UI.

CFrameWnd также обрабатывает сообщение WM_ENTERIDLE для описания текущего элемента меню, выбранного в строке состояния (также известной как строка сообщения).

Структура меню в приложении, редактируемая с помощью Visual C++, используется для представления возможных команд, доступных в момент WM_INITMENUPOPUP. ON_UPDATE_COMMAND_UI обработчики могут изменять состояние или текст меню, а также использоваться для более сложных задач (например, список MRU файлов или всплывающее меню OLE Verbs), фактически изменяя структуру меню перед его отображением.

То же самое ON_UPDATE_COMMAND_UI обработка выполняется для панелей инструментов (и других панелей управления), когда приложение входит в цикл простоя. Дополнительные сведения о панели управления см. в справочнике по библиотеке классов и техническом примечание 31 .

Вложенные всплывающие меню

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

Во-первых, он вызывается для самого всплывающего меню. Это необходимо, так как всплывающие меню не имеют идентификаторов, и мы используем идентификатор первого пункта меню всплывающего меню для ссылки на все всплывающее меню. В этом случае переменная CCmdUI объекта будет не null и будет указывать на всплывающее меню.

Во-вторых, он вызывается непосредственно перед отображением элементов меню во всплывающем меню. В этом случае идентификатор относится только к первому элементу меню, а переменная элемента m_pSubMenu объекта CCmdUI будет иметь значение NULL.

Это позволяет включить всплывающее меню, отдельное от его пунктов, но требует написания кода с учетом особенностей меню. Например, в вложенном меню со следующей структурой:

File>
    New>
    Sheet (ID_NEW_SHEET)
    Chart (ID_NEW_CHART)

Команды ID_NEW_SHEET и ID_NEW_CHART могут быть включены или отключены независимо. Новое всплывающее меню должно быть включено, если один из этих двух включен.

Обработчик команд для ID_NEW_SHEET (первая команда во всплывающем меню) будет выглядеть примерно так:

void CMyApp::OnUpdateNewSheet(CCmdUI* pCmdUI)
{
    if (pCmdUI->m_pSubMenu != NULL)
    {
        // enable entire pop-up for "New" sheet and chart
        BOOL bEnable = m_bCanCreateSheet || m_bCanCreateChart;
        // CCmdUI::Enable is a no-op for this case, so we
        // must do what it would have done.
        pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
            MF_BYPOSITION |
            (bEnable  MF_ENABLED : (MF_DISABLED | MF_GRAYED)));

        return;
    }
    // otherwise just the New Sheet command
    pCmdUI->Enable(m_bCanCreateSheet);
}

Обработчик команд для ID_NEW_CHART будет обычным обработчиком команд обновления и выглядит примерно так:

void CMyApp::OnUpdateNewChart(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bCanCreateChart);
}

ON_COMMAND и ON_BN_CLICKED

Макросы карты сообщений для ON_COMMAND и ON_BN_CLICKED одинаковы. Механизм маршрутизации уведомлений и команд MFC использует только идентификатор команды, чтобы решить, куда направляться. Уведомления управления с кодом уведомления, равным нулю (BN_CLICKED), интерпретируются как команды.

Замечание

На самом деле все сообщения уведомлений управления проходят через цепочку обработчиков команд. Например, технически возможно написать обработчик уведомлений элемента управления для EN_CHANGE в вашем классе документа. Как правило, это не рекомендуется, так как практические приложения этой функции являются немногими, функция не поддерживается ClassWizard, и использование функции может привести к хрупким коду.

Отключение автоматического отключения элементов управления кнопками

Если вы размещаете элемент управления кнопкой на панели диалоговых окон или в диалоговом окне, где вы самостоятельно вызываете CWnd::UpdateDialogControls, вы заметите, что кнопки без обработчиков ON_COMMAND или ON_UPDATE_COMMAND_UI автоматически отключаются фреймворком. В некоторых случаях не требуется обработчик, но вы хотите, чтобы кнопка оставалась включенной. Самый простой способ достичь этого заключается в добавлении фиктивного обработчика команд (легко сделать с ClassWizard) и ничего не делать в нем.

Маршрутизация сообщений окна

Ниже описаны более сложные разделы о классах MFC и о том, как маршрутизация сообщений Windows и другие разделы влияют на них. Сведения здесь описаны только кратко. Дополнительные сведения об общедоступных API см. в справочнике по библиотеке классов . Дополнительные сведения о реализации см. в исходном коде библиотеки MFC.

Дополнительные сведения о очистке окна см. в Техническом примечание 17 . Это очень важный раздел для всех производных от CWnd классов.

Проблемы CWnd

Функция-член реализации CWnd::OnChildNotify предоставляет мощную и расширяемую архитектуру для дочерних окон (также известных как элементы управления) для перехвата или получения сведений о сообщениях, командах и уведомлениях элемента управления, которые отправляются их родителю (или «владельцу»). Если дочернее окно (/control) представляет собой объект C++ CWnd , виртуальная функция OnChildNotify вызывается сначала с параметрами исходного сообщения (т. е. структурой MSG ). Дочернее окно может оставить сообщение без изменений, обработать его или изменить сообщение для родительского окна (редко).

Реализация CWnd по умолчанию обрабатывает следующие сообщения и использует перехватчик OnChildNotify , чтобы разрешить дочерним окнам (элементам управления) сначала получить доступ к сообщению:

  • WM_MEASUREITEM и WM_DRAWITEM (для самостоятельного рисования)

  • WM_COMPAREITEM и WM_DELETEITEM (для самостоятельного рисования)

  • WM_HSCROLL и WM_VSCROLL

  • WM_CTLCOLOR

  • WM_PARENTNOTIFY

Вы заметите, что перехватчик OnChildNotify используется для изменения сообщений о рисовании владельцем в сообщения о саморисовании.

Помимо перехватчика OnChildNotify, сообщения прокрутки имеют дальнейшее поведение маршрутизации. Подробности о полосах прокрутки и источниках сообщений WM_HSCROLL и WM_VSCROLL см. ниже.

Проблемы CFrameWnd

Класс CFrameWnd предоставляет большую часть реализации маршрутизации команд и обновления пользовательского интерфейса. Это преимущественно используется для основного окна фрейма приложения (CWinApp::m_pMainWnd), но применяется ко всем окнам фреймов.

Главное окно фрейма — это окно с строкой меню и является родительским элементом строки состояния или строки сообщения. Обратитесь к обсуждению выше о маршрутизации команд и WM_INITMENUPOPUP.

Класс CFrameWnd предоставляет управление активным представлением. Следующие сообщения направляются через активное представление:

  • Все сообщения команд (активный вид получает первый доступ к ним).

  • WM_HSCROLL и WM_VSCROLL сообщения от смежных полос прокрутки (см. ниже).

  • WM_ACTIVATEWM_MDIACTIVATE для MDI) превратились в вызовы виртуальной функции CView::OnActivateView.

Проблемы CMDIFrameWnd/CMDIChildWnd

Оба класса окна MDI являются производными от CFrameWnd и поэтому включены для одинаковой маршрутизации команд и обновления пользовательского интерфейса, предоставленного в CFrameWnd. В обычном приложении MDI только основное окно фрейма (т. е. объект CMDIFrameWnd ) содержит строку меню и строку состояния, поэтому является основным источником реализации маршрутизации команд.

Общая схема маршрутизации заключается в том, что активное дочернее окно MDI получает первый доступ к командам. Функции PreTranslateMessage по умолчанию обрабатывают таблицы акселераторов для дочерних окон MDI (первый) и кадра MDI (второй), а также стандартные акселераторы системных команд MDI, которые обычно обрабатываются TranslateMDISysAccel (последний).

Проблемы полосы прокрутки

При обработке сообщения прокрутки (WM_HSCROLL/OnHScroll и/или WM_VSCROLL/OnVScroll), следует попытаться написать код обработчика, чтобы он не зависел от того, откуда поступило сообщение полосы прокрутки. Это не только общая проблема с Windows, так как сообщения прокрутки могут поступать из истинных элементов управления полос прокрутки или из WS_HSCROLL/WS_VSCROLL полос прокрутки, которые не являются элементами управления полосой прокрутки.

MFC расширяет возможности, что позволяет элементам управления полосой прокрутки быть дочерними или соседними элементами окна, содержащего прокрутку (на самом деле связь между полосой прокрутки и окном может иметь любой характер). Это особенно важно для общих полос прокрутки с окнами разделения. Дополнительные сведения о реализации CSplitterWnd см. в Техническом примечание 29, включая дополнительные сведения о проблемах с общей полосой прокрутки.

Кстати, есть два производных класса CWnd, в которых стили полосы прокрутки, заданные при создании, блокируются и не передаются в Windows. При передаче в подпрограмму создания WS_HSCROLL и WS_VSCROLL можно задать независимо, но после создания нельзя их изменить. Конечно, вы не должны напрямую тестировать или задавать биты стиля WS_SCROLL созданного окна.

Для CMDIFrameWnd стили полосы прокрутки, которые вы передаете в Create или LoadFrame, используются для создания MDICLIENT. Если вы хотите иметь прокручиваемую область MDICLIENT (как диспетчер программ Windows), обязательно установите стили как горизонтальной, так и вертикальной полосы прокрутки (WS_HSCROLL | WS_VSCROLL) для стиля, используемого для создания CMDIFrameWnd.

Для CSplitterWnd стили полосы прокрутки применяются к специальным общим полосам прокрутки для области разделителя. Обычно для окон статических разделителей не устанавливают стиль полос прокрутки. Для динамических окон разделения обычно будет задан стиль полосы прокрутки для направления, которое будет разделено, то есть WS_HSCROLL , если можно разделить строки, WS_VSCROLL , если можно разделить столбцы.

См. также

Технические примечания по номеру
Технические заметки по категориям