    Листы свойств и мастера 

    В данной главе рассказывается о том, как создавать один из наиболее 
интересных новых элементов управления, называемый мастером. Как известно, этот 
элемент представляет собой последовательность диалоговых окон, облегчающих 
пользователю процесс выбора из сложной группы опций. Диалоговые окна, из 
которых состоит мастер, должны отображаться в строго определенном порядке. 
Таким образом, с помощью мастера можно реализовать сценарий типа "шаг первый, 
шаг второй, шаг третий...'. Возможно, вы уже сталкивались с мастерами и 
представляете себе, что это такое. Например, в Windows 95 мастера используются 
при инсталляции в системе новых устройств, таких как принтер и др. 
    Да и сами мастера создаются с помощью нового элемента управления, 
называемого листом свойств. Обычно этот элемент используется для просмотра или 
задания различных свойств, связанных с некоторым элементом. И хотя листы 
свойств внешне напоминают закладки, рассмотренные в предыдущей главе, 
возможности их значительно шире. 

    329 


    Основы работы с листами свойств         | 

    Листы свойств дают пользователю возможность проверять и менять различные 
свойства, относящиеся к некоторому компоненту или подсистеме приложения. Так, 
например, лист свойств может использоваться для задания параметров принтера 
или конфигурации модема. Пример листа свойств показан на рис. 13.1. Как 
видите, с точки зрения пользователя этот лист состоит из нескольких страниц. С 
каждой страницей связана своя закладка, при выборе которой отображается 
соответствующая страница. Это делает листы свойств похожими на закладки, 
рассмотренные в главе 12. В то же время листы свойств имеют гораздо больше 
функций и более просты в использовании. 

    Рис. 13.1. Пример листа свойств 

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

    330                                    MFC: основы программирования 

    В MFC листы свойств описываются с помощью класса CPropertySheet, который 
непосредственно порождается от класса CWnd. Каждая страница листа является 
объектом класса CPropertyPage, порождаемого от класса CDialog. 
    Все листы свойств имеют по крайней мере две кнопки: ОК и Cancel. Часто 
присутствует и третья кнопка — Apply. Важно понимать, что, хотя в каждой 
странице с помощью различных диалоговых элементов реализован механизм 
взаимодействия с пользователем, только сам лист свойств (точнее, его объект) 
выполняет действия, связанные с принятием или отменой изменений, внесенных 
пользователем. Говоря другими словами, ни одна страница не должна включать в 
себя кнопки OK, Cancel или Apply. Все связанные с ними операции выполняются 
исключительно объектом листа свойств. 
    Все диалоговые окна страниц отображаются в одном окне. Объект листа 
свойств управляет взаимодействием со страницами и переходами между ними. Как 
правило, в каждом диалоговом окне все элементы управляются обычным образом, 
т.е. с помощью объекта класса диалогового окна. Кроме того, каждая страница 
должна также отвечать на сообщения, генерируемые содержащим ее листом свойств. 
Для выполнения каких-либо действий над страницей используются различные 
функциичлены класса CPropertyPage. О них будет идти речь позднее. 
    При создании листа свойств необходимо придерживаться одного очень важного 
правила: объект класса CPropertyPage, связанный с каждой страницей, не должен 
закрывать свое диалоговое окно. Т.е. он не должен вызывать ни функцию 
EndDialogO, ни функцию DestroyWindowO. Все подобные действия выполняются 
объектом листа свойств. Если закрыть диалоговое окно одной из страниц, то лист 
свойств останется активным, но страница будет пуста. Согласитесь, это 
выглядело бы не очень красиво. 
    При работе с листами свойств в программу должен быть включен стандартный 
библиотечный файл AFXDLGS.H. 

    Создание листа свойств 

    В простейшем случае для создания листа свойств требуется выполнить два 
действия: объявить объект типа CPropertySheet, а затем добавить к нему объекты 
типа CPropertyPage. Каждый объект типа CPropertyPage определяет одну из 
страниц листа свойств. Однако по некоторым соображениям (включая легкость 
использования и безопасность) такой подход довольно редко используется. Вместо 
него применяется чуть более сложный метод. 
    В большинстве приложений, включающих листы свойств, объекты типа 
CPropertySheet и CPropertyPage непосредственно не используются. Работа с ними 
ведется через порожденные классы. Обычно для каждой страницы листа необходимо 
от класса CPropertyPage породить отдельный класс, описывающий свойства данной 
страницы. Класс, порождаемый от CPropertySheet, будет тогда включать объекты 
каждого из классов, описывающих страницы листа. 
    При создании экземпляра объекта листа свойств будут автоматически созданы 
объекты всех его страниц. Преимущества данного подхода таковы: 
    • можно в точности контролировать форму и свойства каждой страницы; 
    • все объекты страниц содержатся внутри объекта листа свойств, который эти 
страницы использует. 

    73. Листы свойств и мастера 331 

    Давайте подробнее рассмотрим процесс создания листа свойств. 

    Создание страниц листа свойств 

    Первое, что необходимо сделать, — это определить страницы листа свойств. 
Поскольку класс CPropertyPage порождается от класса CDialog, создание таких 
страниц почти идентично созданию диалоговых окон. По сути, единственным 
отличием является то, что в конструкторе создаваемого класса должен 
выполняться вызов конструктора класса CPropertyPage, а не класса CDialog. 
Класс CPropertyPage поддерживает три вида конструкторов: 

    CPropertyPage () ; 
    CPropertyPage (LPCSTR Name, UINT ТаЫ1>=0) ; 
    CPropertyPage(HINT ID, UINT TabID^O}; 

    В первом варианте конструктор создает объект страницы, но не 
инициализирует его. Во втором варианте параметр Name определяет имя шаблона 
диалогового окна в файле ресурсов. В третьем варианте конструктора параметр ID 
является идентификатором шаблона диалогового окна. В обоих случаях параметр 
TabID является идентификатором ресурса строки, которая будет использована в 
качестве имени закладки для создаваемой страницы. Если этот параметр равен 
нулю, то в качестве имени закладки будет использовано имя диалогового окна. 
    Если конструктор CPropertyPageQ используется без параметров (т.е. для 
объявления объекта), то для действительного создания страницы необходимо 
вызвать функцию Construct(), которая может иметь следующие прототипы: 

    void CPropertyPage::Construct(LPCSTR Name, UINT TabID=0); 
    void CPropertyPage::Construct(UINT ID, UINT TabID=0); 

    Здесь параметр Name определяет имя шаблона диалогового окна в файле 
ресурсов. Параметр ID является идентификатором данного шаблона. В обоих 
случаях параметр TabID является идентификатором ресурса строки, которая будет 
использована в качестве имени закладки для создаваемой страницы. Если этот 
параметр равен нулю, то в качестве имени закладки будет использовано имя 
диалогового окна. 
    Как уже говорилось, в большинстве случаев для страниц листа свойств 
необходимо порождать собственные классы, использующие класс CPropertyPage в 
качестве базового. При этом работа со страницами будет вестись аналогично 
работе с диалоговыми окнами, классы которых порождались от класса CDialog. В 
частности, каждый класс, порождаемый от CPropertyPage, должен иметь свою 
очередь сообщений и определять собственные обработчики сообщений. 

    Создание объекта листа свойств 

    Когда все классы, определяющие страницы, порождены, можно приступать к 
