Глава 14.
Модальные и немодальные блоки диалога
Импровизировать может лишь тот.
кто знает роль назубок.
Джоди Фостер
Блоки диалога (dialog boxes) — специальные окна, поддерживающие гибкие средства, используя которые пользователь может взаимодействовать с программой. Как правило, они включают дочерние (child) окна, которые в контексте этого типа окон называются элементами управления.
Блоки диалога бывают двух типов — модальные (modal) и немодальные (modeless). Разница между ними заключается в способе управления потоками сообщений. Модальный блок диалога блокирует все остальные окна приложения так, что пользователь не может с ними ничего сделать, пока не закроет блок. Другими словами, модальный блок отсекает поток сообщений, идущих от мыши и клавиатуры к его родительским окнам или окнам того же уровня (sibling), делая их недоступными. Если пользователь все же пытается взаимодействовать с недоступным окном, то система предупреждает об этом звуковым сигналом. Блоки диалога этого типа обычно не препятствуют переключению с одного приложения на другое. В редких случаях можно использовать системные модальные блоки диалога, которые позволяют пользователю работать внутри лишь одного блока диалога и не позволяют переключаться ни на какие другие приложения.
Немодальные блоки диалога больше похожи на обычные окна, т. к. они дают пользователю возможность доступа к остальным окнам приложения. Это связано с тем, что блоки диалога этого типа не прерывают потоков сообщений, идущих в любую часть вашей программы.
Оба типа блоков диалога являются важнейшей частью пользовательского интерфейса Windows, и для них уже давно сложился стандартный и широко используемый набор элементов управления.
Примечание
Наряду с уже привычными элементами управления Windows:, кнопками (PUSHBUTTON), флажками (CHECK BOX), переключателями (RADIO BUTTON), списками (LIST BOX), элементами редактирования (EDIT), комбинированными списками (COMBO BOX), полосами прокрутки (SCROLLBAR) и статическими элементами (STATIC) в Win32 добавлено достаточно большое число новых, которые будут подробно рассмотрены ниже.
На первый взгляд немодальные блоки диалога более привлекательны, поскольку дают пользователю свободу выбора способа действия. Однако большинство блоков диалога все-таки модальные. Это связано с тем, что модальный блок диалога идеально концентрирует внимание пользователя на некотором ограниченном наборе действий, которые он может в данный момент выполнить.
Создание и использование блока диалога, модального и немодального, требует одновременного наличия трех компонентов:
Начнем с шаблона, т. к. без него не будет и самого блока диалога.
Шаблон блока диалога представляет собой объект данных, определенный в файле ресурсов (.гс), аналогичный приведенному ниже:
// Блок диалога About
IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 217, 55
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION [ WS_SYSMENU
CAPTION "О программе Graph"
FONT 8, "MS Sans Serif"
BEGIN
ICON IDR_MAINFRAME,IDC_STATIC,7,7,21,20
CTEXT "Приложение Graph Версия 1.0",
IDC_STATIC,51,17,119,8, SS_NOPREFIX
CTEXT "Copyright © 2000",IDC_STATIC,127,34,71,8
DEFPUSHBUTTON "OK",IDOK,178,7,32,14,WS_GROUP
LTEXT "Тихомиров Ю.",IDC_STATIC,35,34,89,8
END
Один из способов создания шаблона блока диалога — посредством редактирования файла ресурсов. Другой способ заключается в использовании какого-либо визуального средства, такого как Арр Studio или ему подобного, которое позволяет изобразить предварительный набросок блока диалога (рис. 14.1).
Рис. 14.1. Эскиз блока диалога
Но какой бы способ вы не выбрали, помните, что блок диалога — это всего-навсего еще один ресурс. Внимательно посмотрев на приведенный текст, вы заметите, что первая строчка содержит идентификатор ресурса блока диалога (IDD_ABOUTBOX), за которым следует объявление типа ресурса (DIALOG) и размеров блока диалога. Следующая строчка определяет его стиль, объединяя четыре константы, в основном уже знакомые вам. После того как объявлен стиль, можно указать заголовок блока диалога (CAPTION), который появляется в его полосе заголовка. Если не определять этот оператор, то, естественно, полоса останется пустой. Следующая строчка описания ресурса (FONT) определяет размер и имя шрифта, который будет использоваться в этом блоке диалога.
Примечание
Если при определении блока диалога вы не используете стиль WS_CAPTION, то не будет и полосы заголовка.
Описание блока диалога заканчивается несколькими строчками, заключенными между BEGIN и END. Они определяют пиктограмму, две строки текста и кнопку. Как мы говорили, все они являются элементами управления, т. е. окнами. Другими словами, наш блок диалога имеет четыре дочерних окна, размещенных внутри него: кнопку, два статических элемента управления и пиктограмму.
Строчка, описывающая кнопку, начинается с оператора, определяющего тип элемента управления (DEFPUSHBUTTON) и имеет 7 параметров. Первый — текст, ассоциирующийся с элементом управления, затем идут идентификатор, прямоугольник размещения и стиль. Сектор размещения задается четырьмя числами: координатами левого верхнего угла, шириной и высотой.
Поскольку и пиктограмма (ICON), и статические элементы (LTEXT и СТЕХТ) являются окнами, то неудивительно, что они имеют такие же параметры, что и кнопка. Некоторые стили элементов управления указываются непосредственно в названии. Например, DEFPUSHBUTTON говорит о том, что у кнопки установлен стиль BS_DEFPUSHBUTTON, a LTEXT — о том, что элемент управления имеет установленный флаг стиля ES_LEFT. Вместо . конкретных операторов можно указывать также оператор CONTROL, являющийся общим для различных элементов управления и имеющий 8 параметров:
CONTROL <текст>, nID, <класс>, х, у, сх, су, <стили>
где к уже перечисленным параметрам добавляется символьная строка, определяющая имя оконного класса, с помощью которого должен быть создан элемент управления блока диалога.
Итак, формирование шаблона блока диалога включает создание объекта ресурсов, который описывает форму, размер и расположение на экране блока диалога и каждого из его внутренних элементов управления. Данный объект помещается в файл ресурсов приложения вместе с другими объектами (пиктограммами, меню, битовыми образами и т. д.). После того как шаблон сформирован, можно приступать к созданию программного кода, способного вызывать появление блока диалога на экране и его последующее удаление.
Как обычно, библиотека MFC предоставляет специальный класс для создания и работы с блоками диалогов — CDialog. Следуя принятому нами стилю изложения, рассмотрим этот класс подробнее.
Основное назначение класса CDialog состоит в предоставлении интерфейса программирования для управления блоками диалога.
Рис. 14.2. Место класса CDialog в иерархии библиотеки MFC
Использование данного класса позволяет создавать объекты обоих типов блоков диалога — модальные и немодальные. Повторим еще раз: блок диалога подобен любому другому окну Windows, но в отличие от других типов - окон, объект этого класса является комбинацией класса CDialog или производного от него и шаблона диалога. Два способа создания шаблона мы уже рассмотрели, а к третьему — создание шаблона в памяти, вернемся несколько позже. Теперь же познакомимся непосредственно с самим классом CDialog.
Для создания объекта модального блока диалога на основе шаблона, хранящегося в ресурсах, реализованы два варианта конструктора:
CDialog::CDialog (
LPCTSTR IpszTemplateName,
CWnd *pParentWnd = NULL)
и
CDialog::CDialog(
UINT nIDTemplate,
CWnd *pParentWnd = NULL)
Первый параметр определяет шаблон: IpszTemplateName— указатель на символьную строку, содержащую имя шаблона, nIDTemplate — номер идентификатора ресурса. Параметр pParentWnd указывает на объект родителя или владельца, которому принадлежит объект блока диалога. Если он равен NULL, то родительским окном является основное окно приложения.
Следующим шагом является создание блока диалога Windows. Для этого в классе реализована специальная функция
virtual int CDialog::DoModal ()
Создает и активизирует модальный блок диалога Windows, осуществляет полное взаимодействие с пользователем и удаляет блок диалога после завершения работы, возвращая целое значение типа IDOK или IDCANCEL, позволяющее определить конечный результат его работы. Если функция не смогла создать блок диалога, то она возвращает -1.
Помимо использования шаблона, хранящегося в ресурсе, в системе предусмотрена возможность создания шаблона в памяти непосредственно во время выполнения программы. Это необходимо при косвенном создании блока диалога, как модального, так и немодального. Первым шагом на этом пути является определение и заполнение структуры DLGTEMPLATE, которая позволяет задать размеры и стиль блока диалога. Познакомимся с ней поближе:
typedef struct {
DWORD style;
DWORD dwExtendedStyle;
WORD edit;
short x;
short y;
short ex;
short cy;
} DLGTEMPLATE;
Поле style определяет стиль блока диалога. Наряду с уже известными стилями окон (такими как WS_CHILD и WS_CAPTION), для блоков диалога применяются также дополнительные:
DS_3DLOOK
Вывод трехмерной рамки вокруг элементов управления в блоке диалога
DS_ABSALIGN
Координаты блока диалога задаются относительно экрана; если этот стиль не определен, то они задаются относительно рабочей области
DS_CENTER
Блок диалога располагается по центру рабочей области
DS_CENTERMOUSE
Курсор мыши располагается в центре блока диалога
DS_CONTEXTHELP
В полосу заголовка блока диалога добавляется знак вопроса, который подключает контекстно-зависимую справку. На самом деле после создания блока диалога система проверяет наличие этого стиля, и если он присутствует, то добавляет флаг WS_EX_CONTEXTHELP к расширенному стилю блока диалога
DS_CONTROL
Блок диалога может использоваться как элемент управления в другом блоке диалога; наличие этого стиля позволяет пользователю перемещаться по элементам управления дочернего блока диалога, использовать командные клавиши и т. д.
DS_FIXEDSYS
Блок диалога использует SYSTEM_FIXED_FONT вместо SYSTEM_FONT
DS_MODALFRAME
Модальный блок диалога; этот стиль часто комбинируют с WS_CAPTION и WS_SYSMENU
DS_NOFAILCREATE
Создает блок диалога даже в том случае, если произошли ошибки; например, если не может быть создан какой-либо элемент управления
DS_NOIDLEMSG
Блокирует сообщение WM_ENTERIDLE, которое Windows посылает владельцу блока диалога, пока он отображается на экране
DS_SETFONT
Показывает, что шаблон блока диалога (структура DLGTEMPLATE) содержит дополнительный элемент, определяющий имя и размер шрифта; этот шрифт используется для текста в рабочей области блока диалога и для текста внутри элементов управления; при этом сообщение Windows WM_SETFONT посылается в блок диалога и в каждый элемент управления
DS_SETFOREGROUND
Переводит блок диалога на передний план, для чего Windows вызывает функцию SetForeg round Window
DS_SYSMODAL
Создает системный модальный блок диалога; при этом он обязательно должен иметь установленным расширенный стиль WS_EX_TOPMOST, иначе стиль будет проигнорирован
Поле dwExtendedStyle служит для установки расширенных стилей окна. Для создания блоков диалога это поле не используется, за исключением вышеупомянутых случаев. (Оно применяется при создании с помощью шаблона блока диалога других типов окон.)
В поле edit задается число элементов блока диалога или, другими словами, — число структур DLGITEMTEMPLATE, которые описывают элементы управления блока диалога. Эта структура содержит следующие поля:
typedef struct {
DWORD style;
DWORD dwExtendedStyle;
short x;
short y; ""
short ex;
short cy;
WORD id;
} DLGITEMTEMPLATE;
Все поля структуры DLGITEMTEMPLATE аналогичны только что описанным, кроме поля id, которое определяет идентификатор элемента управления. При ее использовании необходимо дополнительно описать еще три массива, определяющие класс, заголовок и данные элемента управления. Каждый массив состоит из одного или более 16-битных элементов:
Поля х, у, сх и су определяют соответственно координаты левого верхнего угла блока диалога, его ширину и высоту. Все размеры задаются в единицах блока диалога, которые можно преобразовать в значения экранных координат при помощи функции MapDialogRect.
При использовании структуры DLGTEMPLATE необходимо дополнительно описать еще три массива переменной длины, которые определяют меню, класс и заголовок блока диалога. Каждый массив содержит один или более 16-битных элементов:
Если для блока диалога установлен стиль DS_SETFONT, то за массивом заголовка располагается 16-битный элемент, определяющий размер шрифта, за которым следует массив, задающий его вид. Массив вида шрифта — строка Unicode, заканчивающаяся нулевым символом и определяющая имя вида. Если эти параметры заданы, то Windows посылает сообщения WM_SETFONT оконным процедурам блока диалога и элементов управления. После создания и заполнения необходимых структур и массивов можно создавать окно блока диалога. Как обычно, сначала нужно создать объект класса, используя для этого пустой конструктор, а затем вызвать функцию
BOOL CDialog::InitModallndirect (
LPCDLGTEMPLATE IpDialogTemplate,
CWnd *pParentWnd = NULL)
или
BOOL CDialog::InitModallndirect (
HGLOBAL hDialogTemplate,
CWnd *pParentWnd = NULL)
Оба варианта этой функции инициализируют объект модального блока диалога, используя предварительно созданный в памяти шаблон, на который указывает либо IpDialogTemplate — указатель на структуру DLGTEMPLATE, либо hDialogTemplate — дескриптор глобальной памяти, где определены структура DLGTEMPLATE и данные для каждого элемента управления.
Примечание
Обратите особое внимание на то, что после выполнения функции CDialog:: InitModallndirect система Windows создает блок диалога, но не отображает его. Он появляется на экране только после непосредственного вызова функции CDialog::DoModal.
Рассмотрим теперь создание немодального блока диалога. Первый способ предполагает наличие ресурса шаблона. Сначала вызывается пустой конструктор для создания объекта (так же, как при создании модального блока диалога на основе шаблона в памяти). Затем для создания немодального блока диалога и присоединения его к объекту CDialog вызывается функция
BOOL CDialog::Create (
LPCTSTR IpszTemplateName,
CWnd *pParentWnd = NULL)
или
BOOL CDialog::Create(
UINT nIDTemplate,
CWnd *pParentWnd = NULL)
Функцию можно вызвать прямо внутри конструктора класса, производного от CDialog, а ее параметры аналогичны параметрам конструктора для модального блока диалога.
Второй способ создания немодального блока диалога опирается на шаблон, предварительно созданный в памяти. Этот способ по своей сути ничем не отличается от аналогичного способа для модального блока диалога, за исключением того, что вместо CDialog::InitModalIndirect вызывается функция
BOOL CDialog::Createlndirect (
LPCDLGTEMPLATE IpDialogTemplate,
CWnd *pParentWnd = NULL)
или
BOOL CDialog::Createlndirect(
HGLOBAL- hDialogTemplate,
CWnd *pParentWnd = NULL)
Еще одним отличием является то, что если при создании модального блока диалога не было необходимости устанавливать стиль WS_VISIBLE, т. к. такой блок диалога становится видимым автоматически, то для немодального блока диалога наличие или отсутствие этого стиля определяет, будет ли он отображаться на экране после своего создания. Если стиль не установлен, то необходим явный вызов функции CWnd::ShowWindow для вывода блока диалога на экран.
В отличие от функции CDialog::DoModal, функции CDialog::Create и CDialog:: Createlndirect завершаются сразу же после создания блока диалога Windows.
После окончания работы с немодальным блоком диалога необходимо вызвать функцию CWnd::DestroyWindow для того, чтобы уничтожить его.
Несмотря на то, что блок диалога представляет собой стандартное окно Windows, для него имеются свои специфические сообщения. Одним из основных является WM_INITDIALOG. Это сообщение посылается процедуре блока диалога во время вызова функций Create, Createlndirect или DoModal после того, как были созданы окна всех его элементов управления, но до того, как они стали видимыми. В ответ на него процедура блока диалога инициализирует каждый элемент управления. Например, она может заполнить список элементами, с которыми потом будет работать пользователь. В ответ на сообщение WM_INITDIALOG система Windows вызывает специальный обработчик
virtual BOOL CDialog::OnlnitDialog ()
через стандартную глобальную процедуру блока диалога, общую для всех блоков диалога библиотеки MFC.
Для того чтобы выполнить какую-либо специальную обработку, необходимо переопределить эту функцию в своем классе. При этом не забывайте вызывать функцию OnlnitDialog базового класса для корректной инициализации. Функция должна возвращать нулевое значение только в том случае, когда фокус ввода явно устанавливается на какой-либо элемент управления, и ненулевое, когда фокус ввода устанавливается на первый элемент.
После окончания работы с модальным блоком диалога обязательно должна вызываться функция
void CDialog::EndDialog (int nResult)
Функция не закрывает блок диалога, а только устанавливает флажок, чтобы закрыть его, как только закончится обработка текущего сообщения. Параметр nResult передается в функцию DoModal, которая использует его в качестве возвращаемого значения. Вызывать эту функцию можно в любой момент, даже в функции OnlnitDialog. В этом случае блок диалога необходимо закрыть до того, как он будет выведен на экран или до установки фокуса ввода на какой-либо элемент управления.
Чаще всего пользователь ожидает увидеть в блоке диалога две кнопки — ОК и Cancel (Отмена). Поэтому для вас не должно быть неожиданностью, что класс CDialog имеет встроенную поддержку обработки команд от этих кнопок. Начнем с обработчика кнопки ОК.
virtual void CDialog::OnOK ()
Фиксирует данные блока диалога и обновляет соответствующие переменные приложения. Если нужны какие-либо дополнительные действия, то можно переопределить эту функцию для модального блока диалога. В случае немодального блока диалога ее переопределение обязательно. Дело в том, что функция EndDialog, которая вызывается в функции базового класса, делает блок диалога невидимым, но не уничтожает его. Поэтому внутри переопределенной функции необходимо вызвать функцию DestroyWindow для уничтожения соответствующего окна Windows.
Для кнопки Cancel (Отмена) в классе реализован свой обработчик
virtual void CDialog::OnCancel ()
Завершает работу с модальным блоком диалога, вызывая функцию EndDialog, и возвращает IDCANCEL. Для немодального блока диалога эту функцию необходимо переопределить и вызвать внутри нее функцию DestroyWindow.
Последней переопределяемой функцией, реализованной в классе, является
virtual void CDialog::OnSetFont (CFont *pFont)
Вызывается в ответ на сообщение WM_SETFONT и устанавливает шрифт, определенный в редакторе блока диалога. Если шрифт нужно изменить, то функцию следует переопределить. Установленный шрифт используется по умолчанию всеми элементами управления блока диалога.
При обсуждении способов создания блока диалога я упомянул о том, что они имеют свои координаты, которые называются координатами блока диалога и позволяют определять размеры блока диалога независимо от конкретного устройства, поскольку масштаб элементов координатной сетки определяется относительно размеров текущего шрифта. Элементы масштаба оси X координат блока диалога соответствуют примерно 1/4 средней ширины используемого шрифта, а элементы оси Y — 1/8 высоты. Для преобразования координат блока диалога в экранные координаты в классе реализована функция
void CDialog::MapDialogRect (LPRECT IpRect)
Получает на входе размеры и координаты блока диалога через указатель IpRect (в системе координат блока диалога) и заменяет значения в соответствующих полях на экранные координаты.
Для перемещения по элементам управления блока диалога можно использовать клавишу <Таb> или мышь. Однако возникают ситуации, когда такого рода переходы необходимо выполнить программно. Для реализации программных перемещений можно использовать следующие функции:
void CDialog::NextDlgCtrl ()
и
void CDialog::PrevDlgCtrl ()
Функции устанавливают фокус на следующий или, соответственно, на предыдущий элемент управления. Все переходы осуществляются циклически, т. е. с последнего элемента управления фокус устанавливается на первый (функция NextDlgCtrl), а с первого— на последний элемент управления (функция PrevDlgCtrt).
Для перемещения фокуса на какой-либо определенный элемент управления можно использовать функцию
void CDialog::GotODlgCtrl (CWnd *pWndCtrl)
Параметр pWndCtrl определяет указатель на объект "элемент управления" (дочернее окно), на который перемещается фокус ввода. Для получения этого указателя можно воспользоваться функцией
CWnd* CWnd::GetDlgltern (int nID)
или
void CWnd::GetDlgltern (
int nID,
HWND *phWnd),
которые по идентификатору элемента управления блока диалога возвращают либо указатель на соответствующий оконный объект, либо указатель на дескриптор окна этого объекта.
Теперь, после того как мы познакомились с возможностями класса CDialog, рассмотрим пример простейшего блока диалога.
Создаем простейший блок диалога
Наш блок настолько прост, что даже нет необходимости создавать свой класс для его поддержки. Однако мы все-таки сформируем класс CAppAbout, чтобы можно было наглядно продемонстрировать разницу между модальным и немодальным блоками диалога.
Такой или аналогичный ему блок диалога фактически стал обязательным элементом любого современного приложения, которое выводит его на экран, чтобы сообщить пользователю некоторые сведения о программе, ее авторе, номере версии и т. д. Он обычно появляется при выборе соответствующего элемента в меню справки, как это показано на рис. 14.3. Сам блок диалога имеет вид, показанный на рис. 14.4. Чтобы не дублировать код, мы ограничимся только теми добавлениями, которые внесены в приложение MDI.
Рис. 14.3. Элемент "About Graph" меню "?"
Рис. 14.4. Блок диалога
///////////////////////////////////////////////////////////////////
// Файл CAppAbout.h
// Copyright (с) 2000 Тихомиров Ю.
//////////////////////////////////////////////////////////////////
class CAppAbout : public CDialog
{
// Construction
public: i
CAppAbout(CWnd* pParent=NULL);
// standard constructor
// Dialog Data
//f{AFX_DATA(CAppAbout)
enum { IDD = IDD_ABOUTBOX };
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA
// Overrides
//' ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAppAbout)
protected:
// DDX/DDV support
virtual void DoDataExchange(CDataExchange* pDX);
//}}AFX_VIRTUAL
// Implementation .
protected:
// Generated message map functions
//{{AFX_MSG(CAppAbout)
// NOTE: the ClassWizard will add member functions here
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
Добавления, внесенные в реализацию членов класса CAppAbout
///////////////////////////////////////////////////////
// Файл CAppAbout.срр
// Copyright (с) 2000 Тихомиров Ю.
/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////
//Блок диалога CAppAbout: реализация класса
// Конструктор класса
CAppAbout::CAppAbout(CWnd* pParent /*=NULL*/) :
Dialog(CAppAbout::IDD, pParent) // этого можно и не делать
{
Create(CAppAbout::IDD, pParent);
// {{AFX_DATA__INIT(CAppAbout) // между этими операторными скобками
//}}AFX_DATA_INIT // код вставляет ClassWizard
}
// Для обмена данными с блоком диалога
void CAppAbout::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{(AFX_DATA_MAP(CAppAbout) // см. предыдущий комментарий
//}}AFX_DATA_MAP }
// Карта сообщений блока диалога
BEGIN_MESSAGE_MAP(CAppAbout, CDialog)
//{{AFX_MSG_MAP(CAppAbout)
// No message handlers
//}}AFX_MSG_MAP
END__MESSAGE__MAP ()
Примечание
Чтобы не заниматься написанием кода самому, я воспользовался тем, который генерирует AppWizard. Единственная разница заключается в том, что код, связанный с классом CAppAbout, вынесен мной в отдельные файлы — AppWizard помещает его в файлы, связанные с классом приложения.
Изменения в классе CMDIApp:
/////////////////////////////////////////////////////////
// Файл Graph.h
// Copyright (с) 2000 Тихомиров Ю.
/////////////////////////////////////////////////////////
// Объект приложения CGraphApp: интерфейс класса
class CMDIApp : public CWinApp
{
...
// Implementation
//{{AFX_MSG(CMDIApp)
afx_msg void OnAppAbout();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
//////////////////////////////////////////////////////////
// Файл MDI.cpp
// Copyright (c) 2000 Тихомиров Ю.
/////////////////////////////////////////////////////////
BEGIN_MESSAGE_MAP(CMDIApp, CWinApp)
//{{AFX_MSG_MAP(CMDIApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
// Обработчик команды меню About
void CMDIApp::OnAppAbout()
{
// Создаем объект класса
CAppAbout aboutDlg;
// Выводим на экран модальный блок диалога
aboutDlg.DoModal();
}
В шаблон меню необходимо добавить следующий пункт:
...
POPUP "&?"
BEGIN
MENUITEM "About Graph ...", ID_APP_ABOUT
END
...
И, наконец, необходимо создать шаблон блока диалога, который должен иметь идентификатор — IDD_ABOUTBOX. Можете воспользоваться для этого одним из трех предлагаемых способов (на самом деле их больше):
Примечание
Последний способ не такой уж и сложный, как может показаться на первый взгляд, но зато самый эффективный.
Как видите, все достаточно просто. Блок диалога появляется на экране при выборе пользователем пункта "About Graph..." из меню справки. Заметим, что для вызова этого блока диалога такой путь является стандартным. После того как пункт меню выбран, вызывается обработчик OnAppAbout команды ID_APP_ABOUT, в котором создается модальный блок диалога.
Для того чтобы вместо модального блока диалога создать немодальный, достаточно изменить конструктор и обработчик команды ID_APP_ABOUT, как показано ниже:
CAppAbout::CAppAbout(CWnd* pParent)
{
...
// Создаем объект Windows
Create(CAppAbout::IDD, pParent);
...
}
void CMDIApp::OnAppAbout()
{
// Создаем объект Windows
CAppAbout *aboutDlg = new CAppAbout();
}
Теперь, если вы выведете на экран блок диалога "О программе...", можно продолжать работу с любым окном приложения без каких-либо ограничений.
Приведенный пример наглядно иллюстрирует общий подход к созданию блоков диалога на основе шаблона, определенного в файле ресурсов. Теперь, опираясь на эти знания, рассмотрим пример приложения, который поможет нам изучить еще несколько новых интересных моментов:
Блок диалога в качестве главного окна приложения
Для реализации этой возможности нужно проделать всего три шага.
1. Определить в ресурсах шаблон блока диалога. Способы, которыми это можно сделать, мы подробно рассмотрели выше. На рис. 14.5 показан блок диалога для нашего приложения, созданный с помощью редактора ресурсов.
Рис. 14.5. Главное окно приложения является блоком диалога
2. Создать класс на основе класса CDialog
class CTemplateDlg : public CDialog
{
public:
CTemplateDlg(CWnd* pParent = NULL);
...
};
3. Создать объект нового класса CtemplateDlg, что мы и делаем в переопределенной функции Initlnstance:
BOOL CTemplateApp::Initlnstance ()
{
...
// Создаем объект класса
CTemplateDlg dig;
// Поскольку это главное окно приложения,
// присваиваем указатель на него переменной m_pMainWnd
mjpMainWnd = &dlg;
// Выводим диалог на экран
int nResponse = dig.DoModal();
}
Примечание
Вы, наверное, обратили внимание, что я не говорю о необходимости создания класса приложения, т. к. это уже должно быть очевидно.
После создания окно сразу выводится на экран. Как видите, все настолько просто, что даже нет смысла останавливаться на этом более подробно. Поэтому мы переходим к новой теме, связанной с взаимодействием между пользователем и блоком диалога.
Обмен данными с блоком диалога
Для обмена данными с блоком диалога необходимо прежде всего определить в нашем классе соответствующие переменные, которые смогут принимать и передавать эти данные. Для этого воспользуемся услугами мастера ClassWizard:
1. Раскройте созданный шаблон блока диалога.
2. При нажатой клавише <Ctrl> дважды щелкните по тому элементу управления, с которым вы хотите сопоставить переменную.
3. На экране появится блок диалога Add Member Variable. В нем введите имя переменной m_х в поле Member variable name, в раскрывающемся списке Category можно оставить выбранное значение Value, а в раскрывающемся списке Variable type выберите UINT, поскольку координаты у нас представляются целым без знака (рис. 14.6). Нажмите кнопку ОК, и переменная добавлена.
Рис. 14.6. Добавляем переменную, ассоциированную с элементом управления блока диалога
Примечание
Вполне возможна ситуация, когда вместо блока диалога Add Member Variable на экране появится блок диалога Adding a Class (рис. 14.7). Это произойдет в том случае, если, например, не создан класс для этого диалога, или вы успели заменить его идентификатор, о котором ClassWizard ничего не знает. В первом случае нажмите кнопку ОК и выполните необходимые действия для создания нового класса, а во втором — установите переключатель в положение Select an existing class и также выполните соответствующие инструкции.
Рис. 14.7. Если не создан необходимый класс, то сделать это можно сейчас
4. Аналогичную процедуру повторите для всех остальных необходимых элементов управления блока диалога. В результате, когда вы откроете окно мастера ClassWizard и раскроете вкладку Member Variables, увидите все введенные переменные (рис. 14.8).
Рис. 14.8. Здесь можно посмотреть переменные, ассоциированные с элементами управления блока диалога
Вам осталось только посмотреть на код, который создал ClassWizard:
///////////////////////////////////////////////////////////////////
// Блок диалога CTemplateDlg
// Определение класса
class CTemplateDlg : publi'c CDialog{
...
// Dialog Data
// Специальный формат AFX комментариев для ClassWizard
//{{AFX_DATA(CTemplateDlg)
// Здесь ClassWizard располагает переменные
// для обмена с блоком диалога
enum { IDD = IDD_TEMPLATE_DIALOG );
CString m_strTitle;
UINT m_x;
UINT m_y;
UINT m_nWidth;
UINT m_nHeight;
int m_nltem;
CString m_strltem;
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFXJ7IRTUAL(CTemplateDlg)
protected:
// Поддержка механизма обмена данными с блоком диалога — (DDX/DDV)
virtual void DoDataExchange(CDataExchange* pDX);
//}}AFX_VIRTUAL
...
};
Мне представляется уместным привести список элементов управления и соответствующих им типов переменных:
Элемент редактирования (EDITBOX)
CString, int, UINT, long, DWORD, float, double, short, BOOL
Обычный флажок (CHECKBOX)
BOOL
Флажок с тремя состояниями (CHECKBOX)
int
Первый в группе переключатель (RADIOBUTTON)
int
Несортированный список (LISTBOX)
CString, int
Комбинированный список (СОМВОВОХ)
CString, int
Все остальные виды списков
CString
Начальные значения данных устанавливаются в переопределенной функции OnlnitDialog или в конструкторе нашего класса блока диалога:
// Реализация класса
CTemplateDlg::CTemplateDlg(CWnd* pParent)
: CDialog(CTemplateDlg::IDD, pParent){
... // Здесь можно расположить инициализацию своих переменных
// Специальный формат AFX комментариев для ClassWizard
//{(AFX_DATA_INIT(CTemplateDlg)
// Инициализация переменных для обмена с блоком диалога
m_strTitle = _T ("").;
m_х = 0;
m_у =0;
m_nWidth = 0;
m_nHeight = 0;
m_nltem = -1;
m_strltem = _T('"");
/ / } } AFX_DATA__INIT
... //а здесь другие необходимые действия
}
Примечание
Рассматриваемый ниже механизм справедлив для любого класса, производного от CWnd, а не только для блоков диалога. Однако поскольку чаще всего он применяется именно при работе с блоками диалога, я позволил себе использовать термин "обмен данными с блоком диалога".
После того как определены источник и приемник данных, а также их типы, пришло время ознакомиться с тем, каким образом осуществляется обмен. Для организации обмена нужны всего две функции:
BOOL CWnd::UpdateData (BOOL bSaveAndValidate = TRUE)
Единственный параметр bSaveAndValidate определяет направленность действий. Если он равен TRUE, то данные, введенные пользователем в блоке диалога, записываются в соответствующие переменные класса, в противном случае данные инициализируют элементы управления блока диалога. Данная функция вызывается в функции CDialog::OntnitDialog с параметром, равным FALSE, для инициализации данных при создании модального блока диалога, и в функции Со/а/од-опок с параметром, равным TRUE, для получения и проверки на допустимость данных из блока диалога, если он закрыт при помощи кнопки ОК. Основным назначением функции CWnd::UpdateData является проведение инициализации и обработки ошибок при обращении к функции
virtual void CWnd::DoDataExchange (CDataExchange *pDX),
которая, собственно, через ее параметр pDX (указатель на объект класса CDataExchange) и "занимается" обменом данных и проверкой их корректности. Эту функцию необходимо переопределить для каждого класса блока диалога, который должен обеспечивать взаимодействие с пользователем. Переопределенная функция имеет следующую стандартную форму, аналогичную приведенной в рассматриваемом приложении:
// Переопределенная функция,
// предназначенная для обмена данными с блоком диалога,
// которую также добавил ClassWizard
void CTemplateDlg::DoDataExchange(CDataExchange* pDX){
CDialog::DoDataExchange(pDX);
//{(AFX_DATA_MAP(CTemplateDlg)
DDXJText(pDX, IDC_TITLE, m_strTitle);
DDX_Text(pDX, IDC_XPOS, m_x);
DDX_Text (pDX, IDC_YPOS, m._y) ;
DDX_Text(pDX, IDC_WIDTH, m_nWidth);
DDX_Text(pDX, IDC_HEIGHT, m_nHeight);
DDX_Radio(pDX, IDC_STATICTEXT, m_nItem);
DDXJText(pDX, IDC_CAPTION, m_strltem);
//}}AFX_DATA_MAP
}
Общая схема обмена данными наглядно представлена на рис. 14.9.
Рис. 14.9. Общая схема обмена данными в приложении
Функция DoDataExchange во многом похожа на функцию Serialize, а ее параметр pDX очень напоминает параметр CArchive функции CObject::Serialize. Например, объект pDX класса CDataExchange так же, как объект класса CArchive, имеет флаг направления
BOOL CDataExchange::m_bSaveAndValidate
Флаг указывает направление при операциях обмена данными (Dialog Data Exchange, DDX). Значение TRUE задает передачу данных от элементов управления блока диалога к членам соответствующего класса, а значение FALSE — в обратном направлении. При операциях проверки корректности данных (Dialog Data Validation, DDV) флаг всегда установлен в TRUE. Значение флага определяется параметром, передаваемым функцией CWnd::UpdateData.
После того как вы определите свою функцию DoDataExchange и не хотите или не можете воспользоваться услугами мастера ClassWizard, вам необходимо вставить в нее требуемые функции DDX, по одной на каждый элемент управления, участвующий в обмене. Библиотека MFC предоставляет достаточно большое число таких функций для организации автоматического обмена и проверки корректности данных. Полный их список можно посмотреть в файле <afxdd_.h>. To же самое можно сказать и про функции DDV. Правда, тут есть один маленький нюанс. Вы не можете вызвать функцию проверки корректности введенных пользователем данных (DDV) без вызова соответствующей функции обмена (DDX). Более того, функция DDV должна следовать непосредственно за функцией DDX этого элемента управления. Функции имеют формат:
DDX_XXX(pDX, nIDC, member) -
для обмена данными
и
DDV_XXX(pDX, member, ...) -
для проверки корректности введенных данных
В этих функциях используются следующие параметры: XXX — имя соответствующей функции (например, DDX_Check или DDV_MinMaxInt), pDX — указатель на объект С Data Exchange, nIDC — идентификатор элемента управления, с которым производится обмен данными, и member — член класса, участвующий в обмене.
Данный шаг, связанный с обменом данными в блоках диалога, также достаточно прост, и мы переходим к третьей теме.
Создание блока диалога на основе шаблона в памяти
Мы уже говорили, что в этом случае объект класса создается при помощи функций С Dialog: :InitModal'Indirect или CDialog::CreateIndirect. Однако перед их использованием необходимо проделать некоторую подготовительную работу. Обратимся к рассматриваемому примеру. Как вы помните, для создания шаблона блока диалога в памяти необходимо определить и заполнить структуру DLGTEMPLATE для блока диалога и некоторое количество структур DLGITEMTEMPLATE (по числу используемых элементов управления). Задачу определения структур у нас решают два класса: СМеmоrу и CItem, которые позволяют создать объект "блок диалога" и необходимое число объектов его элементов управления (в данном случае — "статическое поле" и "элемент редактирования"). После того как объекты созданы и проинициализированы, можно создавать соответствующие окна Windows. Эту задачу у нас выполняет функция С Memory:: Show:
Примечание
Создание необходимых классов СМеmorу и CItem я не рассматриваю, поскольку с основой этого процесса мы уже познакомились.
void CMemory::Show()
// При работе с шаблонами блоков диалога в памяти все
// текстовые строки должны быть представлены в Unicode
WCHAR szBoxTitle[] =
L"Шаблон блока диалога в памяти: выход — <ESC>";
WCHAR SzFontNameU = L"Courier New Cyr";
// Прежде всего определяем размер памяти, необходимой
// для размещения шаблона блока диалога, и дополнительной
// памяти под четыре массива: для меню, класса
//и заголовка шрифта
int nBufferSize = sizeof(DLGTEMPIATE) + // структура
(2*sizeof(WORD)) + // меню и/класс
sizeof(szBoxTitle) + // заголовок
sizeof(WORD)+sizeof(szFontName); // шрифт
// Выравнивание по DWORD
nBufferSize = (nBufferSize + 3) & ~3;
// Добавляем размеры структуры DLGITEMTEMPLATE
//и заголовков элементов
for (int i = 0; i < 2; i+-t-)
{
int n!temLength=(sizeof(DLGITEMTEMPLATE) + // структура
// 3 массива для класса, заголовка и данных
// элемента управления
3*sizeof(WORD) +
// размер строки Unicode для заголовка
(m_dlgltem[i].m_strTitle.GetLengthО+1)*sizeof(WCHAR));
// Выравнивание по DWORD
if (i != 1)
nltemLength = (nltemLength +3) & ~3;
nBufferSize +=.nltemLength;
}
// Выделяем память под шаблон
HLOCAL hLocal = LocalAlloc(LHND, nBufferSize);
if (hLocal == NULL)
{
// Обработка ошибки распределения памяти
}
BYTE *pBuffer = (BYTE*)LocalLock(hLocal);
if (pBuffer == NULL)
{
LocalFree(hLocal);
// Обработка ошибки блокировки памяти
}
BYTE *pDest = pBuffer;
// Переписываем структуру DLGTEMPLATE в память
memcpy(pDest, &m_dlgMemory, sizeof(DLGTEMPLATE));
pDest += sizeof(DLGTEMPLATE);
*(WORD*)pDest = 0; // меню, нет
*(WORD*)pDest = 0; // используем класс по умолчанию
pDest += 2*sizeof(WORD);
// Переписываем заголовок
memcpy(pDest, szBoxTitle, sizeof(szBoxTitle));
pDest += sizeof(szBoxTitle);
*(WORD*)pDest = 10; // размер шрифта
pDest += sizeof(WORD);
// Переписываем имя шрифта
memcpy(pDest, szFontName, sizeof(szFontName));
pDest += sizeof(szFontName);
// Переписываем информацию о каждом элементе шаблона
for (i = 0; i < 2; i++)
{
// Выравнивание буфера по DWORD
pDest = (BYTE*)(((DWORD)pDest + 3) & ~3);
memcpy(pDest, (void*)&m_dlgltem[i].m_ctrltem,
sizeof (DLGITEMTEMPLATE) ) ;
pDest += sizeof(DLGITEMTEMPLATE);
*(WORD*)pDest = OxFFFF; // есть идентификатор ресурса
pDest += sizeof(WORD);
*(WORD*)pDest = m_dlgltem[i).m_ctrtype;
pDest += sizeof(WORD);
// Преобразуем заголовок в Unicode строку
WCHAR *pchCaption;
int nChar = m_dlgltem[i].m_strTitle.GetLength() + 1;
pchCaption = new WCHAR[nChar];
// Преобразуем строку ANSI в строку Unicode
nChar = MultiByteToWideChar(
CP_ACP, // кодовая страница ANSI
// предварительно сформированная строка
m_dlgltem[i].m_strTitle, // адрес строки
-1 // автоматическое определение длины
pchCaption, // адрес буфера строки Unicode
nChar); // размер буфера строки Unicode
ASSERT(nChar > 0);
// Переписываем заголовок элемента управления
memcpy(pDest, pchCaption, nChar*sizeof(WCHAR));
pDest += nChar*sizeof (WC'HAR) ; delete pchCaption;
*(WORD*)pDest = 0;
pDest += sizeof(WORD);
}
// Проверка на полноту заполнения буфера
ASSERT(pDest — pBuffer == nBufferSize);
// После заполнения буфера информация в нем хранится
// в следующем виде:
// Структура DLGTEMPLATE .
// 0x0000 (WORD) — индикатор наличия меню
// 0x0000 (WORD) — класс блока диалога по умолчанию
// Заголовок — строка Unicode
// 0x0000 — размер используемого шрифта
//"Courier New Cyr" — имя используемого шрифта
// Структура DLGITEMTEMPLATE для статического текста
// QxFFFF — есть идентификатор элемента управления
// 0x0081 — идентификатор класса Static
// Заголовок — строка Unicode ,
// 0x0000 — дополнительных данных нет
// Структура 'DLGITEMTEMPLATE для редактируемого текста
// OxFFFF — есть идентификатор элемента управления
// 0x0082 — идентификатор класса Edit
// Заголовок — строка Unicode
// 0x0000 — дополнительных данных нет
// Создание объекта "блок диалога"
CDialog .dig;
// Создание окна Windows блока диалога
dig.InitModallndirect((DLGTEMPLATE*)pBuffer);
// Отображение модального блока диалога на экране
dlg.DoModal();
// Освобождение использованной памяти
LocalUnlock(hLocal); LocalFree(hLocal);
}
Рис. 14.10. Блок диалога, созданный функцией CDialog::lnitModallndirect
Функция CMemory::Show вызывается всякий раз, когда пользователь нажимает кнопку "Показать" в главном окне приложения. Параметры, вводимые в полях "X", "Y", "Ширина", "Высота" и "Текст", передаются в соответствующие элементы управления, которые используют их при инициализации. В результате выполнения функции на экран выводится блок диалога, представленный на рис. 14.10.
Понятно, что приведенный пример не претендует на изящество, однако свою роль в иллюстрации рассматриваемых положений он успешно выполняет.
В свое время фирма Microsoft ввела понятие стандартных блоков диалога (Common Dialog Box), применяемых для выполнения тех или иных стандартных действий, таких, например, как открытие файла.
Теперь уже для всех очевидны достоинства такого подхода, ведь использование стандартных блоков диалога означает целостность интерфейса для пользователя и меньший объем работы для программиста. Эти блоки диалога давно уже стали составной частью Windows, и с первых версий библиотеки MFC в нее, естественно, были включены классы, поддерживающие их создание. В настоящее время в библиотеку включены следующие классы: CFileDialog, CColorDialog, CFontDialog, CFindReplaceDialog, CPrintDialog и СPageSetup Dialog (рис. 14.11).
Рис. 14.11. Место стандартных блоков диалога в иерархии классов MFC
Работа с этими класса не представляет особых трудностей, и поэтому здесь я остановлюсь только на одном из них — классе CColorDialog, отвечающим за создание модального блока диалога для выбора и/или создания цветов, которые определены для вашего дисплея.
Для выбора цветов не обязательно создавать новый класс
Класс CColorDialog, как и другие классы стандартных блоков диалога, продуман настолько хорошо, просто и удобно, что вряд ли у кого-нибудь возникнет желание создавать на его основе производный класс. Основным компонентом этого класса, позволяющим осуществлять настройку блока диалога, является структура типа CHOOSECOLOR:
CHOOSECOLOR CColorDialog::m_cc
typedef struct {
DWORD IStructSize;
HWND hwndOwner;
HWND hlnstance;
COLORREF rgbResult;
COLORREF *lpCustColor;
DWORD Flags;
LPARAM ICustData;
LPCCHOOKPROC IpfnHook:;
LPCTSTR IpTemplateName;
} CHOOSECOLOR;
Служит для настройки внешнего вида блока диалога и имеет следующие поля: IStructSize — размер.структуры в байтах; hwndOwner — дескриптор окна, которое владеет блоком диалога, или NULL; hlnstance — идентификатор блока памяти (установлен флаг CC_ENABLETEMPLATEHANDLE) или дескриптор модуля (установлен флаг CCJENABLETEMPLATE), содержащего шаблон блока диалога; если не установлен ни один из этих флагов, то содержимое поля игнорируется. Поле rgbResult при создании блока диалога определяет первоначально выбранный цвет, если при этом в параметре Flags установлен флаг CC_RGBINIT; в противном случае выбирается черный цвет. При завершении работы блока диалога в это поле записывается цвет, выбранный пользователем. Поле IpCustColors — указатель на массив, содержащий 16 значений COLORREF для настраиваемых цветов блока диалога. Если пользователь изменяет значения этих цветов, то система сама модифицирует содержимое массива. Для того чтобы иметь возможность в дальнейшем использовать эти новые значения, массив должен быть объявлен как статический. Поле ICustData определяет данные, которые система посылает функции фильтра IpfnHook. Когда система посылает сообщение WMJNITDIALOG функции фильтра, параметр IParam этого сообщения указывает на структуру CHOOSECOLOR, определенную при создании блока диалога, и может использоваться для доступа к полю ICustData. Поле IpfnHook — указатель на функцию фильтра CCHookProc, которая может обрабатывать сообщения блока диалога. Это поле игнорируется, если не установлен флаг CC_ENABLEHOOK. Поле IpTemplateName — указатель на строку, которая содержит имя ресурса шаблона блока диалога, определенного в модуле hlnstance, подставляемого вместо стандартного шаблона блока диалога. Для числовых идентификаторов ресурса можно использовать макрос MAKEINTRESOURCE. Это поле игнорируется, если не установлен флаг CC_ENABLETEMPLATE. Поле Flags определяет множество битовых флагов, используемых при инициализации блока диалога. Допустимыми являются комбинации следующих флагов:
CC_ENABLEHOOK
Разрешает использовать функцию фильтра, адрес которой указан в поле IpfnHook. Используется только при инициализации блока диалога
CC_ENABLETEMPLATE
Указывает на то, что поле IpTemplateName содержит указатель на имя ресурса шаблона блока диалога в модуле, определяемом полем hlnstance. Используется только при инициализации блока диалога
CC_ENABLETEMPLATEHANDLE
Указывает на то, что поле hlnstance используется для идентификации блока памяти, содержащего предварительно загруженный шаблон блока диалога. В этом случае игнорируется содержимое поля IpTemplateName. Используется только при инициализации блока диалога
CC_FULLOPEN
Предписывает блоку диалога отображать дополнительные элементы управления, необходимые для создания произвольных цветов. Если этот флаг не установлен, то для отображения дополнительных элементов управления пользователь должен использовать кнопку Define Custom Color (Определить цвет)
CC_PREVENTFULLOPEN
Блокирует кнопку Define Custom Color (Определить цвет), т. е. пользователь не может определить произвольный цвет
CC_RGBINIT
Устанавливает цвет, заданный в поле rgbResult, в качестве используемого по умолчанию
FR_SHOWHELP
В блоке диалога отображается кнопка Help (?)
Из функций класса CColorDialog приведу описание наиболее часто используемых:
CColorDialog::CColorDialog (
COLORREF clrlnit =0,
DWORD dwFlags =0,
CWnd *pParentWnd = NULL)
Конструктор объекта "блок диалога". Параметр clrlnit определяет первоначально выбранный цвет в формате RGB (Red — красный, Green — зеленый, Blue — синий).
virtual int CColorDialog::DcModal ()
Выводит на экран модальный стандартный блок диалога Windows для выбора цвета. Если вы хотите самостоятельно настроить параметры блока диалога, устанавливая соответствующие значения в поля структуры т_сс, то делать это следует после создания объекта "блок диалога", но до вызова этой функции. При успешном завершении функция возвращает IDOK или IDCANCEL в зависимости от кнопки, при помощи которой пользователь закрыл блок диалога. В последнем случае можно вызвать функцию Windows CommDlgExtendedError для проверки наличия ошибки.
COLORREF CColorDialog::GetColor ()
Позволяет получить информацию о цвете, выбранном пользователем. Вызывается после того, как завершилась функция CColorDialog::DoModal.
static COLORREF* CColorDialog::GetSavedCustomColors ()
Возвращает указатель на массив, содержащий 16 дополнительных цветов, созданных пользователем. Вызывается после завершения функции CColorDialog:: DoModal. Те цвета из 16, которые пользователь не настраивал, определяют белый цвет.
Приведу также пример настройки и работы со стандартным блоком диалога. Это будет последняя наша встреча с приложением Graph. И здесь мы добавим в него возможность выбирать цвет заливки не только из ограниченного набора контекстного меню, но и из всего диапазона поддерживаемых цветов. Я сознательно опускаю все вопросы, связанные с меню, картой сообщений и созданием соответствующего обработчика — в этом вы уже в состоянии разобраться самостоятельно. Ваше внимание я акцентирую только на содержимом обработчика. Но сначала фрагмент кода:
/////////////////////////////////////////////////////////////
// Файл ChildWnd.h
// Copyright (с) 2000 Тихомиров Ю.
//////////////////////////////////////////////////////////////
void CChildWnd::OnLineColor()
{
// Прежде всего создаем объект класса
CColorDialog clrDlg;
// Размер структуры задавать обязательно, но это очень просто
clrDlg.m_cc.lStructSize = sizeof(clrDlg.m_cc);
// При открытии блока диалога мы хотим,
// чтобы устанавливался текущий цвет линии
clrDlg.m_cc.rgbResult = m_clrLine;
// В качестве дополнительных цветов устанавливаем
// описанные ранее 16 стандартных цветов
clrDlg.m_cc.lpCustColors = colors;
// Если не используются установки по умолчанию, не забывайте
// устанавливать флаг CC_ENABLEHOOK для подключения необходимой
// для обработки функции обратного вызова, даже если вы
//не определяете для этого свою функцию
clrDlg.m_cc.Flags = CC_ENABLEHOOK I
/* Пока закомментируем эти флаги
CC_FULLOPEN | // Выводим расширенный вариант
// блока диалога
CC_RGBINIT; // По умолчанию в блоке диалога
// будет устанавливаться текущий
// цвет линии
*/
// Выводим блок диалога на экран ...
if(clrDlg.DoModal() == IDOK)
{
//и в случае действительного, выбора цвета — закрытие
// блока диалога кнопкой ОК
// Изменяем текущий цвет линии,
// запоминая его значение в специальной переменной
m_clrLine = clrDlg.GetColor();
}
}
Внешний вид рассматриваемого блока диалога представлен на рис. 14.12. При используемых параметрах по умолчанию выбирается черный цвет. Если же при создании использовать флаги CC_FULLOPEN и CC_RGBINIT, то внешний вид существенно изменится (рис. 14.13).
Рис. 14.12. Блок диалога для выбора цвета
Рис. 14.13. Расширенный вариант блока диалога для выбора цвета
Как видите, все довольно просто, и мне осталось только добавить небольшой комментарий. А связан он со строкой
clrDlg.m_cc.lpCustColors = colors;
в которой в качестве дополнительных цветов (Custom colors) мы предлагаем использовать те, которые определили для работы с контекстным меню и которые уже присутствуют в базовой палитре. С одной стороны безразлично, с каких именно дополнительных цветов мы будем начинать работу с приложением. Но на самом деле мы поступили значительно хуже, просто отбросив эти шестнадцать дополнительных цветов. Вы уже догадались, в чем дело? Действительно, предположим, что мы "настроили" нужный нам цвет и записали его в качестве одного из дополнительных, после чего стали с ним работать. Но вот нам захотелось изменить цвет, и мы снова вызываем этот блок диалога. И что же? В качестве дополнительных цветов нам опять предлагаются все те же самые.
Вы согласны, что такое поведение может нас устроить только в простеньком, никому больше не нужном учебном примере? Тогда будем вносить необходимые изменения. И поможет нам функция CColorDialog::GetSavedCustomColors, которая требует предварительного' определения статического массива для дополнительных цветов. Сделаем ее также и глобальной:
....
static COLORREF *aCustom;
....
Теперь нам осталось только воспользоваться названной выше функцией и внести небольшое изменение в приведенный код. Все, что я внес нового, выделено полужирным текстом и, кроме того, убраны приводимые ранее комментарии:
void CChildWnd::OnLineColor()
{
CColorDialog clrDlg;
// Получаем доступ к массиву дополнительных цветов
aCustom = clrDlg.GetSavedCustomColors();
clrDlg.m_cc.IStructSize = sizeof(clrDlg.m_cc);
clrDlg.m_cc.rgbResult = m_clrLine;
clrDlg.m_cc.IpCustColors = aCustom;
clrDlg.m_cc.Flags = CC_ENABLEHOOK ] CC_FULLOPEN [ CC_RGBINIT;
if(clrDlg.DoModal{) == IDOK)
{
m_clrLine = clrDlg.GetColor ();
}
}
Обратите внимание, всего две новые строчки, а результат совершенно другой — дополнительные цвета теперь не теряются (рис. 14.14).
На этом мы заканчиваем знакомство с блоками диалога и переходим к достаточно обширной теме, которой мы уже частично касались. Речь, как вы догадались, идет об элементах управления.
Рис. 14.14. Теперь можно пользоваться и дополнительными цветами без опасения их потерять