Чтение онлайн

ЖАНРЫ

Программирование на Visual C++. Архив рассылки

Jenter Алекс

Шрифт:

После того, как карта UI добавлена в класс, остаётся один завершающий штрих. Вы должны зарегистрировать все контейнеры элементов пользовательского интерфейса, которые нужно обновлять. В случае с контролами в качестве контейнера выступает сам диалог. В случае с меню это окно, содержащее меню. И так далее. Для каждого контейнера существует своя функция регистрации: UIAddMenuBar для меню, UIAddToolBar для панелей иснтрументов, UIAddStatusBar для строк состояния и UIAddChildWindowContainer для контейнеров дочерних окон. Все перечисленные функции принимают хэндл окна-контейнера и позвращают BOOL, сигнализирующий об успехе или неуспехе регистрации. В случае с диалогом регистрировать контейнер удобно в обработчике сообщения WM_INITDIALOG.

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

Функция Описание
BOOL UIEnable(int nID, BOOL bEnable, BOOL bForceUpdate = FALSE) Изменяет
состояние доступности элементов с идентификатором nID в соответствии со значением bEnable. Флаг принудительного обновления bForceUpdate задаётся, когда нужно фактически обновить элемент, даже если его текущее состояние соответствует желаемому и менять ничего не надо. Поскольку все функции используют этот флаг одинаково, я больше не буду на нём останавливаться.
BOOL UISetCheck(int nID, int nCheck, BOOL bForceUpdate = FALSE) Изменяет состояние флага "checked" элементов с идентификатором nID в соответствии со значением nCheck. Из контролов эту функцию имеет смысл применять только к кнопкам, так как другие контролы не имеют такого флага. nCheck может принимать одно из трёх значений: 0, если кнопка "отжата", 1, если нажата и 2, если она находится в "третьем состоянии" (кнопка активна, но отрисовывается серым цветом). Третье состояние имеется только у конпок со стилями BS_3STATE или BS_AUTO3STATE.
BOOL UISetRadio(int nID, BOOL bRadio, BOOL bForceUpdate = FALSE) Изменяет состояние флага "radio" элементов с идентификатором nID в соответствии со значением nRadio. Для контролов эта функция работает аналогично предыдущей.
BOOL UISetText(int nID, LPCTSTR lpstrText, BOOL bForceUpdate = FALSE) Изменяет текст элементов с идентификатором nID на заданный в параметре lpstrText.
BOOL UISetState(int nID, DWORD dwState) Эта функция позволяет изменить сразу несколько флагов, связанных с элементами nID. Эти флаги объединяются операцией "ИЛИ" и передаются в качестве параметра dwState. Можно использовать флаги UPDUI_DISABLED, UPDUI_CHECKED, UPDUI_CHECKED2 (этот флаг соответствует "третьему состоянию" кнопки), UPDUI_RADIO и UPDUI_DEFAULT. Замечу, что флаг UPDUI_DEFAULT можно менять, только используя функцию UISetState. Специальной функции для его изменения нет. Этот флаг позволяет сделать элемент используемым по умолчанию. Обратите внимание, что функция UISetState не использует флаг bForceUpdate и всегда обновляет элемент, вне зависимости от его текущего состояния.
DWORD UIGetState(int nID) Функция, обратная предыдущей. Возвращает набор флагов, характеризующих состояние элемента.

Функции, которые мы только что рассмотрели, не изменяют фактическое состояние элементов. Они только записывают новые значения во внутренние структуры класса CUpdateUI<>. Чтобы внесённые изменения вступили в силу, нужно вызвать специальную функцию. Для каждого типа контейнеров существует своя функция: UIUpdateMenuBar для меню, UIUpdateToolBar для панели инструментов, UIUpdateStatusBar для строки состояния и UIUpdateChildWindows для контейнера дочерних окон. Каждая из этих функций принимает флаг bForceUpdate. Используйте его, чтобы принудительно обновить все элементы, прописанные в карте UI.

Как это всё работает

Посмотрим, как устроен класс CUpdateUI<>. Карта UI, которую вы создаёте, превращается в массив структур _AtlUpdateUIMap.

struct _AtlUpdateUIMap {

 WORD m_nID;

 WORD m_wType;

};