объявлению класса для самого листа свойств. Этот класс порождается от класса 
CPropertySheet. Конструктор порожденного класса должен осуществлять вызов 
одного из конструкторов базового класса: 

    CPropertySheet () ; 
    CPropertySheet(UINT TitleID, CWnd *0^ner=NULL, UINT Page=0) ; 
    CPropertySheet(LPCSTR IpszTitle, CWnd *Owner=NULL, UINT Page=0); 


    332 MFC: основы программирования 

    В первом случае создается неинициализированный объект листа свойств. Во 
втором случае параметр TitleID является идентификатором ресурса строки 
заголовка листа. В третьем варианте параметр Ips^Title содержит указатель на 
строку заголовка. В обоих случаях параметр Owner является указателем на объект 
родительского окна для листа свойств, а параметр Page — это индекс страницы, 
которая должна быть выбрана при создании объекта. Индексы страниц начинаются с нуля. 
    Если конструктор CPropertySheetO используется без параметров (т.е. просто 
для создания экземпляра объекта), то для инициализации листа свойств 
необходимо вызвать функцию Construct(), которая может иметь следующие прототипы: 

    void CPropertySheet::Construct(DINT TitleID, CWnd *Owner=NULL, 
    UINT Page=0) ; 
    void CPropertySheet::Construct(LPCSTR IpszTitle, CWnd *Owner=NULL, 
    UINT Page=0) ; 

    Параметр TitleID является идентификатором ресурса строки заголовка листа. 
Параметр IpszTitle используется в качестве указателя на данную строку. В обоих 
случаях параметр Owner является указателем на объект родительского окна для 
листа свойств, а параметр Page — это индекс страницы, которая будет выбрана. 

    Добавление страниц к листу свойств 

    Чтобы можно было использовать лист свойств, к нему нужно добавить 
страницы. Это выполняется с помощью функции AddPageQ, являющейся членом класса 
CPropertySheet. Она имеет такой прототип: 

    void CPropertySheet::AddPage(CPropertyPage * Раде}; 

    Параметр Page является указателем либо на объект типа CPropertyPage, либо 
на объект класса, порожденного от CPropertyPage. 
    Страницы добавляются в лист свойств слева направо. Это означает, что 
первая добавляемая страница будет иметь индекс ноль. 

    Активизация листа свойств 

    Когда лист свойств создан и к нему добавлены страницы, его можно 
активизировать с помощью функции DoModalQ. При вызове этой функции 
отображается модальный вариант листа свойств. Функция DoModalQ имеет следующий прототип: 

    virtual int CPropertySheet::DoModal(); 

    Функция возвращает либо IDOK, либо IDCANCEL. При возникновении ошибки 
возвращается ноль. Данная функция не завершает своего выполнения до тех пор, 
пока лист свойств не закрыт. Лист свойств автоматически закрывается при 
нажатии кнопки ОК или Cancel. 
    Для активизации немодального листа свойств необходимо использовать функцию 
Create() класса CPropertySheet. Но в большинстве случаев создаются модальные 
листы свойств. Немодальными они бывают в редких случаях, только в качестве 
исключения. Кстати, для большей части функций, содержащихся в классе 
CPropertySheet, подразумевается, что лист свойств создан модальным. (Например, 
в немодальных листах 

    13. Листы свойств и мастера 333 

    свойств кнопки OK, Cancel и Apply не появляются автоматически.) В примерах 
данной главы будут использоваться именно модальные листы свойств. 

    Резюме по листам свойств 

    Ниже приведен краткий перечень действий, которые необходимо выполнить для 
создания и активизации листа свойств: 
    1. В файле ресурсов должны быть определены диалоговые окна для страниц листа. 
    2. От класса CPropertyPage должны быть порождены все необходимые классы, 
описывающие страницы. 
    3. От класса CPropertySheet должен быть порожден класс, описывающий лист 
свойств. Объекты страниц листа должны являться членами данного класса. 
    4. Необходимо создать объект класса, порожденного от CPropertySheet 
    5. Необходимо активизировать лист свойств с помощью функции DoModalQ. 
    В приводимом далее примере программы все эти действия будут реализованы. 

    Обработка сообщений, поступающих от листов свойств 

    Каждая страница листа свойств является, по сути, диалоговым окном и 
управляется аналогичным образом. Например, при создании страницы программе 
посылается сообщение WM_INn DIALOG, которое вызывает выполнение встроенного 
обработчика OnInitDialogO В то же время класс CPropertyPage содержит ряд 
других встроенных обработчиков, связанных с листами свойств. Вот некоторые из 
них (табл. 13.1): 

    Таблица 13. I. Обработчики, связанные с листами свойств 

    Обработчик        Когда вызывается 

    ОпАрр1у()           При нажатии кнопки Apply 
    OnCancelQ          При нажатии кнопки Cancel 
    OnKillActiveQ        При потере страницей фокуса ввода 
    ОпОК()             При нажатии кнопки ОК 
    OnResctQ           При нажатии кнопки Cancel 
    OnSetActiveQ        При получении страницей фокуса ввода (активизация страницы) 

    Прототипы обработчиков данных сообщений таковы: 

    virtual void CPropertyPage OnApplyO; 
    virtual void CPropertyPage OnCancelO; 
    virtual void CPropertyPage OnKillActive(); 
    virtual void CPropertyPage OnOK(); 
    virtual void CPropertyPage OnResetO; 
    virtual void CPropertyPage OnSetActive(); 


    334                                    MFC: основы программирования 

    Классы, порождаемые от CPropertyPage, должны переопределять некоторые или 
даже все эти функции, чтобы выполнять соответствующие действия. Например, при 
вызове обработчика ОпОК() все изменения установок листа свойств, выполненные 
пользователем, должны запоминаться. При вызове функции OnResetQ атрибутам 
листа свойств должны возвращаться первоначальные значения. При вызове функции 
OnApplyQ изменения, сделанные пользователем в листе свойств, должны возыметь 
эффект, но сам лист при этом должен оставаться активным. 

    Активизация кнопки Apply 

    При первом отображении листа свойств кнопка Apply неактивна. Чтобы 
активизировать ее, необходимо вызвать функцию SetModifiedQ, которая имеет 
такой прототип: 

    void CPropertyPage::SetModified(BOOL How=TRVE); 

    Здесь параметр How определяет, должна ли кнопка Apply стать активной или 
же, наоборот, неактивной. Если этот параметр не равен нулю, то кнопка будет 
активизирована. В противном случае кнопка станет неактивной. 
    Функция SetModified() должна вызываться всякий раз, когда пользователь 
