Если ваше приложение сложное и имеет большое количество параметров, следует хорошо подумать о том, как пользователь будет их настраивать. Обычно для настройки предлагается многоуровневая система вложенных меню и нагромождение диалоговых панелей, которые вызываются друг из друга, однако это совсем не то, чему обрадуется пользователь.
Если вы посмотрите, как выглядит система настройки параметров, например, в приложении Microsoft Word for Windows версии 6.0, то увидите, что она организована в виде блокнота, состоящего из нескольких диалоговых панелей с закладками (рис. 6.1). По-видимому, это наиболее удачный вариант, так как пользователь может легко найти и выбрать нужную ему группу параметров.
Рис. 6.1. Блокнот настройки параметров в приложении Microsoft Word for Windows версии 6.0
Реализация блокнота средствами программного интерфейса Microsoft Windows версии 3.1 весьма непроста, однако в операционной системе Microsoft Windows 95 имеется встроенное средство, значительно облегчающее создание подобных систем настройки параметров. Это средство - органы управления Tab и Property Sheet .
Орган управления Tab выглядит как набор закладок (верхняя часть рис. 6.1). Его можно использовать отдельно (исходные тексты соответствующего приложения TABCTRL есть в SDK), однако мы ограничимся случаем, который встречается чаще - использованием его в составе органа управления Property Sheet, который мы и будем называть блокнотом.
Орган управления Property Sheet в Microsoft Windows 95 выглядит немного иначе, чем блокнот, показанный на рис. 6.1. Для сравнения вы можете взглянуть на рис. 1.6, расположенный в первой главе, или на рис. 6.2 (см. ниже). Отличие заключается в том, что кнопки управления блокнотом (OK, Cancel, Help и Apply) расположены в нижней части окна блокнота.
Как работают эти кнопки?
Если нажать на кнопку OK, будут установлены новые значения параметров, после чего окно блокнота исчезнет с экрана. Как и следовало ожидать, при помощи кнопки Cancel можно отменить внесение изменений в параметры. Кнопка Help предоставляет доступ к справочной системе.
Кнопка Apply дает возможность изменить параметры и посмотреть, как отразятся эти изменения на приложении. Если нажать на эту кнопку, окно блокнота останется на экране, так что если новая комбинация параметров вас не удовлетворит, вы сможете попробовать другую. При этом вам не придется снова выбирать из меню строку, вызывающую на экран блокнот.
Теперь о волшебстве.
Конечно, наша книга не о колдовстве или магии, однако пользователи любят, когда программы самостоятельно выполняют за них всю работу или хотя бы часть работы (чем не волшебство?). Тем не менее, какая бы "умная" не была ваша программа, все же ей приходится при выполнении задач в полуавтоматическом режиме задавать пользователю вопросы или ставить его перед необходимостью выбора различных альтернатив.
И хотя Microsoft Windows 95 не содержит никаких магических органов управления, способных справится с любой работой, в этой операционной системе все же есть средство, позволяющее легко организовать диалог с пользователем в процессе полуавтоматического выполнения тех или иных задач. Примером может послужить диалог при установке операционной системы Microsoft Windows 95. Это средство - орган управления Wizard (в переводе это означает "колдун", однако мы воздержимся от такой терминологии).
Орган управления Wizard , так же как и блокнот, представляет собой набор диалоговых панелей (рис. 1.7, 6.6, 6.7 и 6.8). Различие заключается в том, что в блокноте пользователь имеет доступ к любой диалоговой панели, выдвигая ее на передний план при помощи закладки, а в органе управления Wizard он передвигается от одной панели к другой и обратно при помощи кнопок Back и Next. При достижении последней диалоговой панели кнопка Next заменяется на кнопку Finish. Стоит на нее нажать и... (о, чудо!) все будет сделано.
Несмотря на указанные различия, органы управления Property Sheet и Wizard очень похожи друг на друга (не по внешнему виду, а по способу создания и управления). Поэтому мы и рассмотрим их в одной главе.
Вначале займемся органом управления Property Sheet.
Орган управления Property Sheet - это системная немодальная диалоговая панель, состоящая из нескольких страниц. Перед тем как продолжить чтение, мы рекомендуем вам обратиться к главе "Диалоговые панели", расположенной в 12 томе "Библиотеки системного программиста", и вспомнить, как работают обычные диалоговые панели.
Для создания органа управления Property Sheet вы должны сделать следующее:
Сразу отметим, что для создания органа управления Wizard необходимо использовать точно такую же процедуру. Отличия заключаются в заполнении структур и обработке сообщений в функциях диалога.
Рассмотрим перечисленные выше шаги подробнее.
Шаблон диалоговой панели готовится обычным способом с помощью интегрированной среды разработки приложений Microsoft Visual C++. Единственное, на что нам хотелось бы обратить внимание: на страницах блокнота и органа управления Wizard нет кнопок OK, Cancel и Help, так как эти кнопки относятся ко всему блокноту и располагаются в нижней части его окна. В остальном шаблоны страниц блокнота ничем не отличаются от шаблонов обычных диалоговых панелей.
Функция диалога страницы блокнота обычно обрабатывает сообщения WM_INITDIALOG , WM_COMMAND и WM_NOTIFY . Первое из них передается функции диалога при его инициализации, второе поступает от органов управления, расположенных на странице блокнота. Сообщение WM_NOTIFY генерируется в том случае, когда пользователь нажимает кнопки OK, Cancel, Help, Apply (а также кнопки Back, Next и Finish в органе управления Wizard) или переходит к просмотру другой страницы блокнота. Ниже мы рассмотрим эти сообщения подробнее.
В отличие от обычной функции диалога, функция диалога для страницы блокнота или органа управления Wizard не вызывает функцию EndDialog , так как это привело бы к уничтожению органа управления. Вот пример функции диалога для страницы блокнота, взятый из приложения Property Sheet Demo, полные исходные тексты которого вы найдете ниже в разделе "Приложение Property Sheet Demo":
BOOL APIENTRY DlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc1_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc1_OnCommand); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc1_OnNotify); default: break; } return FALSE; }
Так же как и в функции главного окна приложений, в функции диалога для организации обработки сообщений мы воспользовались удобной макрокомандой HANDLE_MSG .
Для каждой страницы блокнота или органа управления Wizard вы должны подготовить структуру PROPSHEETPAGE , записав в нее описание страницы. Структура PROPSHEETPAGE определена следующим образом:
typedef struct _PROPSHEETPAGE { DWORD dwSize; // размер структуры DWORD dwFlags; // флаги HINSTANCE hInstance; // идентификатор приложения union { LPCTSTR pszTemplate; // идентификатор ресурса // диалоговой панели LPCDLGTEMPLATE pResource; // шаблон диалоговой панели }; union { HICON hIcon; // идентификатор пиктограммы LPCTSTR pszIcon; // идентификатор ресурса пиктограммы }; LPCTSTR pszTitle; // заголовок страницы DLGPROC pfnDlgProc; // адрес функции диалога LPARAM lParam; // дополнительный параметр LPFNPSPCALLBACK pfnCallback;// адрес функции обратного вызова UINT FAR * pcRefParent;// указатель на счетчик использования } PROPSHEETPAGE, FAR *LPPROPSHEETPAGE; typedef const PROPSHEETPAGE FAR *LPCPROPSHEETPAGE;
Возможно, эта структура выглядит несколько громоздко, однако ее заполнение не вызовет у вас никаких затруднений.
В поле dwSize необходимо записать размер структуры, т. е. значение sizeof(PROPSHEETPAGE).
В поле dwFlags следует записать флаги, отмечающие
задействованные поля структуры и определяющие
внешний вид органа управления. Здесь можно
использовать следующие значения:
Флаг |
Описание |
PSP_DEFAULT |
Используются все поля структуры,
имеющие назначение, принятое по умолчанию |
PSP_DLGINDIRECT |
Поле pResource используется вместо поля
pszTemplate. Этот флаг следует указать в том случае,
если вы создаете шаблон диалоговой панели для
страницы динамически в памяти, а не загружаете
его из ресурсов приложения |
PSP_HASHELP |
Если указан этот флаг, для данной
страницы отображается кнопка Help |
PSP_RTLREADING |
Заголовок, определенный в поле pszTitle,
отображается справа налево. Используется только
в арабских версиях Microsoft Windows 95 |
PSP_USECALLBACK |
При создании или уничтожении страницы
управление получает функция обратного вызова,
адрес которой определен в поле pfnCallback |
PSP_USEHICON |
Поле hIcon содержит идентификатор
пиктограммы уменьшенного размера, которая будет
отображаться в левой части закладки страницы |
PSP_USEICONID |
Поле pszIcon содержит идентификатор
ресурса пиктограммы уменьшенного размера,
которая будет отображаться в левой части
закладки страницы |
PSP_USEREFPARENT |
Используется поле pcRefParent |
PSP_USETITLE |
В качестве заголовка страницы следует
использовать строку, адрес которой задан в поле
pszTitle, а не ту строку, что определена в шаблоне
диалоговой панели |
В нашем приложении мы использовали флаги PSP_USETITLE и PSP_USEICONID.
Поле hInstance должно содержать идентификатор приложения, который передается через соответствующий параметр функции WinMain.
Поля pszTemplate и pResource объединены, поэтому вы можете использовать только одно из них. По умолчанию вы должны записать в поле pszTemplate идентификатор ресурса, содержащего шаблон диалоговой панели. Однако указав флаг PSP_DLGINDIRECT, вы вместо этого можете создать шаблон диалоговой панели динамически в оперативной памяти и записать указатель на этот шаблон в поле pResource.
Аналогичным образом объединены поля hIcon и pszIcon. Если в поле pszIcon вы собираетесь записать идентификатор пиктограммы, которая будет отображаться в закладке, следует указать флаг PSP_USEHICON. Если же пиктограмма определена в ресурсах приложения, вы должны записать соответствующий идентификатор ресурса в поле pszIcon и указать флаг PSP_USEICONID.
В том случае, когда вам не нужно отображать пиктограмму на закладке, не указывайте флаги PSP_USEHICON и PSP_USEICONID, а в поле pszIcon (или hIcon, что одно и то же) запишите значение NULL.
При создании шаблона диалоговой панели вы можете указать ее заголовок. Однако вместо этого можно использовать поле pszTitle и флаг PSP_USEICONID.
В поле pfnDlgProc необходимо записать адрес функции диалога, подготовленной с учетом приведенных выше замечаний.
Вот как мы заполнили поля структуры PROPSHEETPAGE для трех страниц блокнота в приложении Property Sheet Demo:
PROPSHEETPAGE psheetPage[3]; psheetPage[0].dwSize = sizeof(PROPSHEETPAGE); psheetPage[0].hInstance = hInst; psheetPage[0].pszIcon = MAKEINTRESOURCE(IDI_EFFECTS); psheetPage[0].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[0].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG1); psheetPage[0].pfnDlgProc = DlgProc1; psheetPage[0].pszTitle = "Set Effects"; psheetPage[0].lParam = 0; psheetPage[1].dwSize = sizeof(PROPSHEETPAGE); psheetPage[1].hInstance = hInst; psheetPage[1].pszIcon = MAKEINTRESOURCE(IDI_TAB); psheetPage[1].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[1].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG2); psheetPage[1].pfnDlgProc = DlgProc2; psheetPage[1].pszTitle = "Using Tabs"; psheetPage[1].lParam = 0; psheetPage[2].dwSize = sizeof(PROPSHEETPAGE); psheetPage[2].hInstance = hInst; psheetPage[2].pszIcon = MAKEINTRESOURCE(IDI_KEYWORD); psheetPage[2].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[2].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG3); psheetPage[2].pfnDlgProc = DlgProc3; psheetPage[2].pszTitle = "Keyword"; psheetPage[2].lParam = 0;
Структура PROPSHEETHEADER , как это видно из ее названия, описывает заголовок блокнота. Она имеет следующий формат:
typedef struct _PROPSHEETHEADER { DWORD dwSize; // размер структуры DWORD dwFlags; // флаги HWND hwndParent; // идентификатор родительского окна HINSTANCE hInstance; // идентификатор приложения union { HICON hIcon; // идентификатор пиктограммы LPCTSTR pszIcon; // идентификатор ресурса пиктограммы }; LPCTSTR pszCaption; // заголовок блокнота UINT nPages; // количество страниц в блокноте union { UINT nStartPage; // номер первой страницы LPCTSTR pStartPage; // имя первой страницы }; union { LPCPROPSHEETPAGE ppsp; // адрес массива структур // PROPSHEETPAGE HPROPSHEETPAGE FAR *phpage; // адрес массива }; // идентификаторов страниц блокнота PFNPROPSHEETCALLBACK pfnCallback; // адрес функции // обратного вызова } PROPSHEETHEADER, FAR *LPPROPSHEETHEADER; typedef const PROPSHEETHEADER FAR *LPCPROPSHEETHEADER;
В поле dwSize нужно записать размер структуры.
Поле dwFlags может содержать логическую
комбинацию следующих значений:
Флаг |
Описание |
PSH_DEFAULT |
Используются все поля структуры,
имеющие назначение, принятое по умолчанию |
PSH_MULTILINETABS |
Закладки страниц могут располагаться в
несколько рядов |
PSH_NOAPPLYNOW |
Не отображается кнопка Apply |
PSH_PROPSHEETPAGE |
При создании органа управления вместо
поля phpage используется поле ppsp |
PSH_PROPTITLE |
К заголовку, определенному в поле pszCaption,
добавляется строка Properties for |
PSH_USECALLBACK |
При инициализации органа управления
используется функция обратного вызова, адрес
которой указан в поле pfnCallback |
PSH_USEHICON |
Поле hIcon содержит идентификатор
пиктограммы уменьшенного размера, которая
отображается в заголовке блокнота. Эта
пиктограмма не нужна для органа управления Wizard |
PSH_USEICONID |
Поле pszIcon содержит идентификатор htcehcf
пиктограммы уменьшенного размера, которая
отображается в заголовке блокнота. Эта
пиктограмма не нужна для органа управления Wizard |
PSH_USEPSTARTPAGE |
Вместо поля nStartPage используется поле
pStartPage |
PSH_WIZARD |
Создается орган управления Wizard, а не
блокнот Property Sheet |
В приложении Property Sheet Demo мы заполнили структуру PROPSHEETHEADER следующим образом:
PROPSHEETHEADER psheetHeader; psheetHeader.dwSize = sizeof(PROPSHEETHEADER); psheetHeader.hInstance = hInst; psheetHeader.pszIcon = MAKEINTRESOURCE(IDI_APPICONSM); psheetHeader.dwFlags = PSH_USEICONID; psheetHeader.hwndParent = hWnd; psheetHeader.pszCaption = "Property Sheet Sample"; psheetHeader.nPages = sizeof(psheetPage) / sizeof(PROPSHEETPAGE); psheetHeader.phpage = (HPROPSHEETPAGE FAR *)&hPage[0];
Обратите внимание на поле phpage. В него мы записали адрес массива идентификаторов страниц HPROPSHEETPAGE. Этот массив заполняется функцией CreatePropertySheetPage, которой в качестве параметра передается адрес предварительно заполненной структуры PROPSHEETPAGE:
HPROPSHEETPAGE hPage[3]; hPage[0] = CreatePropertySheetPage(&psheetPage[0]); hPage[1] = CreatePropertySheetPage(&psheetPage[1]); hPage[2] = CreatePropertySheetPage(&psheetPage[2]);
После заполнения заголовка блокнота можно создать блокнот (или орган управления Wizard, если указан флаг PSH_WIZARD ) при помощи функции PropertySheet :
PropertySheet(&psheetHeader);
Есть еще один способ создания блокнота или органа управления Wizard без использования функции CreatePropertySheetPage. Мы применили этот способ в приложении Wizard Demo:
psheetHeader.dwSize = sizeof(PROPSHEETHEADER); psheetHeader.hInstance = hInst; psheetHeader.pszIcon = NULL; psheetHeader.dwFlags = PSH_PROPSHEETPAGE | PSH_WIZARD; psheetHeader.hwndParent = hWnd; psheetHeader.pszCaption = "Property Sheet Sample"; psheetHeader.nPages = sizeof(psheetPage) / sizeof(PROPSHEETPAGE); psheetHeader.ppsp = (LPCPROPSHEETPAGE)&psheetPage; PropertySheet(&psheetHeader);
Здесь мы не используем пиктограмму, так как она все равно не отображается, поэтому в поле pszIcon записано значение NULL. Кроме того, указаны флаги PSH_PROPSHEETPAGE и PSH_WIZARD.
Так как указан флаг PSH_PROPSHEETPAGE, то в поле ppsp мы записываем адрес массива структур PROPSHEETPAGE, описывающих отдельные страницы органа управления Wizard. При этом нам не нужен массив идентификаторов страниц и, соответственно, функция CreatePropertySheetPage, которая его заполняет.
Когда пользователь нажимает кнопки OK, Cancel или Help, расположенные в нижней части окна блокнота или кнопки Back, Next или Finish, расположенные в нижней части окна органа управления Wizard, функция диалога текущей страницы получает сообщение WM_NOTIFY . Код извещения передается через поле code структуры NMHDR , адрес которой находится в параметре lParam сообщения WM_NOTIFY.
Для органов управления Propery Sheet и Wizard
определены следующие коды извещений:
Код извещения |
Описание |
PSN_SETACTIVE |
Это извещение передается функции
диалога, когда активизируется соответствующая
страница блокнота или органа управления Wizard.
Обработчик извещения может выполнить все
необходимые инициализирующие действия |
PSN_KILLACTIVE |
Страница отодвигается на задний план
или удаляется с экрана. Обработчик этого
извещения может проверить введенные
пользователем значения и если они неправильны,
заблокировать переключение на другие страницы
блокнота |
PSN_APPLY |
Это извещение посылается когда
пользователь нажимает кнопку OK или Apply |
PSN_HELP |
Извещение PSN_HELP посылается когда
пользователь нажимает кнопку Help |
PSN_QUERYCANCEL |
Извещение PSN_QUERYCANCEL посылается когда
пользователь нажимает кнопку Cancel, собираясь
закрыть блокнот. Обработчик может запретить это
действие, например, если пользователь не указал
какие-либо необходимые параметры |
PSN_RESET |
Это извещение посылается вслед за
извещением PSN_QUERYCANCEL, если обработчик последнего
не запретил закрытие блокнота |
PSN_WIZBACK |
Посылается, когда пользователь нажал
кнопку Back в органе управления Wizard |
PSN_WIZNEXT |
Посылается, когда пользователь нажал
кнопку Next в органе управления Wizard |
PSN_WIZFINISH |
Посылается, когда пользователь нажал
кнопку Finish в органе управления Wizard |
Обработчики извещений могут возвращать значение FALSE или TRUE, однако в большинстве случаев дополнительно необходимо устанавливать код завершения в структуре данных диалога с помощью функции SetWindowLong. При этом в качестве второго параметра для этой функции следует указать значение DWL_MSGRESULT.
В следующем фрагменте кода, который взят из приложения "Property Sheet Demo", проверяется длина строки szTempBuf. Если она больше 8, возвращается значение TRUE, а если меньше - FALSE:
if(lstrlen(szTempBuf) > 8) { SetWindowLong(hdlg, DWL_MSGRESULT, TRUE); return TRUE; } else { SetWindowLong(hdlg, DWL_MSGRESULT, FALSE); return FALSE; }
Более подробную информацию об использовании этих извещений вы сможете найти в справочной системе SDK. Самые нужные из них будут описаны в разделах "Приложение Property Sheet Demo" и "Wizard Demo".
Обработчики сообщений, расположенные в функциях диалога, могут посылать органам управления Property Sheet и Wizard различные сообщения, добавляя или удаляя отдельные страницы, изменяя состояние кнопок, таких как Apply или Next, и т. д.
Ниже мы привели список таких сообщений с
кратким описанием.
Сообщение |
Описание |
PSM_ADDPAGE |
Добавление новой страницы в конец
блокнота |
PSM_APPLY |
Действие этого сообщения эквивалентно
действию кнопки Apply. При этом текущая страница
получит извещение с кодом PSN_KILLACTIVE |
PSM_CANCELTOCLOSE |
Если послать это сообщение, вместо
кнопки Cancel появится кнопка Close. Используется в
тех случаях, когда пользователь выполнил такие
изменения, от которых уже нельзя отказаться |
PSM_CHANGED |
Это сообщение разблокирует кнопку Apply |
PSM_GETTABCONTROL |
С помощью этого сообщения можно узнать
идентификатор органа управления Tab (в нашей книге
не описан), который используется для создания
закладок в блокноте |
PSM_PRESSBUTTON |
С помощью этого сообщения можно
симулировать действие кнопок управления
блокнотом или органом управления Wizard, таких как,
например, OK, Next или Finish |
PSM_QUERYSIBLINGS |
Передача сообщения функциям диалога
всех страниц в блокноте |
PSM_REMOVEPAGE |
Удаление страницы из блокнота |
PSM_REBOOTSYSTEM |
Это сообщение посылается при обработке
извещений PSN_APPLY или PSN_KILLACTIVE, если для
использования новых параметров необходимо
выполнить перезапуск системы. При этом функция
PropertySheet возвратит значение ID_PSREBOOTSYSTEM. Перезапуск
системы приложение может выполнить при помощи
функции ExitWindowsEx |
PSM_RESTARTWINDOWS |
Аналогично предыдущему, но используется
когда нужно перезапустить Microsoft Windows 95 без
полного перезапуска системы |
PSM_SETCURSEL |
Активизация указанной страницы
блокнота по номеру или идентификатору |
PSM_SETCURSELID |
Активизация указанной страницы
блокнота по ее идентификатору в ресурсах
приложения |
PSM_SETFINISHTEXT |
Установка нового текста для кнопки Finish с
блокированием кнопок Back и Next |
PSM_SETTITLE |
Установка заголовка блокнота Property Sheet |
PSM_SETWIZBUTTONS |
Блокирование или разблокирование
кнопок Back, Next и Finish в органе управления Wizard |
PSM_UNCHANGED |
Это сообщение блокирует кнопку Apply |
Самые важные из этих сообщений будут рассмотрены ниже при описании исходных текстов приложений Property Sheet Demo и Wizard Demo. Исчерпывающую информацию об использовании этих сообщений вы найдете, конечно, в справочной системе SDK.
Приложение Property Sheet Demo демонстрирует способ создания простейшего блокнота, который появляется при выборе строки Options из меню File.
С помощью блокнота заполняется произвольно выбранная нами структура параметров, приведенная ниже:
typedef struct { int nBold; int nItalic; int nUnderline; int nUseTabs; char szKeyWord[80]; } OPTIONS;
Первая страница блокнота, которая называется Set Effects, показана на рис. 6.2.
Рис. 6.2. Первая страница блокнота
На ней расположены переключатели с независимой фиксацией Bold, Italic и Underline. Если изменить состояние одного из этих переключателей, разблокируется кнопка Apply (которая изначально находится в заблокированном состоянии).
На второй странице блокнота с названием Using Tabs (рис. 6.3) находятся два переключателя с зависимой фиксацией Use Tabs и Don't use Tabs.
Рис. 6.3. Вторая страница блокнота
Кнопка Apply разблокируется автоматически при выборе страницы Using Tabs, что достигается соответствующей обработкой извещения PSN_SETACTIVE.
С помощью страницы Keyword (рис. 6.4) пользователь может ввести некоторое ключевое слово, длина которого не должна превышать 8 символов.
Рис. 6.4. Третья страница блокнота
При выборе этой страницы кнопка Apply разблокируется только в том случае, когда ключевое слово было изменено.
Если длина нового ключевого слова превышает 8 символов, на экране появляется сообщение, показанное на рис. 6.5.
Рис. 6.5.Сообщение о превышении длины ключевого слова
При этом вы не сможете переключиться на другие страницы блокнота до тех пор, пока не будет введено ключевое слово нужной длины.
Функции приложения Property Sheet Demo определены в файле psheet.c (листинг 6.1).
Листинг 6.1. Файл psheet\psheet.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "psheet.h" // Структура, в которой хранятся параметры typedef struct { int nBold; int nItalic; int nUnderline; int nUseTabs; char szKeyWord[80]; } OPTIONS; OPTIONS opt; // Массив описаний страниц блокнота PROPSHEETPAGE psheetPage[3]; // Заголовок блокнота PROPSHEETHEADER psheetHeader; // Идентификаторы страниц блокнота HPROPSHEETPAGE hPage[3]; HINSTANCE hInst; char szAppName[] = "PropSheetApp"; char szAppTitle[] = "Property Sheet Demo"; // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { // Инициализируем библиотеку стандартных органов управления InitCommonControls(); // Инициализируем параметры opt.nBold = opt.nItalic = opt.nUnderline = opt.nUseTabs = 0; strcpy(opt.szKeyWord, "Undef"); return TRUE; } // ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case ID_FILE_EXIT: PostQuitMessage(0); // завершаем работу приложения return 0L; break; case ID_HELP_ABOUT: MessageBox(hWnd, "Property Sheet Demo Application, v.1.0\n" "(C) Alexandr Frolov, 1995\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break; case ID_FILE_OPTIONS: { // Инициализируем страницы блокнота psheetPage[0].dwSize = sizeof(PROPSHEETPAGE); psheetPage[0].hInstance = hInst; psheetPage[0].pszIcon = MAKEINTRESOURCE(IDI_EFFECTS); psheetPage[0].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[0].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG1); psheetPage[0].pfnDlgProc = DlgProc1; psheetPage[0].pszTitle = "Set Effects"; psheetPage[0].lParam = 0; // Добавляем страницу в блокнот, сохраняя ее // идентификатор в массиве hPage hPage[0] = CreatePropertySheetPage(&psheetPage[0]); psheetPage[1].dwSize = sizeof(PROPSHEETPAGE); psheetPage[1].hInstance = hInst; psheetPage[1].pszIcon = MAKEINTRESOURCE(IDI_TAB); psheetPage[1].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[1].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG2); psheetPage[1].pfnDlgProc = DlgProc2; psheetPage[1].pszTitle = "Using Tabs"; psheetPage[1].lParam = 0; hPage[1] = CreatePropertySheetPage(&psheetPage[1]); psheetPage[2].dwSize = sizeof(PROPSHEETPAGE); psheetPage[2].hInstance = hInst; psheetPage[2].pszIcon = MAKEINTRESOURCE(IDI_KEYWORD); psheetPage[2].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[2].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG3); psheetPage[2].pfnDlgProc = DlgProc3; psheetPage[2].pszTitle = "Keyword"; psheetPage[2].lParam = 0; hPage[2] = CreatePropertySheetPage(&psheetPage[2]); // Инициализируем заголовок блокнота psheetHeader.dwSize = sizeof(PROPSHEETHEADER); psheetHeader.hInstance = hInst; psheetHeader.pszIcon = MAKEINTRESOURCE(IDI_APPICONSM); psheetHeader.dwFlags = PSH_USEICONID; psheetHeader.hwndParent = hWnd; psheetHeader.pszCaption = "Property Sheet Sample"; psheetHeader.nPages = sizeof(psheetPage) / sizeof(PROPSHEETPAGE); psheetHeader.phpage = (HPROPSHEETPAGE FAR *)&hPage[0]; // Создаем и отображаем блокнот PropertySheet(&psheetHeader); return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ----------------------------------------------------- // Функция DlgProc1 // для первой страницы блокнота // ----------------------------------------------------- BOOL APIENTRY DlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc1_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc1_OnCommand); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc1_OnNotify); default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc1_OnInitDialog // Вызывается при инициализации первой страницы блокнота // ----------------------------------------------------- BOOL DlgProc1_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // Устанавливаем переключатели в соответствии // со значениями параметров, записанных в // структуре opt SendMessage(GetDlgItem(hdlg, IDC_BOLD), BM_SETCHECK, opt.nBold, 0L); SendMessage(GetDlgItem(hdlg, IDC_ITALIC), BM_SETCHECK, opt.nItalic, 0L); SendMessage(GetDlgItem(hdlg, IDC_UNDERLINE), BM_SETCHECK, opt.nUnderline, 0L); return TRUE; } // ----------------------------------------------------- // Функция DlgProc1_OnNotify // Обрабатывает извещение от первой страницы блокнота // ----------------------------------------------------- LRESULT DlgProc1_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { // Когда пользователь нажимает кнопку OK, получаем // состояние переключателей, расположенных в первой // странице блокнота, и сохраняем это состояние в // соответствующих полях структуры opt case PSN_APPLY: { opt.nBold = (int)SendMessage( GetDlgItem(hdlg, IDC_BOLD), BM_GETCHECK, 0L, 0L); opt.nItalic = (int)SendMessage( GetDlgItem(hdlg, IDC_ITALIC), BM_GETCHECK, 0L, 0L); opt.nUnderline = (int)SendMessage( GetDlgItem(hdlg, IDC_UNDERLINE), BM_GETCHECK, 0L, 0L); break; } default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc1_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc1_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { // Разблокируем кнопку "Apply" PropSheet_Changed(GetParent(hdlg), hdlg); return NULL; } // ----------------------------------------------------- // Функция DlgProc2 // для второй страницы блокнота // ----------------------------------------------------- BOOL APIENTRY DlgProc2(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc2_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc2_OnNotify); default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc2_OnInitDialog // Вызывается при инициализации второй страницы блокнота // ----------------------------------------------------- BOOL DlgProc2_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { SendMessage(GetDlgItem(hdlg, IDC_USETABS), BM_SETCHECK, opt.nUseTabs, 0L); SendMessage(GetDlgItem(hdlg, IDC_DONTUSETABS), BM_SETCHECK, (opt.nUseTabs) ? 0 : 1, 0L); return TRUE; } // ----------------------------------------------------- // Функция DlgProc2_OnNotify // Обрабатывает извещение от второй страницы блокнота // ----------------------------------------------------- LRESULT DlgProc2_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { case PSN_SETACTIVE: { // Разблокируем кнопку Apply, когда страница // блокнота становится активной PropSheet_Changed(GetParent(hdlg), hdlg); break; } case PSN_KILLACTIVE: { // Блокируем кнопку Apply, когда страница // блокнота становится неактивной PropSheet_UnChanged(GetParent(hdlg), hdlg); break; } case PSN_APPLY: { opt.nUseTabs = (int)SendMessage( GetDlgItem(hdlg, IDC_USETABS), BM_GETCHECK, 0L, 0L); break; } default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc3 // для третьей страницы блокнота // ----------------------------------------------------- BOOL APIENTRY DlgProc3(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc3_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc3_OnNotify); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc3_OnCommand); default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc3_OnInitDialog // Вызывается при инициализации третьей страницы блокнота // ----------------------------------------------------- BOOL DlgProc3_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { SetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), opt.szKeyWord); return TRUE; } // ----------------------------------------------------- // Функция DlgProc3_OnNotify // Обрабатывает извещение от третьей страницы блокнота // ----------------------------------------------------- LRESULT DlgProc3_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { // Выполняем проверку длины ключевого слова, // которое не должно быть длиннее 8 символов case PSN_KILLACTIVE: { char szTempBuf[80]; // Получаем новое ключевое слово во временный буфер GetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), szTempBuf, 80); // Проверяем его длину if(lstrlen(szTempBuf) > 8) { // Если длина больше 8 символов, выводим // предупреждающее сообщение MessageBox(NULL, "Too long keyword, " "must be shorter than 8 characters", szAppName, MB_OK | MB_ICONEXCLAMATION); // Отменяем закрытие страницы блокнота SetWindowLong(hdlg, DWL_MSGRESULT, TRUE); // Отменяем обновление параметров return TRUE; } else { // Если длина сообщения правильная, разрешаем // закрытие страницы блокнота и // обновление параметров SetWindowLong(hdlg, DWL_MSGRESULT, FALSE); return FALSE; } break; } case PSN_APPLY: { GetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), opt.szKeyWord, 80); break; } default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc3_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc3_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { // Сообщение от редактора текста if(id == IDC_EDITKEYWORD) { // Если пользователь изменил ключевое слово в окне // редактирования, разблокируем кнопку "Apply" if(codeNotify == EN_CHANGE) { PropSheet_Changed(GetParent(hdlg), hdlg); } } return FALSE; }
В файле psheet.h (листинг 6.2) находятся описания функций, определенных в приложении Property Sheet Demo.
Листинг 6.2. Файл psheet\psheet.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); BOOL APIENTRY DlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc1_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc1_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); void DlgProc1_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); BOOL APIENTRY DlgProc2(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc2_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc2_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); BOOL APIENTRY DlgProc3(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc3_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc3_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); void DlgProc3_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify);
В файле resource.h (создается автоматически системой Microsoft Visual C++) находятся определения констант для работы с ресурсами приложения Property Sheet Demo. Этот файл представлен в листинге 6.3.
Листинг 6.3. Файл psheet\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by PSHEET.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDI_EFFECTS 105 #define IDI_TAB 106 #define IDI_KEYWORD 107 #define IDD_DIALOG1 121 #define IDD_DIALOG2 122 #define IDD_DIALOG3 123 #define IDC_BOLD 1000 #define IDC_ITALIC 1001 #define IDC_UNDERLINE 1002 #define IDC_USETABS 1004 #define IDC_EDITKEYWORD 1006 #define IDC_DONTUSETABS 1007 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_FILE_OPTIONS 40029 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 125 #define _APS_NEXT_COMMAND_VALUE 40030 #define _APS_NEXT_CONTROL_VALUE 1008 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл psheet.rc (листинг 6.4) содержит определение ресурсов приложения Property Sheet Demo. Он создается автоматически.
Листинг 6.4. Файл psheet\psheet.rc
//Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Options...", ID_FILE_OPTIONS MENUITEM SEPARATOR MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END ////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Icon // IDI_APPICON ICON DISCARDABLE "psheet.ico" IDI_APPICONSM ICON DISCARDABLE "psheetsm.ico" IDI_EFFECTS ICON DISCARDABLE "EFFECTS.ICO" IDI_TAB ICON DISCARDABLE "TAB.ICO" IDI_KEYWORD ICON DISCARDABLE "KEYWORD.ICO" ////////////////////////////////////////////////////////////// // Dialog // IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN CONTROL "Bold",IDC_BOLD,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,17,23,35,10 CONTROL "Italic",IDC_ITALIC,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,17,34,35,10 CONTROL "Underline",IDC_UNDERLINE,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,17,45,46,10 GROUPBOX "Effects",IDC_STATIC,6,9,112,53 END IDD_DIALOG2 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN CONTROL "Use Tabs", IDC_USETABS, "Button", BS_AUTORADIOBUTTON,18,22, 63,10 GROUPBOX "Tabs",IDC_STATIC,9,7,105,63 CONTROL "Don't use Tabs",IDC_DONTUSETABS,"Button", BS_AUTORADIOBUTTON,18,35,83,10 END IDD_DIALOG3 DIALOG DISCARDABLE 0, 0, 211, 102 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_EDITKEYWORD,22,35,138,13,ES_AUTOHSCROLL GROUPBOX "Keyword",IDC_STATIC,9,15,166,52 END ////////////////////////////////////////////////////////////// // String Table // STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Займемся описанием глобальных переменных и функций приложения Property Sheet Demo.
Структура opt, имеющая тип OPTIONS, содержит параметры, которые настраиваются с помощью блокнота. Тип OPTIONS определен в нашем приложении.
Массив структур psheetPage типа PROPSHEETPAGE хранит описания страниц блокнота. Заголовок блокнота записан в структуре psheetHeader типа PROPSHEETHEADER.
Идентификаторы отдельных страниц, создаваемых функцией CreatePropertySheetPage, записываются в массив структур hPage типа HPROPSHEETPAGE.
В переменной hInst хранится идентификатор приложения, полученный функцией WinMain. Строчные массивы szAppName и szAppTitle хранят, соответственно, имя и заголовок приложения.
Функция WinMain не имеет никаких особенностей, поэтому для экономии места в книге мы не будем ее описывать.
Функция WndProc обрабатывает следующие сообщения: WM_CREATE, WM_DESTROY и WM_COMMAND. Обработка выполняется с использованием макрокоманды HANDLE_MSG.
Обработчик сообщения WM_CREATE инициализирует библиотеку стандартных органов управления, вызывая для этого функцию InitCommonControls.
Затем он выполняет начальную инициализацию полей структуры параметров opt.
Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.
Обработчики сообщений от строк Exit и About действуют также, как и в предыдущем приложении.
Когда пользователь выбирает из меню File строку Options, соответствующий обработчик создает блокнот и отображает его на экране. Процедура создания и отображения блокнота заключается в заполнении массива структур psheetPage, содержащих описания страниц, массива psheetHeader с описанием заголовка блокнота. Каждая страница добавляется в блокнот отдельно функцией CreatePropertySheetPage, причем идентификатор созданное страницы записывается в соответствующий элемент массива hPage.
Блокнот создается и отображается функцией PropertySheet, которой в качестве единственного параметра передается адрес заполненной структуры заголовка блокнота.
Функция DlgProc1 - это функция диалога для первой страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG, WM_COMMAND и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc1_OnInitDialog, DlgProc1_OnCommand и DlgProc1_OnNotify.
Заметим, что ни сама функция диалога, ни одна из только что перечисленных функций обработки сообщений не вызывает функцию EndDialog, так как в противном случае блокнот будет уничтожен.
Функция DlgProc1_OnInitDialog обрабатывает сообщение WM_INITDIALOG, которое поступает в функцию диалога первой страницы блокнота при инициализации последней.
Единственное, что она делает, это устанавливает переключатели, расположенные на первой странице, в соответствии с содержимым полей структуры параметров opt:
SendMessage(GetDlgItem (hdlg, IDC_BOLD), BM_SETCHECK, opt.nBold, 0L);
Метод установки состояния переключателей в диалоговой панели основан на использовании сообщения BM_SETCHECK. Это сообщение, а также функция GetDlgItem, возвращающая идентификатор окна органа управления, расположенного в диалоговой панели, были подробно описаны в 12 томе "Библиотеки системного программиста".
Функция DlgProc1_OnNotify обрабатывает извещения, которые поступают в функцию диалога первой страницы в форме сообщения WM_NOTIFY.
В ответ на извещение PSN_APPLY функция DlgProc1_OnNotify получает состояние переключателей и записывает его в соответствующие поля структуры opt:
opt.nBold = (int)SendMessage(GetDlgItem(hdlg, IDC_BOLD), BM_GETCHECK, 0L, 0L);
Для определения состояния переключателей им посылается сообщение BM_GETCHECK.
Функция DlgProc1_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от переключателей, расположенных на первой странице.
Как только пользователь нажмет какую-либо из имеющихся на первой странице блокнота кнопку, функция DlgProc1_OnCommand разблокирует кнопку Apply, посылая блокноту сообщение PSM_CHANGED:
PropSheet_Changed(GetParent(hdlg), hdlg);
Сообщение посылается макрокомандой PropSheet_Changed. В качестве первого параметра этой макрокоманды (как и других макрокоманд, предназначенных для посылки сообщений блокноту) необходимо указать идентификатор окна блокнота. Окно блокнота является родительским по отношению к окнам страниц, поэтому мы воспользовались функцией GetParent . В качестве второго параметра необходимо указать идентификатор диалога, который передается в функцию диалога.
Функция DlgProc2 является функцией диалога для второй страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc2_OnInitDialog и DlgProc2_OnNotify.
Функция DlgProc2_OnInitDialog обрабатывает сообщение WM_INITDIALOG, которое поступает в функцию диалога второй страницы блокнота при ее инициализации.
Ее задача аналогична задаче функции DlgProc1_OnInitDialog - установка переключателей, расположенных на второй странице в соответствии с содержимым полей структуры параметров opt.
Переключатель с идентификатором IDC_DONTUSETABS всегда устанавливается в состояние, противоположное состоянию переключателя IDC_USETABS.
Функция DlgProc2_OnNotify обрабатывает извещения, которые поступают в функцию диалога второй страницы.
Когда пользователь переключается на вторую страницу, делая ее активной, функция диалога получает извещение PSN_SETACTIVE. Обработчик этого извещения разблокирует кнопку Apply, вызывая для этого макрокоманду PropSheet_Changed.
Извещение PSN_KILLACTIVE поступает в функцию диалога, когда пользователь завершил работу с текущей страницей блокнота и пытается переключиться на другую страницу. При этом кнопка Apply блокируется при помощи макрокоманды PropSheet_UnChanged :
PropSheet_UnChanged(GetParent(hdlg), hdlg);
В ответ на извещение PSN_APPLY функция DlgProc2_OnNotify получает состояние переключателя IDC_USETABS и записывает его в поле nUseTabs структуры opt.
Функция DlgProc3 является функцией диалога третьей страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG, WM_COMMAND и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc3_OnInitDialog, DlgProc3_OnCommand и DlgProc3_OnNotify.
Функция DlgProc3_OnInitDialog устанавливает содержимое текстового редактора IDC_EDITKEYWORD, записывая в него строку из поля opt.szKeyWord.
Функция DlgProc3_OnNotify обрабатывает извещения, которые поступают в функцию диалога второй страницы.
Если пользователь изменил текст в окне редактора IDC_EDITKEYWORD, обработчик сообщения WM_COMMAND (будет описан ниже) разблокирует кнопку Apply. Если теперь пользователь попытается нажать эту кнопку или кнопку OK, либо попытается переключиться на другую страницу блокнота, функция диалога третьей страницы получит извещение PSN_KILLACTIVE.
Обработчик этого извещения записывает содержимое текстового редактора во временный буфер szTempBuf и с помощью функции lstrlen определяет его длину. Если длина больше 8 символов, на экран выводится предупреждающее сообщение, после чего выполняется блокирование страницы. Теперь пользователь будет вынужден ввести ключевое слово правильной длины, иначе он не сможет переключиться на другую страницу или завершить работу с блокнотом.
Для блокирования обработчик извещения PSN_KILLACTIVE возвращает значение TRUE, установив предварительно код завершения TRUE в структуре окна диалога с помощью функции SetWindowLong :
SetWindowLong(hdlg, DWL_MSGRESULT, TRUE); return TRUE;
Если же длина полученной текстовой строки меньше 8 символов, обработчик извещения PSN_KILLACTIVE возвращает значение FALSE:
SetWindowLong(hdlg, DWL_MSGRESULT, FALSE); return FALSE;
В результате функция диалога третьей и остальных страниц получает извещение PSN_APPLY. При этом обработчик извещения PSN_APPLY третьей страницы сохраняет новое ключевое слово в поле opt.szKeyWord.
Кнопка Apply, расположенная на третьей странице блокнота, разблокируется только в том случае, когда пользователь изменил ключевое слово. Для этого обработчик сообщения WM_COMMAND, реализованный функцией DlgProc3_OnCommand, отслеживает извещение EN_CHANGE. Это извещение поступает от окна текстового редактора в форме сообщения WM_COMMAND в том случае, когда пользователь изменил редактируемый текст (см. том 12 "Библиотеки системного программиста").
Разблокировка кнопки Apply выполняется при помощи макрокоманды PropSheet_Changed.
Немного изменив приложение Property Sheet Demo, мы превратили его в приложение Wizard Demo. Сравнив исходные тексты, попробуйте сами найти отличия.
Страницы блокнота теперь отображаются последовательно.
На рис. 6.6 показано первая страница органа управления Wizard.
Рис. 6.6. Первая страница органа управления Wizard в приложении Wizard Demo
Кнопка Back для этой страницы отображается всегда в заблокированном состоянии, так как эта страница первая. Для перехода ко второй странице (рис. 6.7) вы должны нажать кнопку Next.
Рис. 6.7. Вторая страница органа управления Wizard в приложении Wizard Demo
На второй странице кнопки Back и Next разблокированы, так что вы можете либо вернуться к первой странице, либо перейти к третьей (рис. 6.8).
Рис. 6.8. Третья страница органа управления Wizard в приложении Wizard Demo
Третья страница - последняя, поэтому вместо кнопки Next на ней отображается кнопка Finish. Если нажать на кнопку Finish, на экране появится диалоговая панель, отображающая установленные параметры (рис. 6.9).
Рис. 6.9. Отображение установленных параметров
Все функции приложения Wizard Demo определены в файле wizdemo.c (листинг 6.5).
Листинг 6.5. Файл wizdemo\wizdemo.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "wizdemo.h" // Структура, в которой хранятся параметры typedef struct { int nBold; int nItalic; int nUnderline; int nUseTabs; char szKeyWord[80]; } OPTIONS; OPTIONS opt; // Массив описаний страниц блокнота Wizard PROPSHEETPAGE psheetPage[3]; // Заголовок блокнота Wizard PROPSHEETHEADER psheetHeader; HINSTANCE hInst; char szAppName[] = "WizardApp"; char szAppTitle[] = "Wizard Demo"; // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { // Инициализируем библиотеку стандартных органов управления InitCommonControls(); // Инициализируем параметры opt.nBold = opt.nItalic = opt.nUnderline = opt.nUseTabs = 0; strcpy(opt.szKeyWord, "Undef"); return TRUE; } // ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case ID_FILE_EXIT: PostQuitMessage(0); // завершаем работу приложения return 0L; break; case ID_HELP_ABOUT: MessageBox(hWnd, "Wizard Demo Application, v.1.0\n" "(C) Alexandr Frolov, 1995\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break; case ID_FILE_OPTIONS: { // Инициализируем страницы блокнота Wizard psheetPage[0].dwSize = sizeof(PROPSHEETPAGE); psheetPage[0].hInstance = hInst; psheetPage[0].pszIcon = MAKEINTRESOURCE(IDI_EFFECTS); psheetPage[0].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[0].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG1); psheetPage[0].pfnDlgProc = DlgProc1; psheetPage[0].pszTitle = "Set Effects"; psheetPage[0].lParam = 0; psheetPage[1].dwSize = sizeof(PROPSHEETPAGE); psheetPage[1].hInstance = hInst; psheetPage[1].pszIcon = MAKEINTRESOURCE(IDI_TAB); psheetPage[1].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[1].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG2); psheetPage[1].pfnDlgProc = DlgProc2; psheetPage[1].pszTitle = "Using Tabs"; psheetPage[1].lParam = 0; psheetPage[2].dwSize = sizeof(PROPSHEETPAGE); psheetPage[2].hInstance = hInst; psheetPage[2].pszIcon = MAKEINTRESOURCE(IDI_KEYWORD); psheetPage[2].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[2].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG3); psheetPage[2].pfnDlgProc = DlgProc3; psheetPage[2].pszTitle = "Keyword"; psheetPage[2].lParam = 0; // Инициализируем заголовок блокнота Wizard psheetHeader.dwSize = sizeof(PROPSHEETHEADER); psheetHeader.hInstance = hInst; psheetHeader.pszIcon = NULL; psheetHeader.dwFlags = PSH_PROPSHEETPAGE | PSH_WIZARD; psheetHeader.hwndParent = hWnd; psheetHeader.pszCaption = "Wizard Demo Sample"; psheetHeader.nPages = sizeof(psheetPage) / sizeof(PROPSHEETPAGE); psheetHeader.ppsp = (LPCPROPSHEETPAGE)&psheetPage; // Создаем и отображаем блокнот Wizard PropertySheet(&psheetHeader); return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ----------------------------------------------------- // Функция DlgProc1 // для первой страницы блокнота // ----------------------------------------------------- BOOL APIENTRY DlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc1_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc1_OnNotify); default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc1_OnInitDialog // Вызывается при инициализации первой страницы // ----------------------------------------------------- BOOL DlgProc1_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // Устанавливаем переключатели в соответствии // со значениями параметров, записанных в // структуре opt SendMessage(GetDlgItem(hdlg, IDC_BOLD), BM_SETCHECK, opt.nBold, 0L); SendMessage(GetDlgItem(hdlg, IDC_ITALIC), BM_SETCHECK, opt.nItalic, 0L); SendMessage(GetDlgItem(hdlg, IDC_UNDERLINE), BM_SETCHECK, opt.nUnderline, 0L); return TRUE; } // ----------------------------------------------------- // Функция DlgProc1_OnNotify // Обрабатывает извещение от первой страницы // ----------------------------------------------------- LRESULT DlgProc1_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { case PSN_SETACTIVE: { // Разблокируем кнопку Next PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_NEXT); break; } case PSN_WIZNEXT: { opt.nBold = (int)SendMessage( GetDlgItem(hdlg, IDC_BOLD), BM_GETCHECK, 0L, 0L); opt.nItalic = (int)SendMessage( GetDlgItem(hdlg, IDC_ITALIC), BM_GETCHECK, 0L, 0L); opt.nUnderline = (int)SendMessage( GetDlgItem(hdlg, IDC_UNDERLINE), BM_GETCHECK, 0L, 0L); break; } default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc2 // для второй страницы // ----------------------------------------------------- BOOL APIENTRY DlgProc2(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc2_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc2_OnNotify); default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc2_OnInitDialog // Вызывается при инициализации второй страницы // ----------------------------------------------------- BOOL DlgProc2_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { SendMessage(GetDlgItem(hdlg, IDC_USETABS), BM_SETCHECK, opt.nUseTabs, 0L); SendMessage(GetDlgItem(hdlg, IDC_DONTUSETABS), BM_SETCHECK, (opt.nUseTabs) ? 0 : 1, 0L); return TRUE; } // ----------------------------------------------------- // Функция DlgProc2_OnNotify // Обрабатывает извещение от второй страницы // ----------------------------------------------------- LRESULT DlgProc2_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { case PSN_SETACTIVE: { // Разблокируем кнопк Next и Back, когда страница // блокнота Wizard становится активной PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_NEXT | PSWIZB_BACK); break; } case PSN_WIZNEXT: { opt.nUseTabs = (int)SendMessage( GetDlgItem(hdlg, IDC_USETABS), BM_GETCHECK, 0L, 0L); break; } default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc3 // для третьей страницы // ----------------------------------------------------- BOOL APIENTRY DlgProc3(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc3_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc3_OnNotify); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc3_OnCommand); default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc3_OnInitDialog // Вызывается при инициализации третьей страницы // ----------------------------------------------------- BOOL DlgProc3_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { SetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), opt.szKeyWord); return TRUE; } // ----------------------------------------------------- // Функция DlgProc3_OnNotify // Обрабатывает извещение от третьей страницы // ----------------------------------------------------- LRESULT DlgProc3_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { case PSN_SETACTIVE: { // Разблокируем кнопку Back, когда страница // блокнота становится активной PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_BACK); break; } case PSN_WIZFINISH: { char szBuf[256]; GetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), opt.szKeyWord, 80); // Заполнение структуры opt завершено, теперь мы // выводим на экран установленные параметры wsprintf(szBuf, "Bold \t= %d\n" "Italic \t= %d\n" "Underline\t= %d\n" "Use Tabs\t= %d\n" "Keyword\t= %s\n", opt.nBold, opt.nItalic, opt.nUnderline, opt.nUseTabs, opt.szKeyWord); MessageBox(NULL, szBuf, "Options", MB_OK | MB_ICONINFORMATION); break; } default: break; } return FALSE; } // ----------------------------------------------------- // Функция DlgProc3_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc3_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { // Сообщение от редактора текста if(id == IDC_EDITKEYWORD) { // Если пользователь изменил ключевое слово в окне // редактирования, разблокируем кнопку Finish if(codeNotify == EN_CHANGE) { PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_BACK | PSWIZB_FINISH); } } return FALSE; }
Файл wizdemo.h (листинг 6.6) содержит описание функций, определенный в нашем приложении.
Листинг 6.6. Файл wizdemo\wizdemo.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); BOOL APIENTRY DlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc1_OnInitDialog(HWND hwnd,HWND hwndFocus, LPARAM lParam); LRESULT DlgProc1_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); BOOL APIENTRY DlgProc2(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc2_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc2_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); BOOL APIENTRY DlgProc3(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc3_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc3_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); void DlgProc3_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify);
В файле resource.h (который создается автоматически системой Microsoft Visual C++) находятся определения констант для работы с ресурсами приложения Wizard Demo. Этот файл представлен в листинге 6.7.
Листинг 6.7. Файл wizdemo\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by WIZDEMO.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDI_EFFECTS 105 #define IDI_TAB 106 #define IDI_KEYWORD 107 #define IDD_DIALOG1 121 #define IDD_DIALOG2 122 #define IDD_DIALOG3 123 #define IDC_BOLD 1000 #define IDC_ITALIC 1001 #define IDC_UNDERLINE 1002 #define IDC_USETABS 1004 #define IDC_EDITKEYWORD 1006 #define IDC_DONTUSETABS 1007 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_FILE_OPTIONS 40029 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 125 #define _APS_NEXT_COMMAND_VALUE 40030 #define _APS_NEXT_CONTROL_VALUE 1008 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл wizdemo.rc (листинг 6.8) содержит определение ресурсов приложения Wizard Demo. Он создается автоматически.
Листинг 6.8. Файл wizdemo\wizdemo.rc
//Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Options...", ID_FILE_OPTIONS MENUITEM SEPARATOR MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END ////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Icon // IDI_APPICON ICON DISCARDABLE "wizdemo.ico" IDI_APPICONSM ICON DISCARDABLE "wizdemsm.ico" IDI_EFFECTS ICON DISCARDABLE "EFFECTS.ICO" IDI_TAB ICON DISCARDABLE "TAB.ICO" IDI_KEYWORD ICON DISCARDABLE "KEYWORD.ICO" ////////////////////////////////////////////////////////////// // Dialog // IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN CONTROL "Bold",IDC_BOLD,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,19,21,35,10 CONTROL "Italic",IDC_ITALIC,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,19,32,35,10 CONTROL "Underline",IDC_UNDERLINE,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,19,43,46,10 GROUPBOX "Effects",IDC_STATIC,8,7,112,53 END IDD_DIALOG2 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN CONTROL "Use Tabs", IDC_USETABS, "Button", BS_AUTORADIOBUTTON,18,22, 63,10 GROUPBOX "Tabs",IDC_STATIC,9,7,105,63 CONTROL "Don't use Tabs",IDC_DONTUSETABS,"Button", BS_AUTORADIOBUTTON,18,35,83,10 END IDD_DIALOG3 DIALOG DISCARDABLE 0, 0, 211, 102 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_EDITKEYWORD,24,58,138,13,ES_AUTOHSCROLL GROUPBOX "Keyword",IDC_STATIC,11,38,166,52 ICON IDI_APPICON,IDC_STATIC,11,7,18,20 END ////////////////////////////////////////////////////////////// // String Table // STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Займемся описанием глобальных переменных и функций приложения Wizard Demo, обратив внимание на отличия от приложения Property Sheet Demo.
Мы бы могли создать орган управления Wizard точно таким же способом, что и блокнот, добавляя страницы по одной с помощью функции CreatePropertySheetPage. Однако есть и другой способ, который не требует использования этой функции. В этом случае нам не потребуется и массив структур hPage типа HPROPSHEETPAGE, который мы изъяли из приложения Wizard Demo.
При создании органа управления Wizard массив структур psheetPage, описывающих страницы, заполняется точно также, как и в приложении Property Sheet Demo.
Однако заголовок блокнота Wizard инициализируется немного по-другому. Например, в поле dwFlags устанавливаются флаги PSH_PROPSHEETPAGE и PSH_WIZARD.
Первый из них говорит о том, что в поле psheetHeader.ppsp будет записан адрес заполненного массива структур, описывающих страницы. Это поле используется вместо поля psheetHeader.phpage, в который мы записывали адрес массива идентификатора страниц, созданных функцией CreatePropertySheetPage.
Флаг PSH_WIZARD говорит о том, что необходимо создать орган управления Wizard, а не обычный блокнот Property Sheet.
Блокнот Wizard создается и отображается функцией PropertySheet, как и в предыдущем приложении.
Функция DlgProc1_OnNotify обрабатывает извещения PSN_SETACTIVE и PSN_WIZNEXT.
Обработчик извещения PSN_SETACTIVE разблокирует кнопку Next, вызывая макрокоманду PropSheet_SetWizButtons :
PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_NEXT);
Эта макрокоманда посылает окну блокнота Wizard сообщение PSM_SETWIZBUTTONS. Второй параметр макрокоманды может принимать значения PSWIZB_BACK (разблокировка кнопки Back), PSWIZB_NEXT (разблокировка кнопки Next) или PSWIZB_FINISH (разблокировка кнопки Finish). Можно также использовать любую комбинацию этих значений, объединяя их при помощи логической операции ИЛИ. Если в качестве второго параметра макрокоманды PropSheet_SetWizButtons указать нулевое значение, все перечисленные выше кнопки будут заблокированы.
Извещение PSN_WIZNEXT поступает в функцию диалога первой страницы тогда, когда пользователь переходит к следующей странице, нажимая кнопку Next. Наш обработчик получает состояние переключателей и записывает его в соответствующие поля структуры opt.
Задача функции DlgProc2_OnNotify - разблокировка кнопок Next и Back, когда пользователь переключается на вторую страницу. Для разблокировки вызывается только что описанная макрокоманда PropSheet_SetWizButtons :
PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_NEXT | PSWIZB_BACK);
В ответ на извещение PSN_WIZNEXT функция DlgProc2_OnNotify получает состояние переключателя IDC_USETABS и записывает его в поле nUseTabs структуры opt.
Функция DlgProc3_OnNotify обрабатывает извещения, которые поступают в функцию диалога второй страницы.
Обработчик извещения PSN_SETACTIVE разблокирует кнопку Back.
При поступлении извещения PSN_WIZFINISH функция диалога отображает установленные параметры. Однако для того чтобы такое извещение поступило в функцию диалога, кнопка Next должна быть преобразована в кнопку Finish. Эту задачу решает обработчик сообщения WM_COMMAND, описанный ниже.
Обработчик сообщения WM_COMMAND отображает кнопки Back и Finish, когда пользователь изменил ключевое слово. Это делается так:
PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_BACK | PSWIZB_FINISH);