6. Блокнот и немного волшебства

Если ваше приложение сложное и имеет большое количество параметров, следует хорошо подумать о том, как пользователь будет их настраивать. Обычно для настройки предлагается многоуровневая система вложенных меню и нагромождение диалоговых панелей, которые вызываются друг из друга, однако это совсем не то, чему обрадуется пользователь.

Если вы посмотрите, как выглядит система настройки параметров, например, в приложении 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.

6.1. Создание органа управления 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 .

Подготовка массива структур PROPSHEETPAGE

Для каждой страницы блокнота или органа управления 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 и создание блокнота

Структура 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, которая его заполняет.

6.2. Обработка извещений

Когда пользователь нажимает кнопки 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".

6.3. Посылка сообщений

Обработчики сообщений, расположенные в функциях диалога, могут посылать органам управления 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.

6.4. Приложение Property Sheet Demo

Приложение 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

Функции приложения 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

Функция WinMain не имеет никаких особенностей, поэтому для экономии места в книге мы не будем ее описывать.

WndProc

Функция WndProc обрабатывает следующие сообщения: WM_CREATE, WM_DESTROY и WM_COMMAND. Обработка выполняется с использованием макрокоманды HANDLE_MSG.

WndProc_OnCreate

Обработчик сообщения WM_CREATE инициализирует библиотеку стандартных органов управления, вызывая для этого функцию InitCommonControls.

Затем он выполняет начальную инициализацию полей структуры параметров opt.

WndProc_OnDestroy

Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.

WndProc_OnCommand

Обработчики сообщений от строк Exit и About действуют также, как и в предыдущем приложении.

Когда пользователь выбирает из меню File строку Options, соответствующий обработчик создает блокнот и отображает его на экране. Процедура создания и отображения блокнота заключается в заполнении массива структур psheetPage, содержащих описания страниц, массива psheetHeader с описанием заголовка блокнота. Каждая страница добавляется в блокнот отдельно функцией CreatePropertySheetPage, причем идентификатор созданное страницы записывается в соответствующий элемент массива hPage.

Блокнот создается и отображается функцией PropertySheet, которой в качестве единственного параметра передается адрес заполненной структуры заголовка блокнота.

DlgProc1

Функция DlgProc1 - это функция диалога для первой страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG, WM_COMMAND и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc1_OnInitDialog, DlgProc1_OnCommand и DlgProc1_OnNotify.

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

DlgProc1_OnInitDialog

Функция DlgProc1_OnInitDialog обрабатывает сообщение WM_INITDIALOG, которое поступает в функцию диалога первой страницы блокнота при инициализации последней.

Единственное, что она делает, это устанавливает переключатели, расположенные на первой странице, в соответствии с содержимым полей структуры параметров opt:

SendMessage(GetDlgItem (hdlg, IDC_BOLD), 
    BM_SETCHECK, opt.nBold, 0L);

Метод установки состояния переключателей в диалоговой панели основан на использовании сообщения BM_SETCHECK. Это сообщение, а также функция GetDlgItem, возвращающая идентификатор окна органа управления, расположенного в диалоговой панели, были подробно описаны в 12 томе "Библиотеки системного программиста".

DlgProc1_OnNotify

Функция 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

Функция DlgProc1_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от переключателей, расположенных на первой странице.

Как только пользователь нажмет какую-либо из имеющихся на первой странице блокнота кнопку, функция DlgProc1_OnCommand разблокирует кнопку Apply, посылая блокноту сообщение PSM_CHANGED:

PropSheet_Changed(GetParent(hdlg), hdlg);

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

DlgProc2

Функция DlgProc2 является функцией диалога для второй страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc2_OnInitDialog и DlgProc2_OnNotify.

DlgProc2_OnInitDialog

Функция DlgProc2_OnInitDialog обрабатывает сообщение WM_INITDIALOG, которое поступает в функцию диалога второй страницы блокнота при ее инициализации.

Ее задача аналогична задаче функции DlgProc1_OnInitDialog - установка переключателей, расположенных на второй странице в соответствии с содержимым полей структуры параметров opt.

Переключатель с идентификатором IDC_DONTUSETABS всегда устанавливается в состояние, противоположное состоянию переключателя IDC_USETABS.

DlgProc2_OnNotify

Функция 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

Функция DlgProc3 является функцией диалога третьей страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG, WM_COMMAND и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc3_OnInitDialog, DlgProc3_OnCommand и DlgProc3_OnNotify.

DlgProc3_OnInitDialog

Функция DlgProc3_OnInitDialog устанавливает содержимое текстового редактора IDC_EDITKEYWORD, записывая в него строку из поля opt.szKeyWord.

DlgProc3_OnNotify

Функция 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.

DlgProc3_OnCommand

Кнопка Apply, расположенная на третьей странице блокнота, разблокируется только в том случае, когда пользователь изменил ключевое слово. Для этого обработчик сообщения WM_COMMAND, реализованный функцией DlgProc3_OnCommand, отслеживает извещение EN_CHANGE. Это извещение поступает от окна текстового редактора в форме сообщения WM_COMMAND в том случае, когда пользователь изменил редактируемый текст (см. том 12 "Библиотеки системного программиста").

Разблокировка кнопки Apply выполняется при помощи макрокоманды PropSheet_Changed.

6.5. Приложение Wizard Demo

Немного изменив приложение 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

Все функции приложения 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.

WndProc_OnCommand

При создании органа управления 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

Функция 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

Задача функции DlgProc2_OnNotify - разблокировка кнопок Next и Back, когда пользователь переключается на вторую страницу. Для разблокировки вызывается только что описанная макрокоманда PropSheet_SetWizButtons :

PropSheet_SetWizButtons(GetParent(hdlg), 
  PSWIZB_NEXT | PSWIZB_BACK);

В ответ на извещение PSN_WIZNEXT функция DlgProc2_OnNotify получает состояние переключателя IDC_USETABS и записывает его в поле nUseTabs структуры opt.

DlgProc3_OnNotify

Функция DlgProc3_OnNotify обрабатывает извещения, которые поступают в функцию диалога второй страницы.

Обработчик извещения PSN_SETACTIVE разблокирует кнопку Back.

При поступлении извещения PSN_WIZFINISH функция диалога отображает установленные параметры. Однако для того чтобы такое извещение поступило в функцию диалога, кнопка Next должна быть преобразована в кнопку Finish. Эту задачу решает обработчик сообщения WM_COMMAND, описанный ниже.

DlgProc3_OnCommand

Обработчик сообщения WM_COMMAND отображает кнопки Back и Finish, когда пользователь изменил ключевое слово. Это делается так:

PropSheet_SetWizButtons(GetParent(hdlg),
  PSWIZB_BACK | PSWIZB_FINISH);