внес какие-либо изменения в лист свойств. 

    Пример работы с листом свойств 

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

    ; Определения диалоговых окон для листа свойств 
    tinclude <afxres.h> 
    #include "ids.h" 
    PropertyMenu MENU 
    ( 
    POPUP "&Property Sheet" 
    { 
    
    MENUITEM "&Activate\tF2", IDM_ACTIVATE 
    MENUITEM "E&xit\tF3", IDM_EXIT 
    ) 
    
    MENUITEM "&Help", IDM_HELP 
    } 
    PropertyMenu ACCELERATORS 
    ( 
    
    VK_F2, IDM_ACTIVATE, VIRTKEY 
    VK_F3, IDM_EXIT, VIRTKEY 


    13. Листы свойств и мастера                                           335 


    VK_F1, IDM_HELP, VIRTKEY ) 
    PropDialogI DIALOG 10, 10, 110, 100 
    Caption "First Page" 
    STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU I WS_VISIBLE 
    ( 
    DEFPUSHBUTTON "One", IDDJ3NE, 11, 10, 32, 14, 
    
    WS_CHILD [ WS_VISIBLE | WS_TABSTOP 
    PUSHBUTTON "Two", IDD_TWO, 11, 34, 32, 14, 
    
    WS_CHILD I WS_VISIBLE | WS_TABSTOP 
    PUSHBUTTON "Three", IDD_THREE, 11, 58, 32, 14, 
    
    WS_CHILD I WS_VISIBLE | WS_TABSTOP 
    LISTBOX IDD_LB1, 66, 25, 50, 33, LBS_NOTIFY | 
    
    WS_VISIBLE | WS_BORDER | WS__VSCROLL | WS_TABSTOP 
    ) 
    PropDialog2 DIALOG 10, 10, 110, 100 
    Caption "Second Page" 
    STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU I WS_VISIBLE 
    { 
    DEFPUSHBUTTON "Invert", IDD_INVERT, 11, 10, 32, 14, 
    
    WS_CHILD | WS_VISIBLE | WS_TABSTOP 
    AUTOCHECKBOX "Check Box I", IDD_CB1, 66, 10, 70, 10 
    AUTOCHECKBOX "Check Box 2", IDD_CB2, 66, 30, 70, 10 
    AUTOCHECKBOX "Check Box 3", IDD_CB3, 66, 50, 70, 10 
    } 
    PropDialog3 DIALOG 10, 10, НО, 100 
    Caption "Third Page" 
    STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU I WS_VISIBLE 
    ( 
    DEFPUSHBUTTON "Top", IDD_TOP, 11, 10, 32, 14, 
    
    WS_CHILD I WS_VISIBLE | WS_TABSTOP 
    PUSHBUTTON "Bottom", IDD_BOTTOM, 11, 34, 32, 14, 
    
    WS_CHILD I WS_VISIBLE | WS_TABSTOP 
    AUTORADIOBUTTON "Radio Button I", IDD_RB1, 66, 10, 70, 10 
    AUTORADIOBUTTON "Radio Button 2", IDD_RB2, 66, 30, 70, 10 
    AUTORADIOBUTTON "Radio Button 3", IDD_RB3, 66, 50, 70, 10 
    } 
    Ниже приведен текст библиотечного файла IDS.H: 
    #define IDM_ACTIVATE  100 
    #define IDM_EXIT      101 
    #define IDM_HELP      102 
    #define IDD_ONE       200 
    #define IDD_TWO       201 
    #define IDD_THREE     202 
    #define IDD_TOP       203 
    #define IDD_BOTTOM    204 


    336                                     MFC: основы программирования 


    tdefine IDD_INVERT   205 
    tdefine IDD_LB1      301 
    tdefine IDD_CB1      501 
    #define IDD_CB2      502 
    tdefine IDD_CB3      503 
    tdefine IDD_RB1      601 
    tdefine IDD_RB2      602 
    #define IDD_RB3      603 
    #define NUMSTRINGS      5 
    tdefine NUMPAGES        3 
    Это текст файла PROP.H, содержащего объявления порождаемых классов: 
    // PROP.H 
    /* 
    Ниже определяются классы, предназначенные для создания диалоговых окон 
страниц листа свойств. На основе объектов страниц впоследствии будет создан 
объект листа свойств 
    #/ 
    // Это класс для создания диалогового окна первой страницы 
    class CPropDialogI : public CPropertyPage 
    { 
    public: 
    CPropDialogI () : CPropertyPage() (}; 
    BOOL OnInitDialog (); 
    BOOL OnApplyO ; 
    
    /* В реальных приложениях необходимо переопределять 
    следующие функции: 
    
    void OnCancelO; // вызывается при нажатии кнопки Cancel 
    void OnOK(); // вызывается при нажатии кнопки ОК 
    #/ 
    afx msg void OnOneO; 
    afx msg void OnTwoO," 
    afx_msg void OnThree(); 
    afx_msg void OnListBoxO; 
    DECLARE_MESSAGE_MAP() 
    ); 
    // Это класс для создания диалогового окна второй страницы 
    class CPropDialog2 : public CPropertyPage 


    13. Листы свойств и мастера                                           337 


    public:                                                            щ 
    CPropDialog2() : CPropertyPageО {);                               I 
    BOOL OnInitDialog(); 
    BOOL OnApplyO ; 
    
    /* В реальных приложениях необходимо переопределять 
    следующие функции: 
    
    void OnCancel (); // вызывается при нажатии кнопки Cancel 
    void OnOK(); // вызывается при нажатии кнопки ОК 
    */ 
    afx msg void Onlnvert(); 
    afx msg void OnCBlO; 
    afx_msg void OnCB2(); 
    afx_msg void ОпСВЗО; 
    DECLARE_MESSAGE_MAP() 
    ); 
    
    // Это класс для создания диалогового окна третьей страницы 
    class CPropDialog3 : public CPropertyPage 
    < 
    public: 
    CPropDialog3 () : CPropertyPage() (}; 
    BOOL OnInitDialog(); 
    BOOL OnApplyO ; 
    
    /* В реальных приложениях необходимо переопределять 
    следующие функции: 
    
    void OnCancel(); // вызывается при нажатии кнопки Cancel 
    void OnOK(); // вызывается при нажатии кнопки ОК 
    */ 
    afx_msg void OnTopO; 
    afx msg void OnBottomO; 
    afx_msg void OnRBl(); 
    afx_msg void OnRB2(); 
    afx msg void OnRB3(); 
    DECLARE_MESSAGE_MAP() 
    }; 
    // Это класс для создания листа свойств 
    class CSamplePropSheet : public CPropertySheet 
    { 
    CPropDialogI pagel; // первая страница 


    338                                     MFC: основы программирования 


    CPropDialog2 page2; // вторая страница 
    CPropDialog3 радеЗ; // третья страница 
    public: 
    
    CSamplePropSheet() : CPropertySheet<) ( 
    Construct("Sample Property Sheet", this); 
    pagel.Construct("PropDialogI", 0) ; 
    pagel.Construct("PropDialog2" , 0) ; 
    pagel.Construct("PropDialog3", 0) ; 
    AddPage(&pagel) ; 
    AddPage(&page2) ; 
    AddPage(&page3) ; 
    } ); 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    { 
    
    CSamplePropSheet m PropSheet; // объект листа свойств 
    public: 
    CMainWin() ; 
    afx msg void OnActivate (); 
    afx msg void OnExitO; 
    afx msg void OnHelpO; 
    DECLARE_MESSAGE_MAP() 
    ); 
    // Это класс для создания приложения 
    class' САрр : public CWinApp 
    ( 
    public: 
    BOOL Initlnstance () ; 
    ); 
    Вот текст самой программы: 
    // Использование листа свойств 
    #include <afxwin.h> 
    #include <afxdlgs.h> 
    ^include <string.h> 
    #include "ids.h" 
    #include "prop.h" 
    char list[][40] = ( 
    "Red", 
    "Green", 
    "Yellow", 
    "Black", 
    "White", 
    }; 
    // Создание главного окна приложения 


    13. Лист»» свойств и мастера                                            339 


    CMainWin::CMainWin()                                                ' 
    { 
    Create(NULL, "Using A Property Sheet", 
    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "PropertyMenu"); 
    InitCommonControls () ; 
    
    // Загрузка таблицы быстрого доступа 
    if(!LoadAccelTable("PropertyMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance() 
    ( 
    m pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow) ; 
    m_pMainWnd->UpdateWindow<) ; 
    return TRUE; 
    } 
    
    // Это очередь сообщений приложения                                  , 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)                                I 
    ON_COMMAND(IDM_ACTIVATE, OnActivate)                                ! 
    ON_COMMAND(IDM_EXIT, OnExit) 
    
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    
    // Обработка сообщения IDM_ACTIVATE 
    afx_msg void CMainWin::OnActivate() 
    ( 
    
    m_PropSheet.DoModal(); // активизирует модальный лист свойств 
    } 
    
    // Обработка сообщения IDM_EXIT 
    afx_msg void CMainWin::OnExit() 
    { 
    int response; 
    response = MessageBox("Quit the Program?", "Exit", MB_YESNO) ; 
    if(response == IDYES) 
    SendMessage(WM CLOSE); // завершение приложения 1 
    
    // Обработка сообщения IDM_HELP 
    afx_msg void CMainWin::OnHelp () 
    ( 
    MessageBox("Property Sheet", "Help"); 
    } 


    340                                    MFC: основы программирования 


    // Это очередь сообщений диалогового окна первой страницы 
    BEGIN_MESSAGE_MAP(CPropDialogI, CPropertyPage) 
    ON_COMMAND(IDD_ONE, OnOne) 
    ON_COMMAND(IDD_TWO, OnTwo) 
    ON_COMMAND(IDD__THREE, OnThree) 
    
    ON_LBN_DBLCLK(IDD_LB1, OnListBox) 
    END_MESSAGE_MAP() 
    // Инициализация диалогового окна первой страницы 
    BOOL CPropDialogI::OnInitDialog() 
    ( 
    int i; 
    
    CPropertyPage::OnInitDialog(); // Вызов функции базового класса 
    CListBox *lbptr = (CListBox *) GetDlgItem(IDD_LB1) ; 
    
    for(i=0; i<NUMSTRINGS; i++) 
    lbptr->AddString(list[i]) ; 
    !bptr->SetCurSel(0) ; 
    return TRUE; 
    ) 
    
    // Заменитель функции OnApplyO класса CPropDialogI 
    afx_msg BOOL CPropDialogI::OnApply() 
    ( 
    MessageBox("Selection Applied", "First Page"); 
    return TRUE; 
    1 
    
    // Обработка сообщения IDD_ONE 
    afx_msg void CPropDialogI::0n0ne() 
    ( 
    MessageBox("One", "Button Pressed"); 
    ) 
    
    // Обработка сообщения IDD TWO 
    arx msg void CPropDialogI::OnTwo() 
    { 
    MessageBox ("Two", "Button Pressed"); 
    ) 
    
    // Обработка сообщения IDD_THREE 
    afx_msg void CPropDialogI::OnThree() 
    ( 
    MessageBox ("Three", "Button Pressed"); 
    » 
    // Обработка идентификационных сообщений списка 
    afx_msg void CPropDialogI::OnListBox() 
    ( 
    int i; 


    13. Листы свойств и мастера                                           341 


    char str[255] ; 
    CListBox *lbptr = (CListBox *) GetDlgItem(IDD_LB1) ; 
    i = !bptr->GetCurSel (); 
    lbptr->GetText(i, str) ; 
    MessageBox(str, "Selection is"); 
    SetModified(l); 
    } 
    
    // Это очередь сообщений диалогового окна второй страницы 
    BEGIN_MESSAGE_MAP(CPropDialog2, CPropertyPage) 
    ON_COMMAND(IDD_INVERT, Onlnvert) 
    ON_COMMAND(IDD_CB1, OnCBl) 
    ON_COMMAND(IDD_CB2, OnCB2) 
    
    ON_COMMAND(IDD_CB3, ОпСВЗ) 
    END_MESSAGE_MAP() 
    
    // Инициализация диалогового окна второй страницы                   J 
    BOOL CPropDialog2::OnInitDialog()                                   1 
    { 
    CPropertyPage::OnInitDialog(); // Вызов функции базового класса 
    CButton *cbptr = (CButton *) GetDlgItem(IDD_CB1); 
    cbptr->SetCheck(l);                                                i 
    return TRUE; 
    > 
    // Заменитель функции OnApplyO класса CPropDialog2 
    afx_msg BOOL CPropDialog2::OnApply() 
    { 
    MessageBox("Selection Applied", "Second Page"); 
    return TRUE; 
    » 
    
    // Обработка сообщения IDD_INVERT 
    afx_msg void CPropDialog2::Onlnvert() 
    { 
    CButton *lbptrl = (CButton *) GetDlgItem(IDD_CB1) ; 
    CButton *lbptr2 = (CButton *) GetDlgItem(IDD_CB2); 
    CButton *lbptr3 = (CButton *) GetDlgItem(IDD_CB3) ; 
    
    // инвертирование состояния контрольных переключателей 
    if(lbptrl->GetCheck()) lbptrl->SetCheck(0) ; 
    else lbptrl->SetCheck(l); 
    if(lbptr2->GetCheck()) lbptr2->SetCheck(0) ; 
    else lbptr2->SetCheck(l) ; 


    342                                     MFC: основы программирования 


    if(lbptr3->GetCheck()) lbptr3->SetCheck(0) ; 
    else lbptr3->SetCheck(l); 
    SetModified(l) ; 
    1 
    
    // Обработка сообщения IDD_CB1 
    afx_msg void CPropDialog2::OnCBl() 
    ( 
    SetModified(l) ; 
    ) 
    
    // Обработка сообщения IDD_CB2 
    afx_nisg void CPropDialog2 : :OnCB2 () 
    ( 
    SetModified(l) ; 
    ) 
    
    // Обработка сообщения IDD CB3 
    afx_msg void CPropDialog2::OnCB3() 
    ( 
    SetModified(1) ; 
    ) 
    
    // Это очередь сообщений диалогового окна третьей страницы 
    BEGIN_MESSAGE_MAP(CPropDialog3, CPropertyPage) 
    ON_COMMAND(IDD_TOP, OnTop) 
    ON_COMMAND(IDD_BOTTOM, OnBottom) 
    ON_COMMAND(IDD_RB1, OnRBl) 
    ON_COMMAND(IDD_RB2, OnRB2) 
    
    ON_COMMAND(IDD_RB3, OnRB3) 
    END_MESSAGE_MAP() 
    // Инициализация диалогового окна третьей страницы 
    BOOL CPropDialog3::OnInitDialog() 
    ( 
    CPropertyPage::OnInitDialog(); // Вызов функции базового класса 
    CButton *cbptr = (CButton *) GetDlgItem(IDD_RBl) ; 
    cbptr->SetCheck(1) ; 
    return TRUE; 
    1 
    // Заменитель функции OnApplyO класса CPropDialog3 
    afx_msg BOOL CPropDialog3::OnApply() 
    ( 
    MessageBox("Selection Applied", "Third Page"); 
    return TRUE; 
    ) 
    // Обработка сообщения IDD_TOP 


    13. Листы свойств и мастера                                          343 


    afx_msg void CPropDialog3::OnTop() 
    ( 
    CButton *lbptrl = (CButton *) GetDlgItem(IDD_RB1); 
    CButton *lbptr2 = (CButton *) GetDlgItem(IDD_RB2); 
    CButton *lbptr3 = (CButton *) GetDlgItem(IDD_RB3); 
    
    // сброс нижних кнопок 
    lbptr2->SetCheck(0) ; 
    lbptr3->SetCheck(0) ; 
    lbptrl->SetCheck(1); // установка верхней кнопки 
    SetModified(l) ; 
    ) 
    
    // Обработка сообщения IDD_BOTTOM 
    afx msg void CPropDialog3::OnBottom() 
    ( 
    CButton *lbptrl = (CButton *) GetDlgItem(IDD_RBl) 
    CButton *lbptr2 = (CButton *) GetDlgItem(IDD_RB2) 
    CButton *lbptr3 = (CButton *) GetDlgItem(IDD_RB3) 
    
    // сброс верхних кнопок 
    lbptr2->SetCheck<0) ; 
    lbptrl->SetCheck(0) ; 
    lbptr3->SetCheck(1); // установка нижней кнопки 
    SetModified(l) ; 
    } 
    
    // Обработка сообщения IDD RB1 
    afx_msg void CPropDialog3::OnRBl() 
    ( 
    SetModified(l) ; 
    > 
    
    // Обработка сообщения IDD_RB2 
    afx_msg void CPropDialog3::OnRB2() 
    ( 
    SetModified(l) ; 
    } 
    
    // Обработка сообщения IDD RB3 
    afx_msg void CPropDialog3::OnRB3() 
    { 
    SetModified(l) ; 
    } 
    
    CApp App; // создание экземпляра приложения 
    Пример работы программы показан на рис. 13.2. 


    344                                     MFC: основы программирования 


    Рис. 13.2. Пример работы с листом свойств 


    13. Листы свойств и мастера                                               345 


    Текст программы довольно "прозрачен" и не должен представлять никаких 
трудностей. Однако давайте все же обратим внимание на некоторые моменты. В 
файле PROP.H вначале объявляются классы для страниц листа, называемые от 
CPropDialogI до CPropDialog3. Эти классы затем используются в классе 
CSamplePropSheet для объявления объектов страниц листа. Нужно помнить, что 
данные объекты не должны быть уничтожены, пока используется лист свойств, 
содержащий их. Именно поэтому (в большинстве случаев) объекты страниц 
объявляются членами порождаемого класса листа свойств. Они не будут уничтожены 
до тех пор, пока не удален объект самого листа. 
    Реализация функции ОпАрр1у() включена в классы страниц только в качестве 
иллюстрации. Единственным назначением этой функции является отображение окна 
сообщения. Прототипы функций ОпОК() и ОпСапсеЦ) включены в файл PROP.H в виде 
комментариев. Эти функции могут быть реализованы в реальных приложениях. 
    В закрытой (private) части класса CMainWin объявлена переменная 
mJPropSheet, которая является объектом типа CSamplePropSheet. Таким образом, 
когда в функции CApp::InitInstance() создается объект главного окна 
приложения, объект листа свойств создается автоматически. 

    Создание мастера 

    Теперь, когда известно, как создавать лист свойств, не составит особого 
труда преобразовать его в мастера. С точки зрения программиста мастер является 
последовательным набором диалоговых окон. Он определяется точно таким же 
образом, что и лист свойств: сначала объявляются объекты страниц, содержащихся 
в мастере, а затем эти страницы добавляются в лист свойств. Чтобы 
преобразовать такой лист свойств в мастера, необходимо выполнить еще несколько действий: 
    1. Задать режим мастера. 
    2. Определить специальные кнопки мастера, которые будут появляться при 
отображении каждой его страницы. 
    3. Переопределить, при необходимости, функции OnWizardFinish(), 
OnWizardNextQ и OnWizardBackQ. 
    4. При желании, добавить графические изображения к страницам мастера. 
Давайте посмотрим, как реализуется каждое из этих действий. 
    Задание режима мастера 
    Как уже было сказано, мастер создается таким же способом, что и лист 
свойств. Чтобы преобразовать лист свойств в мастера, необходимо изменить 
способ отображения данного листа. Это довольно легко сделать: достаточно перед 
обращением к функции DoModalQ вызвать функцию SetWizardMode(), которая 
является членом класса CPropertySheet и имеет следующий прототип: 

    void CPropertySheet::SetWizardMode() ; 

    После вызова данной функции лист свойств будет автоматически отображен в 
виде мастера. 


    346 MFC: основы программирования 


    Определение кнопок мастера 

    Хотя при вызове функции SetWzardModeQ лист свойств будет автоматически 
преобразован в мастера, необходимо выполнить еще ряд действий, чтобы такой 
мастер мог работать корректно. Как известно, мастера имеют собственный набор 
управляющих кнопок, которые называются Back, Next и Finish. Установка и отмена 
этих кнопок должна осуществляться программным путем. Например, на первой 
странице должна находиться кнопка Next, а кнопки Back быть не должно. На 
последней странице должны присутствовать кнопки Back и Finish. На страницах, 
расположенных между первой и последней, должны присутствовать кнопки Next и 
Back. Для выполнения подобных действий предназначена функция 
SetWizardButtons(), прототип которой таков: 

    void CPropertySheet::SetWizardButtons(DWORD dwWhich}; 

    В параметре dwWhich указывается, какая кнопка (или кнопки) должна быть 
отображена. На странице будут присутствовать только указанные кнопки, а 
остальные будут недоступны. Кнопкам мастеров соответствуют следующие константы: 

    • PSWIZB_BACK 
    • PSWIZB_DISABLEDFINISH 
    • PSWIZB_FINISH 
    • PSWIZB_NEXT 

    С помощью операции побитового ИЛИ можно объединять две или более таких 
констант, чтобы задать сразу несколько кнопок на странице. Следовательно, для 
задания кнопок Back и Finish должна использоваться такая команда: 

    SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH) ; 

    Константе PSWIZBJDISABLEDFINISH соответствует неактивная кнопка Finish. 
Она может использоваться в тех случаях, когда пользователь некорректно 
завершил последовательность диалоговых окон мастера. 

    Обработка стандартных сообщений мастеров 

    Есть три стандартных сообщения, которые связаны непосредственно с 
мастерами. Они генерируются при нажатии пользователем одной из кнопок мастера. 
Класс CPropertyPage содержит встроенные функции-обработчики для данных 
сообщений. Эти функции могут быть переопределены в программе. Вот их прототипы: 

    virtual void CPropertyPage::OnWizardBack(); 
    virtual void CPropertyPage::OnWizardNext(); 
    virtual void CPropertyPage::OnWizardFinish(); 

    Как и следовало ожидать, функция OnWizardBackQ вызывается при нажатии 
кнопки Back, функция OnWizardNextQ — при нажатии кнопки Next, а функция 
OnWizardFinishO — при нажатии кнопки Finish. 
    Все эти обработчики могут переопределяться в программе. Например, внесение 
изменений может не давать никакого эффекта, пока пользователь не нажмет кнопку 
Finish. При нажатии кнопки Back может происходить сбрасывание опций, а при 
нажатии кнопки Next — проверка правильности выбора, сделанного пользователем. 

    13. Листы свойств и мастера                                           347 

    Помимо стандартных обработчиков, для мастеров можно также переопределять 
функцию ОпСапсе1(). Она вызывается при нажатии пользователем кнопки Cancel. 
Работа функции была описана ранее. 

    Использование растровых изображений 

    Возможно, вы обращали внимание, что у большинства мастеров на первой 
странице в левой ее части находится некоторое изображение, служащее для 
идентификации мастера. Аналогичные изображения могут также находиться и на 
последующих страницах. Они не являются обязательными, но, тем не менее, 
настоятельно рекомендуется для каждого мастера помещать такое изображение по 
крайней мере на первой странице. Чтобы добавить растровые изображения к 
мастеру, необходимо для каждого класса, связанного с диалоговым окном одной из 
страниц, переопределить стандартную функцию OnPaintQ. Для загрузки и вывода 
изображений на экран используется процедура, которая была описана в главе 7. 

    Стилевые переменные 

    При работе с мастерами можно использовать ряд специальных переменных, 
благодаря которым мастерам можно придавать вид, соответствующий требованиям 
фирмы Microsoft. Вот эти переменные (табл. 13.2): 

    Таблица 13.2. Стилевые переменные 

    Переменная      Значение 

    WtZ_CXDLG     Ширина страницы 
    WIZ_CYDLG     Высота страницы 
    WIZ_CXBMP      Ширина графического изображения 
    WIZ_BODYX      Координата Х начала рабочей области страницы 
    WIZ_BODYCX    Ширина рабочей области страницы (не включающая ширину 
графического изображения) 

    Значения этих переменных приводятся в логических единицах, определяемых 
относительно диалогового окна. Для доступа к вышеуказанным переменным 
необходимо включить в файл ресурсов программы стандартный библиотечный файл PRSHT.H. 
    Стилевые переменные предназначены для того, чтобы создавать такие же 
мастера, как и в Windows. Эти переменные позволяют правильно размещать 
элементы управления на странице относительно области, занимаемой графическим 
изображением. Помните, однако, что если на странице нет изображения, то можно 
использовать всю рабочую область. В следующем примере первые две страницы 
содержат графическое изображение, а последняя страница — нет. 

    348                                    MFC: основы программирования 


    Пример работы с мастером 

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

    ; Определения диалоговых окон мастера 
    ^include <afxres.h> 
    ^include <prsht.h> 
    tinclude "ids.h" 
    wizbmp BITMAP bpl.bmp 
    PropertyMenu MENU ( 
    POPUP "^Wizard Demo" ( 
    
    MENUITEM "&Activate\tF2", IDM_ACTIVATE 
    MENUITEM "E&it\tF3", IDM_EXIT 
    ) 
    
    MENUITEM "SHelp", IDM_HELP 
    ) 
    PropertyMenu ACCELERATORS ( 
    VK_F2, IDM_ACTIVATE, VIRTKEY 
    VK_F3, IDM_EXIT, VIRTKEY 
    VK_F1, IDM_HELP, VIRTKEY 
    » 
    PropDialogI DIALOG 10, 10, WIZ_CXDLG, WIZ_CYDLG 
    Caption "Wizard Demo -- First Page" 
    STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU 1 WS_VISIBLE 
    ( 
    DEFPUSHBUTTON "One", IDD_ONE, WIZ_BODYX, 10, 32, 14, 
    
    WS_CHILD I WS_VISIBLE | WS_TABSTOP 
    PUSHBUTTON "Two", IDD_TWO, WIZ_BODYX, 34, 32, 14, 
    
    
    WS_CHILD | WS_VISIBLE | WS_TABSTOP 
    PUSHBUTTON "Three", IDD_THREE, WIZ_BODYX, 58, 32, 14, 
    
    WS_CHILD | WS_VISIBLE | WS_TABSTOP 
    LISTBOX IDD_LB1, WIZ_BODYX+66, 25, 50, 33, LBS__NOTIFY | 
    WS_VISIBLE | WS_BORDER | WS_VSCROLL I WS_TABSTOP 
    } 
    PropDialog2 DIALOG 10, 10, 110, 100 
    Caption "Wizard Demo -- Second Page" 
    STYLE WS_POPUP I WS_CAPTION | WS_SYSMENU | WS_VISIBLE 
    { 


    13. Листы свойств и мастера                                           349 


    DEFPUSHBUTTON "Invert", IDD_INVERT, WIZ_BODYX, 10, 32, 14, 
    WS_CHILD | WS_VISIBLE | WS_TABSTOP 
    
    AUTOCHECKBOX "Check Box I", IDD_CB1, WIZ_BODYX+66, 10, 70, 10 
    AUTOCHECKBOX "Check Box 2", IDD__CB2, WIZ_BODYX+66, 30, 70, 10 
    AUTOCHECKBOX "Check Box 3", IDD__CB3, WIZ_BODYX+66, 50, 70, 10 
    ) 
    PropDialog3 DIALOG 10, 10, 110, 100 
    Caption "Wizard Demo -- Third Page" 
    STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE 
    { 
    DEFPUSHBUTTON "Top", IDD_TOP, 11, 10, 32, 14, 
    
    WS_CHILD I WS_VISIBLE | WS_TABSTOP 
    PUSHBUTTON "Bottom", IDD_BOTTOM, 11, 34, 32, 14, 
    
    WS_CHILD I WS_VISIBLE I WS_TABSTOP 
    AUTORADIOBUTTON "Radio Button I", IDD_RB1, 66, 10, 70, 10 
    AUTORADIOBUTTON "Radio Button 2", IDD_RB2, 66, 30, 70, 10 
    AUTORADIOBUTTON "Radio Button 3", IDD_RB3, 66, 50, 70, 10 
    ) 
    Вот текст файла WIZ.H, содержащего объявления порождаемых классов: 
    //  WIZ.H 
    /* 
    Ниже определяются классы, предназначенные для создания диалоговых окон 
страниц листа свойств. На основе объектов страниц впоследствии будет создан 
объект мастера 
    */ 
    // Это класс для создания диалогового окна первой страницы 
    class CPropDialogI : public CPropertyPage 
    { 
    CBitmap m_bmp; 
    public: 
    CPropDialogI() : CPropertyPage0 (}; 
    BOOL OnInitDialog() ; 
    BOOL OnWizardFinish(); 
    BOOL OnSetActive <) ; 
    /* В реальных приложениях необходимо переопределять следующие функции: 
    
    void OnCancelO; // вызывается при нажатии кнопки Cancel 
    void OnWizardBack(); // вызывается при нажатии кнопки Back 
    void OnWizardNext(); // вызывается при нажатии кнопки Next 
    */ 
    afx_msg void On0ne(); 
    afx msg void OnTwoO; 


    350                                     MFC: основы программирования 


    afx msg void OnThree(); 
    afx_msg void OnListBoxO; 
    afx msg void OnPaintf); 
    DECLARE_MESSAGE_MAP () 
    ); 
    // Это класс для создания диалогового окна второй страницы 
    class CPropDialog2 : public CPropertyPage 
    ( 
    CBitmap m bmp; 
    public: 
    CPropDialog2() : CPropertyPage() {}; 
    BOOL OnInitDialog(); 
    BOOL OnWizardFinish(); 
    BOOL OnSetActive (); 
    /* В реальных приложениях необходимо переопределять следующие функции: 
    
    void OnCancelO; // вызывается при нажатии кнопки Cancel 
    void OnWizardBack(); // вызывается при нажатии кнопки Back 
    void OnWizardNext(); // вызывается при нажатии кнопки Next 
    */ 
    afx msg void OnInvertO; 
    afx msg void OnCBlO; 
    afx_msg void OnCB2(); 
    afx msg void OnCB3(); 
    afx msg void OnPaintO; 
    DECLARE_MESSAGE_MAP() 
    ); 
    
    // Это класс для создания диалогового окна третьей страницы 
    class CPropDialog3 : public CPropertyPage 
    { 
    public: 
    CPropDialog3() : CPropertyPage() (}; 
    BOOL OnInitDialog() ; 
    BOOL OnWizardFinish() ; 
    BOOL OnSetActive() ; 
    /* .В реальных приложениях необходимо переопределять следующие функции: 
    void OnCancelO; // вызывается при нажатии кнопки Cancel 
    void OnWizardBack(); // вызывается при нажатии кнопки Back 


    13. Листы свойств и мастера                                           351 


    void OnWizardNext (); // вызывается при нажатии кнопки Next 
    */ 
    afx msg void OnTopO; 
    afx msg void OnBottomO; 
    afx_insg void OnRBl(); 
    afx_msg void OnRB2(); 
    afx_msg void OnRB3(); 
    DECLARE_MESSAGE_MAP() 
    }; 
    // Это класс для создания листа свойств 
    class CSamplePropSheet : "public CPropertySheet 
    { 
    CPropDialogI pagel; // первая страница 
    CPropDialog2 page2; // вторая страница 
    CPropDialog3 радеЗ; // третья страница 
    public: 
    CSamplePropSheet () : CPropertySheet () 
    ( 
    Construct("Sample Wizard", this); 
    pagel.Construct("PropDialogI", 0) ; 
    pagel.Construct("PropDialog2", 0) ; 
    pagel.Construct("PropDialog3", 0) ; 
    AddPage(bpagel) ; 
    AddPage(&page2) ; 
    AddPage(&page3) ; 
    ) 
    }; 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    { 
    CSamplePropSheet m_PropSheet; // объект листа свойств 
    public: 
    CMainWin() ; 
    afx msg void OnActivate(); 
    afx_msg void OnExitO; 
    afx_msg void OnHelpO; 
    DECLARE_MESSAGE_MAP() 
    }; 
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    { 
    public: 


    352                                    MFC: основы программирования 


    BOOL Initlnstance (); 
    1; 
    Ниже приведен текст самой программы: 
    // Работа с мастером 
    
    #include <afxwin.h> 
    I  #include <afxdlgs.h> 
    |  #include <string.h> 
    |  #include "ids.h" 
    ^include "wiz.h" 
    CSamplePropSheet *PropSheetPtr; // указатель на объект листа свойств 
    char list[][40] = ( 
    "Red", 
    "Green", 
    "Yellow", 
    "Black", 
    "White", 
    ); 
    // Создание главного окна приложения 
    CMainWin:;CMainWin() 
    ( 
    Create(NULL, "Using A Wizard", 
    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "PropertyMenu"); 
    InitCommonControls () ; 
    
    // Загрузка таблицы быстрого доступа 
    if(!LoadAccelTable("PropertyMenu" ) ) 
    MessageBox ("Cannot Load Accelerators", "Error"); 
    PropSheetPtr = &m_PropSheetPtr; // сохранение указателя 
    // на объект листа свойств 
    ) 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance () 
    ( 
    m_pMainWnd = new CMainWin; 
    m pMainWnd->ShowWindow(m nCmdShow) ; 
    m_pMainWnd->UpdateWindow() ; 
    return TRUE; 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 


    13. Листы свойств и мастера                                           353 


    ON_COMMAND(IDM_ACTIVATE, OnActivate)                               Т 
    ON_COMMAND(IDM_EXIT, OnExit) 
    ON__COMMAND(IDM_HELP, OnHelp) 
    END__MESSAGE_MAP() 
    // Обработка сообщения IDM_ACTIVATE 
    afx__msg void CMainWin::OnActivate() 
    ( 
    
    m_PropSheet.SetWizardMode(); // активизирует режим мастера 
    m_PropSheet.DoModal(); // активизирует модальный лист свойств 
    } 
    // Обработка сообщения IDM_EXIT                                      ; 
    afx msg void CMainWin::OnExitО { 
    int response; 
    s 
    response = MessageBox("Quit the Program?", "Exit", MB YESNO);      ! 
    
    if (response == IDYES)                                               ^ 
    SendMessage(WM_CLOSE); // завершение приложения 
    i 
    // Обработка сообщения IDM HELP                                      ^ 
    afx_msg void CMainWin::OnHelp<) 
    ( 
    MessageBox ( "Wizard", "Help") ; 
    } 
    // Это очередь сообщений диалогового окна первой страницы            j 
    BEGIN_MESSAGE_MAP(CPropDialogI, CPropertyPage)                       ! 
    ON_WM_PAINT() 
    ON_COMMAND(IDD_ONE, OnOne) 
    ON_COMMAND(IDD_TWO, OnTwo) 
    ON_COMMAND(IDD_THREE, OnThree) 
    
    ON_LBN_DBLCLK(IDD_LB1, OnListBox) 
    END_MESSAGE_MAP() 
    // Инициализация диалогового окна первой страницы 
    BOOL CPropDialogI::OnInitDialog() 
    ( 
    int i; 
    
    CPropertyPage::OnInitDialogО; // Вызов функции базового класса 
    CListBox *lbptr = (CListBox *) GetDlgItem(IDD_LB1); 
    for(i=0; KNUMSTRINGS; i++) 
    lbptr->AddString(list[i]) ; 
    ibptr->SetCurSel (0); 
    < 


    354                                     MFC: основы программирования 


    m bmp.LoadBitmap("wizbmp"); 
    return TRUE; 
    ! 
    // Вывод изображения 
    afx_msg void CPropDialogI::OnPaint() 
    ( 
    CPaintDC DC(this) ; 
    CDC memDC; 
    
    // создание совместимого контекста устройства 
    memDC.CreateCompatibleDC(&DC) ; 
    
    // выбор изображения в контекст области памяти 
    memDC.SelectObject(&m_bmp) ; 
    
    // Копирование изображения в контекст окна 
    DC.BitBlt(0, 0, 129, 226, bmemDC, 0, О, SRCCOPY) ; 
    ] 
    
    I / Активизация первой страницы 
    afx_msg BOOL CPropDialogI::OnSetActive() 
    ( 
    PropSheetPtr->SetWizardButtons(PSWIZB_NEXT) ; 
    return TRUE; 
    ) 
    // Заменитель функции OnWizardFinish() класса CPropDialogI 
    afx_msg BOOL CPropDialogI::OnWizardFinish() 
    ( 
    MessageBox("Wizard Finished", "First Page"); 
    return TRUE; 
    
    // Обработка сообщения IDD ONE 
    afx msg void CPropDialogI::0n0ne() 
    ( 
    MessageBox ( "One", "Button Pressed"); 
    ) 
    
    // Обработка сообщения IDD_TWO 
    afx msg void CPropDialogI::OnTwo() 
    { 
    MessageBox("Two", "Button Pressed"); 
    
    // Обработка сообщения IDD_THREE 
    afx msg void CPropDialogI::OnThree() 
    ( 


    13. Листы свойств и мастера                                           355 


    MessageBox ("Three", "Button Pressed"); 
    } 
    // Обработка идентификационных сообщений списка 
    afx_msg void CPropDialogI::OnListBox() 
    ( 
    int it
    char str[255] ; 
    CListBox *lbptr = (CListBox *) GetDlgItem(IDD_LB1); 
    i = !bptr->GetCurSel (); 
    lbptr->GetText (i, str); 
    MessageBox (str, "Selection is"); 
    SetModified(l) ; 
    ) 
    
    // Это очередь сообщений диалогового окна второй страницы 
    BEGIN_MESSAGE_MAP(CPropDialog2, CPropertyPage) 
    ON_WM_PAINT() 
    ON_COMMAND(IDD_INVERT, Onlnvert) 
    ON_COMMAND(IDD_CB1, OnCBl) 
    ON_COMMAND(IDD_CB2, OnCB2) 
    
    ON_COMMAND(IDD_CB3, ОпСВЗ) 
    END_MESSAGE_MAP() 
    // Инициализация диалогового окна второй страницы 
    BOOL CPropDialog2::OnInitDialog() 
    ( 
    CPropertyPage::OnInitDialogО; // Вызов функции базового класса 
    CButton *cbptr = (CButton *) GetDlgItem(IDD_CB1); 
    cbptr->SetCheck(1) ; 
    m bmp.LoadBitmap("wizbmp"); 
    return TRUE; 
    } 
    // Вывод изображения 
    afx msg void CPropDialog2::OnPaint() 
    ( 
    CPaintDC DC(this) ; 
    CDC memDC; 
    
    // создание совместимого контекста устройства 
    memDC.CreateCompatibleDC(&DC) ; 
    // выбор изображения в контекст области памяти                    : 


    356                                    MFC: основы программирования 


    memDC . Select-Object ( &гп bmp) ; 
    
    // Копирование изображения в контекст окна 
    DC.BitBlt(0, 0, 129, 226, SmernDC, 0, О, SRCCOPY); 
    // Активизация второй страницы 
    afx_msg BOOL CPropDialog2::OnSetActive() 
    ( 
    PropSheetPtr->SetWizardButtons(PSWIZB_NEXT I PSWIZB^BACK); 
    return TRUE; 
    \ 
    II Заменитель функции OnWizardFinish() класса CPropbialog2 
    afx_msg BOOL CPropDialog2::OnWizardFinish() 
    ( 
    
    MessageBox("Wizard Finished", "Second Page"it
    return TRUE; 
    ) 
    // Обработка сообщения IDD INVERT 
    afx msg void CPropDialog2::0nlnvert() 
    1 
    CButton *lbptrl = (CButton *) GetDlgItem(IDD_CBl) ; 
    CButton *lbptr2 = (CButton *) GetDlgItem(IDD_CB2) ; 
    CButton *lbptr3 = (CButton *) GetDlgItem(IDD_CB3); 
    
    // инвертирование состояния контрольных переключателей 
    if(lbptrl->GetCheck()) lbptrl->SetCheck(0) ; 
    else lbptrl->SetCheck(1) ; 
    if(lbptr2->GetCheck()) lbptr2->SetCheck(0) ; 
    else lbptr2->SetCheck(l) ; 
    if (lbptr3->GetCheck()) lbptr3->SetCheck(0) ; 
    else lbptr3->SetCheck(1) ; 
    SetModified(l) ; 
    ) 
    
    // Обработка сообщения IDD CB1 
    afx msg void CPropDialog2::OnCBl() 
    { 
    SetModified(l) ; 
    ) 
    
    // Обработка сообщения IDD_CB2 
    afx_msg void CPropDialog2::OnCB2() 
    1 
    SetModified(l) ; 


    13. Листы свойств и мастера                                           357 


    // Обработка сообщения IDD_CB3                                       i 
    afx msg void CPropDialog2::OnCB3<) 
    ( 
    SetModified(l) ; 
    } 
    
    // Это очередь сообщений диалогового окна третьей страницы 
    BEGIN_MESSAGE_MAP(CPropDialog3, CPropertyPage)                       J 
    ON_COMMAND(IDD_TOP, OnTop)                                           ' 
    ON_COMMAND(IDD_BOTTOM, OnBottom) 
    ON_COMMAND(IDD_RB1, OnRBl) 
    ON_COMMAND(IDD_RB2, OnRB2) 
    
    ON_COMMAND(IDD_RB3, OnRB3) 
    END_MESSAGE_MAP() 
    // Инициализация диалогового окна третьей страницы 
    BOOL CPropDialog3::OnInitDialog() 
    ( 
    CPropertyPage::OnInitDialog(); // Вызов функции базового класса 
    CButton *cbptr = (CButton *) GetDlgItem(IDD_RBl); 
    cbptr->SetCheck(l) ; 
    return TRUE; 
    ) 
    // Активизация третьей страницы 
    afx_msg BOOL CPropDialog3::OnSetActive() 
    ( 
    PropSheetPtr->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH); 
    return TRUE; 
    i 
    // Заменитель функции OnWizardFinish() класса CPropDialog3 
    afx_msg BOOL CPropDialog3::OnWizardFinish() 
    ( 
    MessageBox("Wizard Finished", "Third Page"); 
    return TRUE; 
    } 
    
    // Обработка сообщения IDD TOP 
    afx msg void CPropDialog3::OnTop () 
    ( 
    CButton *lbptrl = (CButton *) GetDlgItem(IDD_RBl) ; 
    CButton *lbptr2 = (CButton *) GetDlgItem(IDD_RB2) ; 
    CButton *lbptr3 = (CButton *) GetDlgItem(IDD_RB3) ; 


    358                                     MFC: основы программирования 


    // сброс нижних кнопок 
    lbptr2->SetCheck(0) ; 
    lbptr3->SetCheck(0) ; 
    
    lbptrl->SetCheck(1) ; // установка верхней кнопки 
    SetModified(l) ; 
    // Обработка сообщения IDD_BOTTOM 
    afx msg void CPropDialog3::OnBottom() 
    ( 
    CButton *lbptrl = (CButton *) GetDlgItem(IDD_RBl); 
    CButton *lbptr2 = (CButton *) GetDlgItem(IDD_RB2); 
    CButton *lbptr3 = (CButton *) GetDlgItem(IDD_RB3); 
    
    // сброс верхних кнопок 
    lbptr2->SetCheck (0) ; 
    lbptrl->SetCheck (0) ; 
    lbptr3->SetCheck (1); // установка нижней кнопки 
    SetModified(l) ; 
    } 
    
    // Обработка сообщения IDD_RB1 
    afx_msg void CPropDialog3::OnRBl() 
    { 
    SetModified(l) ; 
    ) 
    
    // Обработка сообщения IDD_RB2 
    afx_msg void CPropDialog3::OnRB2() 
    { 
    SetModified(l) ; 
    
    // Обработка сообщения IDD RB3 
    afxJ-nsg void CPropDialog3 : :OnRB3 () 
    ( 
    SetModified(l) ; 
    ) 
    CApp App; // создание экземпляра приложения 
    Пример работы программы показан на рис. 13.3. 


    13. Листы свойств и мастера                                           359 


    Рис. 13.3. Пример работы с мастером 


    360                                         MFC: основы программирования 

    Как видите, чтобы преобразовать лист свойств, созданный в предыдущем 
примере, в мастера, потребовалось совсем немного изменений. В частности, 
функция OnSetActiveQ была переопределена для каждой страницы потому, что 
каждый раз при активизации страниц на них должны устанавливаться кнопки 
мастера. Кроме того, в текст программы была добавлена глобальная переменная 
PropSheetPtr. Она используется внутри функции CMainWinQ для передачи указателя 
на объект листа свойств. При активизации очередной страницы этот указатель 
также используется для вызова функции SetWizardButtonsQ. Поскольку функция 
OnSetActive() является членом класса CPropertyPage (а не CPropertySheet), а 
функция SetWizardButtonsQ является членом класса CPropertySheet (а не 
CPropertyPage), то вторая функция не может быть непосредственно вызвана внутри 
первой. Вместо этого она должна вызываться через объект листа свойств. 
    И наконец, заметьте, что последняя страница не содержит графического 
изображения. В данном случае позиция элементов управления, расположенных на 
странице, устанавливается без привязки к стилевой переменной WIZ_BODYX. 
Предполагается, что координата Х начала рабочей области страницы равна нулю. 
Таким образом, для элементов управления используется все пространство страницы. 

    Использование листов свойств и мастеров 

    Листы свойств и мастера повсеместно используются в современных приложениях 
Windows. Более того, применение мастеров как средства решения пользователем 
сложных задач становится скорее правилом, чем исключением. Фактически, одной 
из определяющих характеристик современного приложения является использование в 
нем листов свойств и/или мастеров. Поэтому, если вы столкнулись с 
необходимостью представить на экране большое число опций или произвести 
сложную последовательность действий, настоятельно рекомендуем использовать 
листы свойств или мастера. Это гораздо лучше, чем работать с большими и 
громоздкими диалоговыми окнами. 

    13. Листы свойств и мастера                                           361