Каждая структура содержит в точности те значения, которые вы передаёте макросу UPDATE_ELEMENT в качестве параметров. Массив завершается структурой со значениями {(WORD)-1, 0}. Для обращения к нему используется функция GetUpdateUIMap, внутри которой он описывается как статическая переменная. Этот массив один на все объекты класса, порождённого от CUpdateUI<>. Кроме этого, каждый объект класса наследует от CUpdateUI<> переменные m_UIElements, m_pUIData и m_wDirtyType.

m_UIElements — это массив контейнеров,

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

m_pUIData — массив структур _AtlUpdateData. Количество элементов в этом массиве в точности соответствует количеству записей в карте UI. Каждая структура _AtlUpdateData содержит флаги состояния (те самые, которые меняет функция UISetState) и указатель на строку, которые должны быть назначены элементу. Место для строк распределяется динамически. Вот как описана структура _AtlUpdateUIData.

struct _AtlUpdateUIData {

 WORD m_wState;

 void* m_lpData;

};

Теперь понятно, что делают функции типа UIEnable и UISetCheck. Они просто изменяют поля структуры _AtlUpdateUIData, соответствующей заданному элементу. Что касается семейства функций UIUpdateXXX, то они используют данные из m_pUIData, чтобы обновить элементы управления.

Наконец, переменная m_wDirtyType используется в целях оптимизации. В ней содержатся типы тех элементов, состояние которых было изменено с момента последнего обновления. Когда вы вызываете функцию UIUpdateXXX, WTL проверяет соответствующий флаг в m_wDirtyType и обновляет элементы, только если он установлен. После обновления m_wDirtyType сбрасывается в ноль.

Где обновлять элементы

Механизм обновления элементов пользовательского интерфейса, реализованный в WTL, не навязывает вам определённой стратегии обновления, а просто избавляет вас от рутинной работы. Вы можете обновлять элементы всякий раз, когда пользователь делает какое-то действие. В этом случае по всей программе будут разбросаны "пачки" вызовов функций обновления UIEnable, UISetText и т. д. Но совершенно очевидно, что такой подход раздувает и запутывает ваш код. Гораздо лучше написать одну функцию, которая обновляет все элементы в зависимости от текущего состояния программы. Потом к этой функции можно обращаться всякий раз, когда состояние элементов может измениться.

Альтернативный вариант, который, кстати, используется в MFC, заключается в обновлении элементов в фоне, то есть когда очередь сообщений пуста. Если вы используете немодальный диалог, вам будет нетрудно реализовать эту идею и в WTL: для этого достаточно зарегистрировать объект диалога в цикле сообщений как фоновый обработчик, а затем обновлять элементы в функции OnIdle. Однако если диалог модальный, цикл сообщений скрыт внутри функции DialogBoxParam и фоновая обработка в стиле wtl недоступна. В этом случае можно использовать сообщение WM_ENTERIDLE (модальный диалог посылает его родительскому окну, когда очередь сообщений исчерпана) или вообще отказаться от фоновой обработки.

[ ПРОДОЛЖЕНИЕ СЛЕДУЕТ ]

Это все на сегодня. Пока! 

Алекс Jenter jenter@rsdn.ru Duisburg, 2001. Публикуемые в рассылке материалы принадлежат сайту RSDN. 

Программирование на Visual C++

Выпуск №55 от 18 ноября 2001 г.

Добрый день!

Сегодня в выпуске – продолжение второй части статьи "Использование WTL".

Если вы еще не читали первую часть, ее можно найти на RSDN.

СТАТЬЯ 

Использование WTL

Часть 2. Диалоги и контролы (продолжение)

Автор: Александр Шаргин

Класс CDialogResize<>: масштабирование диалогов в стиле WTL

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

Обычно эта проблема решается так. Диалогу назначается стиль WS_THICKFRAME (Border: resizing в редакторе ресурсов). Затем в программе перехватывается сообщение WM_SIZE, сигнализирующее об изменении размеров диалога. В ответ на него программа соответствующим образом изменяет размеры контролов в диалоге. Этот подход универсален и достаточно прост в реализации, но требует написания большого количества кода, связанного с пересчётом координат. Поэтому в WTL введён класс, который в ряде случаев избавит вас от рутинной работы по масштабированию контролов. Этот класс называется CDialogResize<>. Он описан в файле atlframe.h. Хотя этот класс не является универсальным, он подойдёт в большинстве случаев. Замечу, что его можно применять с любым окном, содержащим дочерние окна, но чаще всего он применяется именно с диалогами.

Поделиться с друзьями: