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++ в Visual C++. Технический примечание 20 содержит сведения об именовании идентификаторов.

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

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

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

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

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

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

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

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

  • В макросе ON_COMMAND.

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

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

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

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

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

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

Вы можете поместить обычную кнопку в обычном модальном диалоговом окне с идентификатором удостоверения кнопки, заданной соответствующим идентификатором команды. Когда пользователь выбирает кнопку, владелец диалогового окна (обычно главное окно кадра) получает команду так же, как и любая другая команда. Это называется командой 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 члена m_pSubMenu объекта будет не null и будет указывать на всплывающее меню.

Во-вторых, он вызывается непосредственно перед отображением элементов меню во всплывающем меню. В этом случае идентификатор относится только к первому элементу меню, а переменная CCmdUI члена m_pSubMenu объекта будет иметь значение 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_ACTIVATE (и WM_MDIACTIVATE для MDI) превратились в вызовы виртуальной функции CView::OnActivateView.

Проблемы CMDIFrameWnd/CMDIChildWnd

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

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

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

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

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

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

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

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

См. также

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