|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Программирование на Visual C++Выпуск №55 от 18 ноября 2001 г. Добрый день! Сегодня в выпуске – продолжение второй части статьи "Использование WTL". Если вы еще не читали первую часть, ее можно найти на RSDN. СТАТЬЯИспользование WTL Часть 2. Диалоги и контролы (продолжение) Класс CDialogResize<>: масштабирование диалогов в стиле WTL Как известно, обычные диалоги не позволяют себя масштабировать. С точки зрения пользователя это довольно неудобно. Часть информации не помещается в маленьких контролах, и их приходится прокручивать, чтобы просмотреть всё целиком. В то же время часть экрана монитора всё равно остаётся незанятой, и диалог вполне мог бы её занять. Возникает вопрос: как реализовать масштабируемые диалоги в вашем приложении? Обычно эта проблема решается так. Диалогу назначается стиль WS_THICKFRAME (Border: resizing в редакторе ресурсов). Затем в программе перехватывается сообщение WM_SIZE, сигнализирующее об изменении размеров диалога. В ответ на него программа соответствующим образом изменяет размеры контролов в диалоге. Этот подход универсален и достаточно прост в реализации, но требует написания большого количества кода, связанного с пересчётом координат. Поэтому в WTL введён класс, который в ряде случаев избавит вас от рутинной работы по масштабированию контролов. Этот класс называется CDialogResize<>. Он описан в файле atlframe.h. Хотя этот класс не является универсальным, он подойдёт в большинстве случаев. Замечу, что его можно применять с любым окном, содержащим дочерние окна, но чаще всего он применяется именно с диалогами. Итак, чтобы воспользоваться поддержкой масштабирования, которую предоставляет WTL, нужно включить в число базовых классов вашего диалога класс CDialogResize<>, задав в качестве параметра шаблона имя порождаемого класса. После этого вам, как обычно, потребуется написать карту – на этот раз карту масштабирования. Макросы, из которых она формируется, приведены в таблице 4.
Кроме написания карты масштабирования, необходимо выполнить ещё два действия. Во-первых, класс CDialogResize<> имеет свою собственную карту сообщений. В частности, она содержит обработчик сообщения WM_SIZE, который инициирует перемасштабирование контролов при каждом изменении размеров диалога. Эту карту сообщений следует подключить к карте сообщений вашего диалога, используя макрос CHAIN_MSG_MAP: BEGIN_MSG_MAP(CMyDialog) ... CHAIN_MSG_MAP(CDialogResize<CMyDialog>) ... END_MSG_MAP() Во вторых, после того, как ваш дилог создан, необходимо инициализировать внутренние структуры WTL, связанные с масштабированием. Это делается при помощи функции DlgResize_Init. Удобно вызывать её из обработчика сообщения WM_INITDIALOG. Функция DlgResize_Initимеет следующий прототип: void DlgResize_Init(bool bAddGripper = true, bool bUseMinTrackSize = true, DWORD dwForceStyle = WS_THICKFRAME | WS_CLIPCHILDREN) В большинстве случаев на параметры можно не обращать внимание, так как значения по умолчанию вполне удовлетворяют всем нуждам. Параметр bAddGripper указывает, нужно ли добавить к диалогу "гриппер" – маленький уголок, за который можно ухватиться курсором и изменить размеры диалога. Флаг bUseMinTrackSize определяет, нужно ли ограничивать минимальные размеры диалога. В большинстве случаев это хорошая идея, так как сильно уменьшенный дилог всё равно плохо выглядит и не удобен для работы с ним. Минимальный размер диалога хранится в переменной m_ptMinTrackSize, которую ваш класс диалога наследует от класса CDialogResize<>. По умолчанию в неё записывается первоначальный размер диалога (тот, который установлен в момент вызова функции DlgResize_Init). Вы можете записать туда любое другое значение. Что касается параметра dwForceStyle, то это просто стиль, который принудительно назначается диалогу в функции DlgResize_Init. Ещё одна функция из класса CDialogResize<>, о которой следует упомянуть, – DlgResize_UpdateLayout. Эта функция принудительно пересчитывает координаты всех контролов в зависимости от переданных ей размеров диалога (cx и cy). Именно она вызывается из обработчика сообщения WM_SIZE, но при необходимости вы можете вызывать её в любом другом месте. Как составлять карту масштабированияНа самом деле, единственная проблема с классом CDialogResize<> состоит в том, чтобы правильно составить карту масштабирования. Для этого нужно чётко понимать, как работают флаги DLSZ_XXX. Эти флаги по-разному действуют на контрол в группе или без неё. Сначала посмотрим, как флаги DLSZ_XXX действуют на контрол, не включённый в группу. Допустим, размеры диалога изменились на dx и dy соответственно. Тогда: • DLSZ_SIZE_X: ширина контрола изменяется на dx. • DLSZ_SIZE_Y: высота контрола изменяется на dy. • DLSZ_MOVE_X: контрол двигается вдоль оси x на dx . Флаг DLSZ_SIZE_X при этом игнорируется. • DLSZ_MOVE_Y: контрол двигается вдоль оси y на dy . Флаг DLSZ_SIZE_Y при этом игнорируется. Как видно из этого описания, задавать одновременно флаги DLSZ_MOVE_X и DLSZ_SIZE_X (а также DLSZ_MOVE_Y и DLSZ_SIZE_Y) бессмысленно, так как в этом случае будут учитываться только флаги DLSZ_MOVE_*. Описанная схема масштабирования довольно примитивна. Так, очевидно, что к двум расположенным рядом контролам нельзя применять флаг DLSZ_SIZE_*, так как они оба увеличат размер и "заедут" друг на друга. И всё-таки во многих случаях такого механизма оказывается достаточно. Для примера рассмотрим типичный диалог выбора файла (рисунок 2). Рисунок 2. Схема диалога открытия файла При масштабировании логично изменять размер контролов диалога следующим образом: растягивать IDC_LEFT_PANE на всю высоту диалога, растягивать IDC_COMBO по горизонтали, отодвигая IDC_TOOLBAR до предела вправо, отодвигать IDC_NAME и IDC_FILTER вниз и растягивать по горизонтали, перемещать кнопки IDOK и IDCANCEL в правый нижний угол и занимать списком файлов IDC_FILE_LIST всё оставшееся место. Чтобы воплотить в жизнь эту схему, следует записать карту масштабирования следующим образом: BEGIN_DLGRESIZE_MAP(COpenFileDialog) DLGRESIZE_CONTROL(IDC_LEFT_PANE, DLSZ_SIZE_Y) DLGRESIZE_CONTROL(IDC_COMBO, DLSZ_SIZE_X) DLGRESIZE_CONTROL(IDC_TOOLBAR, DLSZ_MOVE_X) DLGRESIZE_CONTROL(IDC_FILE_LIST, DLSZ_SIZE_X | DLSZ_SIZE_Y) DLGRESIZE_CONTROL(IDC_NAME, DLSZ_MOVE_Y | DLSZ_SIZE_X) DLGRESIZE_CONTROL(IDC_FILTER, DLSZ_MOVE_Y | DLSZ_SIZE_X) DLGRESIZE_CONTROL(IDOK, DLSZ_MOVE_Y | DLSZ_MOVE_X) DLGRESIZE_CONTROL(IDCANCEL, DLSZ_MOVE_Y | DLSZ_MOVE_X) END_DLGRESIZE_MAP() Теперь поговорим о контролах, объединённых в группу.
Группы обрабатываются следующим образом. Сначала вычисляются координаты огибающего прямоугольника группы, то есть минимального прямоугольника, содержащего все контролы в ней. Далее размеры этого прямоугольника увеличиваются на dx и dy соответственно (dx и dy имеют то же значение, что и в обсуждении выше). После этого к каждому контролу в группе применяются следующие правила: • DLSZ_MOVE_X: контрол сдвигается вдоль оси X пропорционально изменению ширины группы (то есть её огибающего прямоугольника). • DLSZ_MOVE_Y: контрол сдвигается вдоль оси Y пропорционально изменению высоты группы. • DLSZ_SIZE_X: действует аналогично DLSZ_MOVE_X, но ширина контрола также изменяется пропорционально изменению ширины группы. • DLSZ_SIZE_Y: действует аналогично DLSZ_MOVE_Y, но высота контрола также изменяется пропорционально изменению высоты группы. Проиллюстрирую сказанное простым примером. Допустим, у нас есть диалог с тремя расположенными в ряд кнопками. Если написать для него карту масштабирования вида: BEGIN_DLGRESIZE_MAP(CMyDialog) BEGIN_DLGRESIZE_GROUP() DLGRESIZE_CONTROL(IDC_BUTTON1, DLSZ_SIZE_X) DLGRESIZE_CONTROL(IDC_BUTTON2, DLSZ_SIZE_X) DLGRESIZE_CONTROL(IDC_BUTTON3, DLSZ_SIZE_X) END_DLGRESIZE_GROUP() END_DLGRESIZE_MAP() то этот диалог будет масштабироваться следующим образом: Рисунок 3. Масштабирование с использованием групп Как это всё работает Мы не будем надолго задерживаться на внутренней реализации класса CDialogResize<>, так как там нет почти ничего интересного. Когда вы вызываете функцию DlgResize_Init, начальные положения всех контролов в диалоге запоминаются во внутренних структурах WTL. Функция DlgResize_UpdateLayout использует новые размеры диалога и сохранённые ранее координаты контролов, чтобы назначить им новое положение в соответствии с заданными флагами. Что касается карты масштабирования, она просто превращается в статический массив структур _AtlDlgResizeMap, для доступа к которому используется функция GetDlgResizeMap. Структура _AtlDlgResizeMap хранит заданные вами в карте значения: struct _AtlDlgResizeMap { int m_nCtlID; DWORD m_dwResizeFlags; }; Хочу отметить несколько особенностей реализации класса CDialogResize<>, которые можно использовать в своих целях. 1. Элемент может встречаться в карте масштабирования более одного раза. 2. Элемент, не включённый в группу, двигается относительно его текущего положения. 3. Элемент, включённый в группу, масштабируется с учётом его начального положения (но без учёта его текущей позиции). Таким образом мы можем, к примеру, отмасштабировать элемент по горизонтали, включив его в группу, а затем отдельно увеличить его высоту. Пример диалога с тремя кнопками, который мы рассмотрели выше, имел один недостаток: при увеличении высоты диалога кнопки не растягивались по вертикали. Теперь мы знаем, как решить эту проблему: BEGIN_DLGRESIZE_MAP(CMyDialog) BEGIN_DLGRESIZE_GROUP() DLGRESIZE_CONTROL(IDC_BUTTON1, DLSZ_SIZE_X) DLGRESIZE_CONTROL(IDC_BUTTON2, DLSZ_SIZE_X) DLGRESIZE_CONTROL(IDC_BUTTON3, DLSZ_SIZE_X) END_DLGRESIZE_GROUP() DLGRESIZE_CONTROL(IDC_BUTTON1, DLSZ_SIZE_Y) DLGRESIZE_CONTROL(IDC_BUTTON2, DLSZ_SIZE_Y) DLGRESIZE_CONTROL(IDC_BUTTON3, DLSZ_SIZE_Y) END_DLGRESIZE_MAP() Кроме этого, можно включить элемент в несколько групп. Хотя на его местоположение повлияет только последняя группа, этот приём позволит сложным образом влиять на другие контролы. Но не стоит забывать о чувстве меры. Такие приёмы делают вашу программу более запутанной и более медлительной. Нетривиальное масштабирование контролов в диалоге лучше реализовать вручную, а не заниматься неочевидными фокусами с CDialogResize<>. КонтролыКонтролы – ещё один важный элемент операционной системы Windows. Во времена DOS каждому программисту зачастую приходилось изобретать собственный графический интерфейс. Под Windows задача упростилась: хотя сложные нестандартные "фичи" пользовательского интерфейса по-прежнему приходится разрабатывать вручную, в вашем распоряжении всегда есть базовый набор элементов, которые можно использовать для взаимодействия с пользователем или попытаться построить на их основе более сложные контролы. Библиотека WTL предоставляет программисту классы для удобной работы со стандартными контролами, а также предоставляет средства для расширения их функциональности. Кроме того, в WTL входит несколько нестандартных контролов (кнопка с картинками, гиперссылка и др.), которые вы также можете использовать в приложениях. Рассмотрим все эти классы более подробно. Поддержка cтандартных и общих контролов WindowsМы с вами уже изучили класс CWindow, который предоставляет целый набор обёрток для функций Win32 API, предназначенных для работы с окнами. При работе с контролами этот класс также можно использовать. Но гораздо удобнее использовать специальные классы контролов, которые описаны в файле atlctrls.h. Полный список этих классов приведён в таблице 5.
Каждый из этих классов порождён от CWindow и содержит все его методы. В дополнение каждый класс предоставляет: • Метод GetWndClassName. Этот метод позволяет узнать имя класса окна, соответствующего данному контролу. • Метод Create. В отличие от аналогичного метода из класса CWindow, он не принимает имя класса, так как оно извлекается при помощи GetWndClassName. • Обёртки для стандартных сообщений, которые используются для управления контролом. Например, для статических контролов это сообщения STM_GETICON, STM_GETIMAGE, STM_SETICON и STM_SETIMAGE. Используя обёртки, вы можете не вспоминать, каким образом упаковываются в wParam и lParam параметры этих сообщений. • Обёртки для функций Win32 API, манипулирующих контролом. Такие функции существуют лишь для нескольких контролов (таких, как scroll bar). Обратите внимание, что функциональность всех классов из atlctrls.h регулируется макросами WINVER, _WIN32_IE и _RICHEDIT_VER. Например, функции, специфичные для контролов из internet Explorer 4.0 и выше, оформлены так: #if (_WIN32_IE >= 0x0400) ... #endif //(_WIN32_IE>= 0x0400) Благодаря этому классы контролов из WTL можно использовать при работе с любой версией контролов, получая при этом доступ ко всему набору возможностей используемой версии. Полное описание всех функций и классов из atlctrls.h выходит за рамки данной статьи. "Самодельные" контролыЕсли бы все программы использовали только стандартные контролы, они были бы скучными и неудобными. Поэтому разработчикам часто приходится "изобретать" свои собственные контролы. При этом можно разрабатывать новый контрол "с нуля" или взять за основу уже существующий контрол. Создавать контролы "с нуля" мы уже умеем. Для этого нужно породить новый класс от CWindowImpl<> и написать обработчики нужных сообщений. Чаще других обрабатываются сообщения WM_CREATE и WM_PAINT, а также клавиатурные и мышиные сообщения. Кроме того, нужно предусмотреть средства для взаимодействия программы с вашим контролом. Для этой цели можно ввести нестандартные сообщения, которые будет понимать ваш контрол, или предусмотреть соответствующие методы в вашем классе. Если вы решили построить свой контрол на базе существующего, вам также следует использовать класс CWindowImpl<>. Нужно только учесть два момента. Во-первых, базовым классом для вашего контрола должен быть не CWindow, а класс контрола, который вы модифицируете. Базовый класс задаётся во втором параметре шаблона CWindowImpl<> (по умолчанию этот параметр равен CWindow). А во-вторых, для обработки сообщений по умолчанию должна использоваться не функция DefWindowProc (как для обычных окон), а оконная функция соответствующего контрола. Чтобы этого добиться, следует использовать макрос DECLARE_WND_SUPERCLASS вместо DECLARE_WND_CLASS. Этот макрос объявлен так. #define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) \ static CWndClassInfo& GetWndClassInfo() \ { \ static CWndClassInfo wc = \ { \ { sizeof(WNDCLASSEX), 0, StartWindowProc, \ 0, 0, NULL, NULL, NULL, NULL, NULL, WndClassName, NULL }, \ OrigWndClassName, NULL, NULL, TRUE, 0, _T("") \ }; \ return wc; \ } Параметр WndClassName определяет имя класса вашего нового контрола. В качестве второго параметра OrigWndClassName следует указать имя класса контрола, который вы взяли за основу. При регистрации вашего класса WndClassName WTL скопирует для него параметры из класса с именем OrigWndClassName, а также сохранит адрес оконной процедуры, связанной с этим классом, в переменной CWindowImplBaseT<>::m_pfnSuperWindowProc и будет обращаться к ней для обработки сообщений, которые не были обработаны через карту сообщений. С учётом всего сказанного, типичный класс контрола выглядит так. class CMyCoolControl : public CWindowImpl<CMyCoolControl, CEdit> { public: DECLARE_WND_SUPERCLASS(NULL, CEdit::GetWndClassName()) BEGIN_MSG_MAP(CMyCoolControl) // Карта сообщений END_MSG_MAP() … }; В этом примере новый контрол создаётся на базе поля ввода (которому соответствует класс CEdit). Аналогично используется любой другой контрол.
В библиотеку WTL входит несколько "самодельных" контролов, которые реализованы в файле atlctrlx.h . Вы можете вставлять их в свои программы или использовать как демонстрационные примеры по разработке контролов. Вот список классов, которые написали для вас разработчики WTL. • CBitmapButton. Кнопка с рисунками. • CCheckListViewCtrl. Расширенный список с "галочками". • CHyperLink. Гиперссылка. • CMultiPaneStatusBarCtrlImpl. Строка состояния с набором панелей. • CWaitCursor. Курсор типа "песочные часы". Этот класс, в отличие от всех предыдущих, не имеет отношения к контролам. Поскольку никакой официальной документации на эти классы нет, я приведу их краткое описание. Кроме этого, разобраться с ними вам поможет пример WTLCtlxDemo далее в этой статье. Класс CBitmapButtonКласс CBitmapButton реализует кнопку, с каждым состоянием которой (нажата/отпущена/выключена/в фокусе) связано изображение. Кроме того, с кнопкой связывается всплывающая подсказка, поясняющая её назначение, и набор расширенных стилей (эти стили не имеют ничего общего с расширенным стилями обычного окна). Каждому стилю соответствует битовый флаг. Полный список флагов приведён в таблице 6.
Стиль кнопки, а также связанный с ней список изображений, задаются в конструкторе класса CBitmapButton, хотя можно установить/изменить их и позже, используя соответствующие методы. Для задания текста всплывающей подсказки также существуют соответствующий метод. Полный список методов класса CBitmapButton приведён в таблице 7.
Из названия может показаться, что этот класс реализует список с галочками (check boxes), но это не совсем так. Стандартный контрол ListView уже поддерживает галочки. Достаточно задать ему расширенный стиль LVS_EX_CHECKBOXES. Что касается класса CCheckListViewCtrl, то он позволяет манипулировать несколькими галочками одновременно. Для этого пользователь выделяет несколько элементов в списке (используя Shift и Ctrl, в списке можно довольно быстро пометить нужную группу элементов). После этого щелчок по галочке любого элемента (или нажатие на Space) будет приводить к изменению состояния галочек у всех выделенных элементов. При необходимости такое поведение контрола можно подавить, удерживая Ctrl (при этом список будет вести себя, как обычный ListView). Реализация класса CCheckListViewCtrl достаточно очевидна. Метод SubclassWindow подменяет оконную процедуру списка и принудительно устанавливает ему стиль LVS_EX_CHECKBOXES. Всю остальную работу делают обработчики сообщений WM_LBUTTONDOWN, WM_LBUTTONDBLCLK и WM_KEYDOWN. Все они используют для переключения галочек вспомогательную функцию CheckSelectedItems. Вы можете вызывать эту функцию и сами, хотя такая необходимость возникает нечасто. Функция CheckSelectedItems получает единственный параметр – номер элемента (этот элемент должен быть выделен). Она считывает состояние его галочки, инвертирует это состояние и применяет ко всем выделенным элементам в списке. Резюмируя сказанное выше, для применения класса CCheckListViewCtrl в большинстве случаев достаточно просто связать объект этого класса с контролом, используя макрос DDX_CONTROL. Класс CHyperLinkКласс CHyperLink предназначен для создания гиперссылок. На самом деле, большую часть функциональности он наследует от базового класса CHyperLinkImpl. Гиперссылка создаётся на основе статического элемента управления. Класс CHyperLink наглядно демонстрирует, что иногда для решения самых простых задач приходится написать множество строк кода. Если, конечно, учесть разные мелочи, о которых задумываются далеко не все. Вот список основных возможностей класса. • Гиперссылка выглядит, как в IE. Интересно, что цвета для ссылки (обычной и посещённой) берутся из настроек IE, хранящихся в реестре. Если настройки обнаружить не удаётся, используются цвета по умолчанию (синий и фиолетовый). • При наведении на ссылку курсор приобретает форму руки. Обратите внимание, что под Windows 2000 класс CHyperLink использует системный курсор, а в других версиях Windows создаёт его на лету, избавляя вас от необходимости включать его в ресурсы во всех случаях. Кроме того, следует отметить, что курсор должен располагаться именно на надписи, а не в любой точке статического контрола, даже если вы нарисовали его очень большим. • Если задержать курсор над ссылкой, появляется всплывающая подсказка с адресом, по которому будет осуществляться переход. • Класс CHyperLink поддерживает не только мышиный, но и клавиатурный интерфейс. Ссылка, на которую установлен фокус ввода, выделяется пунктирной рамкой. При этом можно перейти по ней, нажав Enter или Space.
Переход по ссылке реализован через функцию ShellExecute. Эта функция понимает как адреса сайтов (при этом открывается броузер), так и почтовые адреса (при этом запускается почтовый клиент). Список методов класса CHyperLink приведён в таблице 8.
Класс CMultiPaneStatusBarCtrlImpl Класс CMultiPaneStatusBar призван облегчить вашу жизнь при работе со строками состояния. Стандартный контрол status bar из набора общих контролов Windows позволяет создать на строке состояния до 256 панелей, в которых можно отображать текст и иконки. Но он не предоставляет никаких средств для автоматического перемещения этих панелей. Программисту на чистом API приходится передвигать их вручную всякий раз, когда строка состояния изменяет свой размер. В MFC эту работу берёт на себя класс CStatusBar. А в WTL вам поможет класс CMultiPaneStatusBar. Посмотрим, каким образом используется класс CMultiPaneStatusBar. Сначала объект класса связывается с существующей строкой состояния при помощи DDX_CONTROL. Можно и создать строку состояния с нуля, используя метод Create. Затем задаётся набор панелей для строки состояния. Для этого предназначен метод SetPanes. Он принимает количество панелей и массив с их идентификаторами. Идентификаторы используются для последующего обращения к панелям. Одной из панелей можно назначить стандартный идентификатор ID_DEFAULT_PANE. Панель с таким идентификатором растягивается, занимая всё свободное пространство в строке состояния. Остальные панели имеют фиксированный размер (который всегда можно изменить, используя метод SetPaneWidth). О корректном перемещении панелей заботится WTL. Вам остаётся только изменять текст панелей, их иконки и всплывающие подсказки в соответствии с вашими нуждами. Полный список методов класса CMultiPaneStatusBar приведён в таблице 9.
И последний важный момент. Всякий раз, когда окно изменяет размер, вы должны посылать строке состояния сообщение WM_SIZE, чтобы она могла скорректировать своё местоположение и размер. Класс CWaitCursorКласс CWaitCursor – это простенькая обёртка вокруг метода SetCursor из Win32 API. При помощи этого класса вы можете временно изменить вид курсора мыши. Чаще всего класс CWaitCursor применяют, чтобы "выплюнуть" песочные часы на время выполнения длительной операции. Отсюда и название класса. Полный список методов класса CWaitCursor приведён в таблице 8.
Предлагаемые по умолчанию параметры конструктора "заточены" для индикации длительной операции. Использование класса CWaitCursor в этом случае тривиально: void LengthyOperation() { // Конструктор объекта waitCur вызовет метод Set, и курсор поменяется на "песочные часы". CWaitCursor waitCur; // Выполняем длительную операцию. … // Здесь вызывается деструктор для объекта waitCur, и курсор восстанавливается. }Класс COwnerDraw<>: отрисовка контрола родительским окном в стиле WTL Механизм отрисовки контрола родительским окном (owner draw) появился довольно давно – ещё в Windows 3.0. Он позволяет придать контролу совершенно произвольный внешний вид. Его поддерживают такие стандартные элементы управления, как кнопка, меню, простой список и комбинированный список. В основе механизма owner draw лежат сообщения WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM и WM_DELETEITEM. Так, в обработчике WM_DRAWITEM выполняется собственно отрисовка контрола, а в обработчике WM_MEASUREITEM – задание размеров отдельных элементов, содержащихся в контроле (пунктов меню, элементов списка и т.п.). WTL содержит небольшой класс COwnerDraw<>, который помогает вам обрабатывать все эти сообщения (описан в файле atlframe.h). Чтобы им воспользоваться, включите его в список базовых классов окна, которое будет заниматься отрисовкой контролов. Посмотрим, какие элементы входят в класс COwnerDraw<>. В первую очередь это карта сообщений. Точнее, две карты (вы ещё не забыли, что в WTL окно может иметь несколько карт сообщений?). BEGIN_MSG_MAP(COwnerDraw<T>) MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem) MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem) MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem) MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem) ALT_MSG_MAP(1) MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem) MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem) MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem) MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem) END_MSG_MAP() По умолчанию используется карта с номером 0. Она обрабатывает сообщения в родительском окне. Карту с номером 1 можно использовать для перехвата отражённых сообщений, связанных с механизмом owner draw, в самом контроле. Обработчики сообщений реализованы примерно одинаково. Они распаковывают параметры сообщений и передают управление специальным функциям, которые и выполняют основную работу. Вот прототипы этих функций. void DrawItem(LPDRAWITEMSTRUCT); void MeasureItem(LPMEASUREITEMSTRUCT); int CompareItem(LPCOMPAREITEMSTRUCT); void DeleteItem(LPDELETEITEMSTRUCT); Именно эти функции вы можете переопределить в производном классе, чтобы реализовать отрисовку контрола. Это удобнее, чем вручную перехватывать сообщения и вспоминать, каким образом в их параметрах запакована информация. Обратите внимание, что класс COwnerDraw<> содержит стандартную реализацию этих функций. Функции DrawItem, CompareItem и DeleteItem ничего полезного не делают, зато функция MeasureItem возвращает размер пункта меню в зависимости от настроек системы и размер элемента в списке в зависимости от размера стандартного системного фонта, который используется в диалогах и меню. Если такое поведение вас не устраивает, измените его на любое другое. Рассмотрим пример использования класса COwnerDraw<> для рисования нестандартной кнопки. class CButtonDemoDlg : public CSimpleDialog<IDD_BUTTON_DIALOG>, public COwnerDraw<CButtonDemoDlg>, ... { private: HICON m_hIcon1, m_hIcon2; ... public: BEGIN_MSG_MAP(CButtonDemoDlg) ... CHAIN_MSG_MAP(COwnerDraw<CButtonDemoDlg>) END_MSG_MAP() void DrawItem(LPDRAWITEMSTRUCT pDIS) { if ((pDIS->itemState & ODS_SELECTED) != 0) { // Кнопка нажата DrawIcon(pDIS->hDC, 0, 0, m_hIcon2); } else { // Кнопка отпущена DrawIcon(pDIS->hDC, 0, 0, m_hIcon1); } } };Класс CCustomDraw<>: пользовательское рисование в стиле WTL Механизм пользовательского рисования (custom draw) иногда путают с owner draw. Он предназначен для той же цели – изменить внешний вид контролов. Однако он появился несколько позже (вместе с набором общих контролов из библиотеки comctl32.dll) и используется для более новых контролов (таких, как ListView и TreeView). Пользовательское рисование работает следующим образом. Когда контрол перерисовывается, он посылает родительскому окну одно или несколько уведомлений NM_CUSTOMDRAW, упакованных в сообщение WM_NOTIFY. Каждое уведомление соответствует некоторой фазе перерисовки (до/после рисования контрола целиком или отдельного элемента и т. д.). Фазу можно определить по полю dwDrawStage структуры NMCUSTOMDRAW, указатель на которую передаётся вместе с уведомлением. В зависимости от фазы родительское окно может выполнить некоторые действия (например, изменить цвет или фонт отдельного элемента списка). Подробности можно найти в MSDN (см. статью "Customizing a Control's Appearance Using Custom Draw"). В WTL есть класс CCustomDraw<> (описан в файле atlctls.h), который помогает вам перехватывать уведомление NM_CUSTOMDRAW и распаковывать его параметры. Он очень похож на класс COwnerDraw<>, который мы рассмотрели выше. Его реализация выглядит так. template <class T> class CCustomDraw { public: // Message map and handlers BEGIN_MSG_MAP(CCustomDraw<T>) NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw) ALT_MSG_MAP(1) REFLECTED_NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw) END_MSG_MAP() // message handler LRESULT OnCustomDraw(int idCtrl, LPNMHDR pnmh, BOOL& bHandled) { T* pT = static_cast<T*>(this); pT->SetMsgHandled(TRUE); LPNMCUSTOMDRAW lpNMCustomDraw = (LPNMCUSTOMDRAW)pnmh; DWORD dwRet = 0; switch(lpNMCustomDraw->dwDrawStage) { case CDDS_PREPAINT: dwRet = pT->OnPrePaint(idCtrl, lpNMCustomDraw); break; case CDDS_POSTPAINT: dwRet = pT->OnPostPaint(idCtrl, lpNMCustomDraw); break; // Остальные фазы отрисовки // ... default: pT->SetMsgHandled(FALSE); break; } bHandled = pT->IsMsgHandled(); return dwRet; } // Overrideables DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/) { return CDRF_DODEFAULT; } DWORD OnPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/) { return CDRF_DODEFAULT; } // Остальные функции. // ... Как видим, в классе CCustomDraw<> также предусмотрено две карты сообщений – для родительского окна и для самого контрола, если он получает отражённые уведомления. Обработчик OnCustomDraw распаковывает параметры уведомления NM_CUSTOMDRAW и определяет фазу рисования. Каждой фазе соответствует своя функция, которая и вызывается из OnCustomDraw. Вы можете переопределить любую из этих функций в производном классе и включить в неё нужный вам код (реализации из класса CCustomDraw<> не выполняют никой полезной работы). Список фаз рисования и соответствующих им функций приведён в таблице 10.
Вот небольшой пример использования класса CCustomDraw<>. Для разнообразия я поручил обработку сообщения NM_CUSTOMDRAW самому контролу. Подразумевается, что родительское окно переправляет ему уведомления, используя механизм отражения. class CCustomDrawListView : public CWindowImpl<CCustomDrawListView, CListViewCtrl>, public CCustomDraw<CCustomDrawListView> { public: BEGIN_MSG_MAP(CCustomDrawListView) // Направляем сообщения в карту №1 класса CCustomDraw! CHAIN_MSG_MAP_ALT(CCustomDraw<CCustomDrawListView>, 1) END_MSG_MAP() DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw) { // Запрашиваем уведомления NM_CUSTOMDRAW для каждого элемента списка. return CDRF_NOTIFYITEMDRAW; } DWORD OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw) { // Нам нужны поля, специфичные для ListView. LPNMLVCUSTOMDRAW pLVCD = (LPNMLVCUSTOMDRAW)lpNMCustomDraw; if ((lpNMCustomDraw->dwItemSpec & 0x01) != 0) { // Для нечётных элементов: рисуем белым по чёрному. pLVCD->clrText = RGB(255,255,255); pLVCD->clrTextBk = RGB(0,0,0); } else { // Для чётных элементов: рисуем красным по серому. pLVCD->clrText = RGB(255,0,0); pLVCD->clrTextBk = RGB(200,200,200); } return CDRF_NEWFONT; } };От теории к практике Мы изучили уже целую кучу новых классов, и теперь самое время посмотреть, как они применяются на практике. В этом разделе мы изучим целый ряд демонстрационных программ, иллюстрирующих различные аспекты программирования диалогов и контролов с использованием библиотеки WTL. WTLErrLook: приложение на базе модального диалогаДемонстрационный проект WTLErrLook WTLErrLook Приложение WTLErrLook – это упрощённый вариант программы Error Lookup, которая входит в Visual Studio 6. Главное окно программы выполнено в виде модельного диалога. Обмен данными с полями ввода осуществляется с помощью DDX_TEXT. WTLSndVol: приложение на базе немодального диалогаДемонстрационный проект WTLSndVol WTLSndVol WTLSndVol – это упрощённая версия регулятора громкости (sndvol32.exe), который входит в комплект Windows. При запуске программы она не показывает главное окно (которое выполнено в виде немодального дмалога), а размещает иконку в системном трее (Shell_NotifyIcon). Чтобы она отличалась от иконки стандартного регулятора, я сделал её зелёной. Щелчок по иконке приводит к появлению окна регулятора. Для изменения громкости используется класс CSimpleMixer. Рассматривать его устройство мы не будем, так как это тема для отдельной статьи. Чтобы закрыть WTLSndVol, щёлкните правой кнопкой на иконке в трее и выберите из меню команду Exit. WTLNavigator: использование диалогов с ActiveX-контроламиДемонстрационный проект WTLNavigator WTLNavigator WTLNavigator – это примитивный броузер, построенный на основе ActiveX-контрола "Web Browser". Класс главного окна приложения унаследован от класса CAxDialogImpl. WTLCalc: обновление дочерних оконДемонстрационный проект WTLCalc WTLCalc WTLCalc – это простенький калькулятор. Доступность математических операций в калькуляторе зависит от введённого числа: логарифм может применяться только к положительным числам, факториал – только к натуральным и т. д. Соответственно, для включения и выключения кнопок используется механизм CUpdateUI. WTLSizeDlg: пример масштабируемого диалогаДемонстрационный проект WTLSizeDlg WTLSizeDlg Программа WTLSizeDlg не выполняет никакой полезной работы. Она просто рисует диалог и позволяет его масштабировать. Для поддержки масштабирования используется класс CDialogResize. Обратите внимание, что корректное масштабирование контролов обеспечивается благодаря наличию невидимого контрола. WTLCtlDemo: использование стандартных и общих контроловДемонстрационный проект WTLCtlDemo WTLCtlDemo Программа WTLCtlDemo показывает, как можно работать со стандартными контролами – static, button, edit box, list box, combo box, list view и tree view. WTLCtlxDemo: использование "самодельных" контролов WTLДемонстрационный проект WTLCtlxDemo WTLCtlxDemo Программа WTLCtlxDemo демонстрирует применение самодельных контролов, предоставляемых библиотекой WTL – CBitmapButton, CHyperLink, CCheckListViewCtrl и CMultiPaneStatusBarCtrl. Это все на сегодня. Пока! (Алекс Jenter jenter@rsdn.ru) (Duisburg, 2001. Публикуемые в рассылке материалы принадлежат сайту RSDN. ) |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|