    Строки состояния 

    Часто в приложении возникает необходимость информировать пользователя о 
состоянии определенных переменных, атрибутов или параметров программы. Раньше 
в каждой программе это реализовывалось по-своему. Но в новых версиях Widows 
специально для этих целей введен новый элемент управления, называемый строкой 
состояния, или окном состояния. Обычно это особое текстовое поле (или 
несколько полей), размещающееся в нижней части окна программы. В нем 
отображается информация, относящаяся к приложению. В MFC строки состояния 
описываются с помощью класса CStatusBarCtrI. 

    Примечание: Работа со строками состояния может вестись также через старый 
класс CStatusBor. Но в новых версиях Windows рекомендуется использовать класс CStatusBarCtrI. 


    Создание строки состояния 

    Для создания строки состояния необходимо сначала объявить объект типа 
CStatusBarCtrI, а затем вызвать функцию CreateQ, которая имеет такой прототип: 

    BOOL CStatusBarCtrI::Create(DWORD dwStyle, const RECT &SizePos, CWnd 
*0wner, UINT ID} ; 

    Здесь параметр dwStyle определяет стиль строки состояния. Он обязательно 
должен включать стандартный стиль WS_CHILD. Кроме него, обычно указываются 
стили WS_VISIBLE и WS_BORDER. Определено также несколько специальных стилей 
строк состояния, описание которых приведено в табл. 12.1. Положение и размеры 
строки состояния приводятся в структуре типа RECT, на которую ссылается 
параметр SizePos. Впрочем, данный параметр чаше всего игнорируется (если 
только не указан стиль CCS_NORESIZE). Вместо этого размер строки состояния 
устанавливается автоматически в соответствии с размерами ее родительского 
окна. Указатель на объект родительского окна передается в параметре Owner. 
Идентификатор строки передается в параметре ID. Функция CreateQ возвращает 
ненулевое значение при успешном завершении и ноль — в противном случае. 
    Когда строка состояния создана, она обычно разбивается на две части 
(называемых полями), в каждой из которых отображается своя доля информации. 
(Впрочем, вполне допускается создавать строки, не разделенные на части.) 
Запись текста в каждое поле осуществляется раздельно. Ссылка на поля строки 
состояния осуществляется по их индексам. 

    Таблица 12.1. Стили строки состояния 

    Константа                 Стиль 

    CCS_BOTTOM             Строка состояния располагается внизу родительского окна 
    CCS_NODIVIDER          Между строкой состояния и родительским окном нет 
разделительной линии 
    CCS_NOHILITE            Строка состояния не выделяется цветом при активизации 


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


    Константа                 Стиль 

    CCS_NOMOVEY           Строка состояния не может масштабироваться по вертикали 
    CCS_NOPARENTALIGN     Строка состояния не перемещается автоматически между 
верхней и нижней частью родительского окна 
    CCS_NORESIZE            Размер и положение строки состояния указаны в 
параметре SivPos функции CreateQ и не могут быть изменены 
    CCS_TOP                  Строка состояния располагается вверху 
родительского окна (это установка по умолчанию) 

    Определение количества полей строки состояния 

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

    BOOL CStatusBarCtrl::SetParts (int NumParts, int *MidthArray} ; 

    Здесь параметр NumParts задает количество полей строки состояния. Это 
значение не должно превышать 256. Целочисленный массив, на который указывает 
параметр WidthArray, содержит координаты правой границы каждого поля. Таким 
образом, число элементов массива должно равняться значению параметра NumParts. 
Функция возвращает ненулевое значение при успешном завершении и ноль — в 
противном случае. 

    Задание текста для строки состояния 

    Когда строка состояния разделена на поля, с каждым из них можно связать 
свою текстовую строку. Для этого предназначена функция SetTextQ, имеющая 
следующий прототип: 

    BOOL CStatusBarCtrl::SetText(LPCSTR IpszString, int Part, int How} ; 

    Здесь параметр IpszString является указателем на строку текста, которая 
будет отображена в соответствующем поле, определяемом параметром Part. Этот 
параметр содержит индекс поля, в которое будет записан текст. Для всех строк 
состояния индексация начинается с нуля. Параметр How определяет способ 
отображения текста. Обычно этот параметр равен нулю. При этом текст 
отображается во "вдавленном" поле, т.е. как бы внутри поля. Если задать 
значение SBT_POPOUT, текст будет появляться в "выпуклом" поле, т.е. как бы 
поверх поля. 

    Использование строки состояния 

    Работа со строками состояния удивительно проста. Вот общая процедура их использования: 
    1.  Создайте строку состояния. 
    2.  Определите число полей строки. 
    3.  Задайте текст для каждого поля. 
    4.  При возникновении изменений обновляйте текст каждого поля. 

    12. Строки состояния, закладки и окна просмотра деревьев              295 

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

    Рис. 12.1. Пример работы со строкой состояния 

    В программе используется следующий файл ресурсов: 

    ; STATUS.RC 
    ^include <afxres.h> 
    ftinclude "ids.h" 
    StatusMenu MENU ( 
    POPUP "SDemo" 
    { 
    
    MENUITEM "&Dialog\tF2", IDM_DIALOG 
    MENUITEM "E&xit\tF3", IDM_EXIT 
    > 
    MENUITEM "SHelp", IDM_HELP 
    } 


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


    StatusMenu ACCELERATORS 
    { 
    VK_F2, IDM_DIALOG, VIRTKEY 
    VK_F3, IDM_EXIT, VIRTKEY 
    VK_F1, IDM_HELP, VIRTKEY 
    } 
    StatusDialog DIALOG 18, 18, 150, 92 
    CAPTION "Demonstrate a Status Bar" 
    STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU 
    ( 
    PUSHBUTTON "Reset", IDD_RESET, 92, 34, 37, 14, 
    
    WS_CHILD I WS_VISIBLE ] WSJTABSTOP 
    PUSHBUTTON "OK", IDOK, 92, 53, 37, 14, 
    
    WS_CHILD I WS_VISIBLE | WS_TABSTOP 
    EDITTEXT IDD_EB1, 10, 10, 30, 12, ES_LEFT | WS_CHILD | 
    WS_VISIBLE I WS_BORDER 
    
    AUTOCHECKBOX "Option I", IDD_CB1, 10, 40, 48, 12 
    AUTOCHECKBOX "Option 2", IDD_CB2, 10, 60, 48, 12 
    ) 

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

    // IDS.H 
    #define IDMJ3IALOG 100 
    tdefine IDM_HELP   101 
    #define IDM_EXIT   102 
    ftdefine IDD__EB1    200 
    #define IDD_CB1    201 
    #define IDD__CB2    202 
    #define IDD__RESET 203 
    #define IDD_RB1    204 
    #define IDD_RB2    205 
    #define IDD_RB3    206 
    tdefine IDD_RB4    207 
    #define IDD_RB5    208 
    ftdefine IDD_RB6    209 
    #define IDD_SPIN   210 
    ^define IDD_STATUS 300 
    #define ID_TAB     400 
    ftdefine NUMPARTS   3 
    Это текст файла STATUS.H, содержащего объявления порождаемых классов: 
    // STATUS.H 
    // Это класс для создания главного окна 


    12. Строки состояния, закладки и окна просмотра деревьев              297 


    class CMainWin : public CFrameWnd 
    ( 
    public: 
    CMainWin() ; 
    
    // объявления обработчиков меню 
    afx msg void OnDialogO; 
    afx msg void OnExitO; 
    afx_msg void OnHelpO; 
    DECLARE_MESSAGE_MAP() 
    
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    ( 
    public: 
    BOOL Initlnstance (); 
    ); 
    // Это класс для создания диалогового окна 
    class CSampleDialog : public CDialog 
    ( 
    
    CStatusBarCtrl m_Status; // объект строки состояния 
    CSpinButtonCtrl m Spin; // объект поля с прокруткой 
    public: 
    CSampleDialog(char *DialogName, CMnd *0wner) : 
    CDialog(DialogName, Owner) {) 
    BOOL OnInitDialog (); 
    
    afx_msg void OnVScroll(UINT SBCode, UINT Pos, 
    CScrollBar *SB) ; 
    afx_msg void OnCBlO; 
    afx_msg void OnCB2() ; 
    afx msg void OnResetO; 
    DECLARE_MESSAGE_MAP() 
    Вот текст самой программы: 
    // Работа со строкой состояния 
    
    #include <afxwin.h> 
    ftinclude <afxcmn.h> 
    ^include <string.h> 
    #include "status.h" 
    #include "ids.h" 
    CMainWin::CMainWin() 
    ( 
    Create(NULL, "Using A Status Bar", 


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


    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "StatusMenu"); 
    InitCommonControls(); 
    
    // Загрузка таблицы быстрого доступа 
    if('LoadAccelTable("StatusMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    } 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance() 
    ( 
    m_pMainWnd = new CMainWin; 
    m pMainWnd->ShowWindow(m_nCmd3how) ; 
    m pMainWnd->UpdateWindow(); 
    return TRUE; 
    } 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_COMMAND(IDM_DIALOG, OnDialog) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    
    // Обработка сообщения IDM_DIALOG 
    afx_msg void CMainWin::OnDialog() 
    ( 
    CSampleDialog diagOb("StatusDialog", this); 
    diagOb.DoModal(); // активизирует модальное диалоговое окно 
    } 
    
    // Обработка сообщения IDM_EXIT 
    afx_msg void CMainWin::OnExit () 
    ( 
    int response; 
    response = MessageBox("Quit the Program?", "Exit", MB_YESNO) ; 
    if(response == IDYES) 
    SendMessage(WM CLOSE); // завершение приложения 
    } 
    
    // Обработка сообщения IDM_HELP 
    afx msg void CMainWin::OnHelp() 
    ( 
    MessageBox("Status Bar", "Help"); 
    } 
    
    // Это очередь сообщений диалогового окна 
    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog) 


    12. Строки состояния, закладки и окна просмотра деревьев              299 


    ON_WM_VSCROLL() 
    ON_COMMAND(IDD_CB1, OnCBl) 
    ON_COMMAND(IDD_CB2, OnCB2) 
    ON_COMMAND(IDD_RESET, OnReset) 
    END_MESSAGE_MAP() 
    
    // Инициализация диалогового окна 
    BOOL CSampleDialog: .-OnInitDialog () 
    { 
    RECT r; 
    int i; 
    int parts[NUMPARTS] ; 
    CDialog::OnInitDialog(); // Вызов функции базового класса 
    
    // создание поля с прокруткой 
    г.left = г.top = 10; 
    г.right = г.bottom = 50; 
    m_Spin.Create(WS_VISIBLE | WS_CHILD | WS_BORDER | 
    UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 
    r, this, IDD_SPIN) ; 
    
    // задание диапазона поля с прокруткой 
    m_Spin.SetRange(0, 20); 
    
    // создание приятельского окна 
    m_Spin.SetBuddy(GetDlgItem(IDD_EB1)) ; 
    
    // задание начального состояния поля с прокруткой 
    m_Spin.SetPos(10) ; 
    
    // определение размеров полей строки состояния 
    GetClientRect(&r) ; 
    for (i=l; 1<=NUMPARTS; 1++) 
    parts[i-1] = r.right/NUMPARTS *i; 
    // создание строки состояния 
    r.left = r.top =0; // эти координаты игнорируются 
    г.right = r.bottom = 0; 
    
    m_Status.Create(WS_VISIBLE I WS_CHILD, 
    r, this, IDD_STATUS) ; 
    
    // создание полей и присвоение им текста 
    m_Status.SetParts(NUMPARTS, parts) ; 
    m_Status.SetText("Spin Ctrl: 10", 0, 0) ; 
    m_Status.SetText("Option 1: OFF", 1, 0); 
    m_Status.SetText("Option 2: OFF", 2, 0) ; 
    return TRUE; 
    } 
    
    // Обработка сообщения WM_VSCROLL, 
    // генерируемого полем с прокруткой 
    afx_msg void CSampleDialog::OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB) 


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


    char str [255] ; 
    
    // Параметр SB должен указывать на объект поля с прокруткой 
    if (SB != (CScrollBar *)&m_Spin) return; 
    wsprintf (str, "Spin Ctrl: °6d", Pos) ; 
    m_Status.SetText(str, 0, 0) ; 
    } 
    
    // Обработка сообщения IDD CB1 
    afx_msg void CSampleDialog::OnCBl() f 
    CButton *cbptr = (CButton *) GetDlgItem(IDD_CBl); 
    if(cbptr->GetCheck()) 
    m_Status.SetText("Option 1: ON", 1, 0) ; 
    else 
    m_Status.SetText("Option 1: OFF", 1, 0) ; 
    1 
    
    // Обработка сообщения IDD_CB2 
    afx_msg void CSampleDialog::OnCB2() 
    ( 
    CButton *cbptr = (CButton *) GetDlgItem(IDD_CB2) ; 
    if(cbptr->GetCheck ()) 
    m_Status.SetText("Option 2: ON", 2, 0) ; 
    else 
    m_Status.SetText("Option 2: OFF", 2, 0) ; 
    // Обработка сообщения IDD_RESET 
    afx msg void CSampleDialog::OnReset() 
    ( 
    CButton *cbptr = (CButton *) GetDlgItem(IDD_CBl) ; 
    cbptr->SetCheck(0) ; 
    cbptr = (CButton *) GetDlgItem(IDD_CB2) ; 
    cbptr->SetCheck(0) ; 
    m_Spin.SetPos(10) ; 
    m_Status.SetText("Spin Ctrl: 10", 0, 0) ; 
    m_Status.SetText("Option 1: OFF", 1, 0) ; 
    m_Status.SetText("Option 2: OFF", 2, 0) ; 
    ) 
    CApp Арр; // создание экземпляра приложения 
    Строка состояния в программе создается и инициализируется внутри функции 
OnInitDialogO. Вначале строка разбивается на три равных поля. Это выполняется с 


    12. Строки состояния, закладки и окна просмотра деревьев              301 


    помощью функции GetClientRect(). Данная функция возвращает текущий размер 
рабочей области окна. Ее прототип таков: 

    void CWnd::GetClientRect(RECT *Rect) const; 

    Здесь параметр Reel является указателем на структуру типа RECT, содержащую 
размеры рабочей области. 
    Поскольку строка состояния содержит три поля, ширина диалогового окна 
(получаемая с помощью функции GetCIientRectQ) делится на 3. Таким образом, 
диалоговое окно делится на три части. Конечная координата каждой из них 
становится координатой конца соответствующего поля строки состояния и 
записывается в массив parts. Помните, что необходимо указывать конечные 
координаты полей, а не их длину. Когда все поля инициализированы, в них 
отображается начальный текст. Если состояние какого-либо элемента в диалоговом 
окне изменяется, текст в соответствующем поле строки состояния автоматически обновляется. 
    В данной программе демонстрируются лишь простейшие функции работы со 
строками состояния. Класс CStatusBarCtrl содержит множество других функций, 
изучить которые вы сможете самостоятельно. 

    Закладки 

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

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


    Создание закладок 

    Для создания закладок необходимо сначала объявить объект типа CTabCtrl, a 
затем вызвать функцию CreateQ, имеющую такой прототип: 

    BOOL CTabCtrl:: Create (DWORD dwStyle, const RECT S-SizePos, CWnd *0wner, 
UINT ID}; 

    Здесь параметр dwStyle определяет стиль закладок. Он обычно включает 
стандартные стили WS_CHILD и WS_VISILE. Имеются также специальные стили 
закладок (табл. 12.2): 

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


    Таблица 12.2. Стили закладок 

    Константа              Стиль 

    TCS_BUTTONS         Используются кнопки, а не закладки 
    TCS_FIXEDWIDTH     Все закладки имеют одинаковый размер 
    TCS_MULTILINE       Могут использоваться несколько рядов закладок 
    TCS_TOOLTIPS        С закладками связаны всплывающие подсказки 
    TCS_SINGLBL1NE      Все закладки размещаются на одной линии (для их 
просмотра может использоваться полоса прокрутки) 

    Параметр SizePos ссылается на структуру типа RECT, которая содержит размер 
и положение области закладок. Указатель на объект родительского окна 
содержится в параметре Owner. Идентификатор закладок передается в параметре 
ID. Функция CreateQ возвращает ненулевое значение при успешном завершении и 
ноль — в противном случае. 

    Инициализация закладок 

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

    BOOL CTabCtrl::Insertltem(int Index, TC_ITEM * Item); 

    Здесь параметр Index является индексом инициализируемой закладки. Отсчет 
индексов начинается с нуля. Параметр Item является указателем на структуру 
типа ТС_1ТЕМ, определяющую отдельную закладку. Функция Insertltem() должна 
вызываться для каждой отдельной закладки. Функция возвращает ненулевое 
значение при успешном завершении и ноль — в противном случае. 
    Тип ТС_1ТЕМ определен следующим образом: 

    typedef struct _TC_ITEM 
    ( 
    UINT mask; 
    UINT IpReservedl; 
    UINT lpReserved2; 
    LPSTR pszText; 
    int cchTextMax; 
    int ilmage; 
    LPARAM IParam; 
    } TC_ITEM; 

    Значение, содержащееся в поле mask данной структуры, определяет, какое из 
полей — pszText, ilmage или IParam — содержит нужную информацию. Поле mask 
может принимать одно из следующих значений (табл. 12.3): 

    Таблица J2.3. Значения поля mask 

    Значение             Смысл 

    TCIF_IMAGE          Информация содержится в поле ilmage 
    TCIF_PARAM          Информация содержится в поле IParam 
    TCIF_TEXT           Информация содержится в поле pszText 


    12. Строки состояния, закладки и окна просмотра деревьев              303 

    При инициализации закладки в поле pszText содержится указатель на строка 
которая будет отображена в поле заголовка закладки. При получении информации i 
закладке в данном поле должен находиться указатель на массив, в который буде 
записан текст заголовка. В этом случае поле cchTextMax определяет размер этоп массива. 
    Если с закладками связан список графических изображений, то в поле ilmage 
буде' находиться индекс изображения, связанного с конкретной закладкой. В 
противно]\ случае значением этого поля должно быть -1. (Список изображений 
определяет набо[ растровых образов, к которым можно получить доступ через 
индекс соответствующе! закладки. Такой список описывается с помощью класса 
CImageUst.) Параметр IParan может содержать информацию, относящуюся к 
конкретному приложению. 

    Некоторые функции класса CTabCtrl 

    Класс CTabCtrl содержит множество функций, с помощью которых можнс 
полностью управлять закладками. Ниже описаны лишь самые основные из эти> функций. 
    Часто возникает необходимость узнать, какая именно закладка в данный момет 
выбрана. Для этого предназначена функция GetCurSel(), имеющая такой прототип: 

    int CTabCtrl::GetCurSel() const; 

    Функция возвращает индекс выбранной закладки или -1, если ни одна из 
закладок еще не была выбрана. 
    Для выбора закладки предназначена функция SetCurSelQ, прототип которой следующий: 

    int CTabCtrl::SetCurSel (int Index); 

    Здесь параметр Index является индексом выбираемой закладки. Функция 
возвращает индекс предыдущей выбранной закладки или -1, если ни одна из 
закладок не была выбрана. 
    Для получения информации о закладке необходимо использовать функцию 
GetItemQ, которая имеет такой прототип: 

    BOOL CTabCtrl::GetItem(int Index, TC_ITEM * Item) const; 

    Здесь параметр Index содержит индекс закладки. Параметр Item является 
указателем на структуру типа ТС_ГГЕМ, в которую передается информация о 
требуемой закладке. Перед вызовом необходимо инициализировать поле mask этой 
структуры, чтобы определить, какая именно информация должна быть получена. 
После завершения функции в этой структуре будет содержаться запрашиваемая 
информация. Функция возвращает ненулевое значение при успешном завершении и 
ноль — в противном случае. 
    Чтобы изменить информацию, связанную с закладкой при ее инициализации, 
необходимо использовать функцию Setltem(), которая имеет следующий прототип: 

    BOOL CTabCtrl::Setltem(int Index, TC_ITEM * Item}; 

    Здесь параметр Index содержит индекс закладки. Параметр Item является 
указателем на структуру типа ТС_1ТЕМ, содержащую задаваемую информацию. 
Необходимо инициализировать поле mask этой структуры, чтобы определить, какую именно 

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

    информацию необходимо изменить. Функция возвращает ненулевое значение при 
успешном завершении и ноль — в противном случае. 
    Хотя в последующих примерах не используется функция AdjustRectQ, в 
реальных приложениях в ней часто возникает необходимость. Эта функция имеет 
такой прототип: 

    void CTabCtrl::AdjustRect(BOOL How, RECT *Rect}; 

    Она предназначена для получения размеров рабочей области, используемой 
закладками. Помните, что при создании закладок их окно включает в себя как 
сами закладки, так и ту область, в которой будет выводиться информация либо 
отображаться всплывающее диалоговое окно. Эта область называется рабочей 
областью закладок. Она может включать в себя другие элементы. Именно поэтому 
необходимо знать ее размеры. Если параметр How не равен нулю, то параметр Rect 
должен указывать на структуру типа RECT, в которой записаны размеры рабочей 
области. После завершения функции в этой структуре будут находиться размеры 
всей области, занимаемой закладками. Если параметр How равен нулю, то параметр 
Rect, наоборот, должен указывать на структуру, содержащую размеры всего окна, 
а после завершения функции в эту структуру будут записаны размеры рабочей области. 

    Сообщения закладок 

    Когда пользователь активизирует одну из закладок, программе посылается 
сообщение WM_NOTIFY. В этом случае с ним могут быть связаны три 
идентификационных кода: TCN_SELCHANGE, TCN.SELCHANGING и TCN_KEYDOWN. Код 
TCN_SELCHANGING генерируется перед тем, как будет выбрана новая закладка. Код 
TCN_SELCHANGE генерируется после выбора новой закладки. Код TCN_KEYDOWN 
генерируется при нажатии клавиши. 
    Сообщения, поступающие от закладок, обрабатываются с помощью стандартной 
макрокоманды ON_NOTIFY(), общий вид которой таков: 

    ONJMOTIFY(notification-code, ID, handler} 

    Здесь параметр notification-code — это идентификационный код сообщения. В 
параметре ID передается идентификатор закладок. Имя функции-обработчика 
сообщения указывается в параметре handler. Она должна иметь следующий прототип: 

    afx_msg void handler(NMHDR *hdr, LRESULT * Result}; 

    Для кодов TCN_SELCHANGE и TCN_KEYDOWN параметр Result не используется. Для 
кода TCN_SELCHANGING функция должна устанавливать этот параметр в ноль, чтобы 
разрешить смену закладки, или в любое значение, отличное от нуля, чтобы 
запретить это. 

    Пример работы с закладками 

    В следующей небольшой программе демонстрируется работа с закладками. 
Закладки создаются как дочернее окно главного окна программы. Затем в этом 
окне объявляются три закладки, называемые One, Two и Three. При выборе одной 
из них в окне отображается строка сообщения, указывающая на сделанный выбор. 
Пример работы программы показан на рис. 12.2. 

    12. Строки состояния, закладки и окна просмотра деревьев              305 


    Рис. 12.2. Первый пример работы с закладками 

    В данной программе не требуется файл ресурсов. Библиотечный файл IDS.H тот 
же, что и ранее. Вот текст файла ТАВ.Н, содержащего объявления порождаемых классов: 

    //  ТАВ.Н 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    { 
    CTabCtrl m_Tab; // объект закладок 
    public: 
    CMainWin() ; 
    afx_msg void OnTabChange(NMHDR *hdr, LRESULT *NotUsed); 
    DECLARE_MESSAGE_MAP() 
    }; 
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    { 
    public: 
    BOOL Initlnstance() ; 
    ); 
    * 


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

    Это текст самой программы: 

    // Простая демонстрационная программа работы с закладками 
    
    tinclude <afxwin.h> 
    #include <afxcmn.h> 
    finclude <string.h> 
    tinclude "ids.h" 
    ^include "tab.h" 
    CMainWin::CMainWin() 
    ( 
    Create(NULL, "Using A Tab Control"); 
    InitCommonControls (); 
    RECT r; 
    TC_ITEM tci; 
    
    // получение размеров рабочей области окна 
    GetClientRect(&r) ; 
    
    // создание закладок 
    m_Tab.Create(WS_VISIBLE I WS_CHILD, 
    r, this, ID_TAB) ; 
    tci.mask = TCIF_TEXT; 
    tci.ilmage = -1; 
    tci.pszText = "One"; 
    m_Tab.InsertItem(0, Stci); 
    tci.pszText = "Two"; 
    m_Tab.Insertltem(1, &tci) ; 
    tci.pszText = "Three"; 
    m_Tab.Insertltem(2, &tci); 
    ) 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance () 
    ( 
    m_pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow); 
    m_pMainWnd->UpdateWindow() ; 
    return TRUE; 
    1 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_NOTIFY(TCN_SELCHANGE, ID_TAB, OnTabChange) 


    12. Строки состояния, закладки и окна просмотра деревьев              307 


    END_MESSAGE_MAP() 
    // Обработка команд выбора закладок 
    afx_nisg void CMainWin::OnTabChange(NMHDR *hdr, LRESULT *NotUsed) 
    { 
    CClientDC DC(this) ; 
    char str[255]; 
    wsprintf(str, "Changed to Tab %d ", m_Tab.GetCurSel()+1) ; 
    DC.SetBkColor(RGB(200, 200, 200)); 
    DC.TextOut (40, 100, str, strlen(str)); 
    } 
    CApp App; // создание экземпляра приложения 

    В конструкторе CMainWin(), перед созданием закладок, производится БЫЗО! 
функции GetClientRect() для получения размеров главного окна. Размер области 
занимаемой закладками, устанавливается равным размеру рабочей области главного 
окна. Такая установка используется достаточно часто, хотя данные размеры могу] 
быть произвольными. Когда объект закладок создан, к нему добавляются три 
закладки. Каждый раз при получении сообщения WMJNOTIFYc кодом TCNJSELCHANGI в 
рабочей области окна закладок отображается строка сообщения, указывающая не 
то, какая именно закладка была выбрана. 

    Использование закладок 

    Закладки очень легко создавать, однако использовать их чуточку сложнее. 
Дело в том, что с каждой закладкой обычно связано некоторое диалоговое окно. 
Каждый раз при выборе новой закладки текущее диалоговое окно должно быть 
закрыто и отображено новое. Кроме того, в общем случае, размеры каждого 
диалогового окна должны соответствовать размерам рабочей области окна 
закладок. Есть и множество других тонкостей, которые не рассматриваются в 
данной книге. (Кстати, в следующей главе описываются листы свойств, которые во 
многих случаях гораздо легче использовать.) В данном параграфе, тем не менее, 
приводится общий метод использования закладок в приложении. Освоив азы, при 
желании, можно будет перейти к более сложным вещам, 
    Поскольку в любой момент времени может быть выбрана произвольная закладка, 
вместо модальных диалоговых окон необходимо использовать немодальные окна. 
Если помните, немодальное окно позволяет активизировать другие части 
приложения до того, как оно будет закрыто. (В этом его отличие от модального 
окна, которое должно быть закрыто перед использованием другой части 
приложения.) Обычно при выборе закладки программа закрывает текущее диалоговое 
окно и открывает следующее. Поскольку окно может быть закрыто в любой момент, 
необходимо предпринять соответствующие действия (такие, например, как 
сохранение установок) перед отображением нового окна. 

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

    В следующей программе демонстрируется использование закладок. В ней 
создаются три закладки, названные Options, Pitch и Page Size. С каждой из них 
связано отдельное немодальное окно. Первое из них взято из примера работы со 
строками состояния, рассмотренного ранее в данной главе. Остальные два созданы 
только для иллюстрации. Пример работы программы показан на рис. 12.3. 
    В программе используется следующий файл ресурсов: 

    ; TAB.RC 
    tinclude <afxres.h> 
    ^include "ids.h" 
    
    TabDialoql DIALOG 2, 16, 158, 106 
    STYLE HS_CHILD | WS_VISIBLE ] WS_POPUP 
    ( 
    
    PUSHBUTTON "Reset", IDD_RESET, 92, 34, 37, 14, 
    WS_CHILD I WS_VISIBLE | WS_TABSTOP 
    
    EDITTEXT IDD_EB1, 10, 10, 30, 12, ES_LEFT I WS_CHILD | 
    WS_VISIBLE | WS_BORDER 
    AUTOCHECKBOX "Option I", IDD_CB1, 10, 40, 48, 12 
    AUTOCHECKBOX "Option 2", IDD_CB2, 10, 60, 48, 12 
    ) 
    TabDidloq2 DIALOG 2, 16, 158, 106 
    STYLE WS_POPUP I WS_CHILD | WS_VISIBLE 
    ( 
    
    LTEXT "Choose Pitch", 199, 10, 10, 60, 12 
    AUTORADIOBUTTON "High Pitch", IDD_RB1, 10, 40, 48, 12 
    AUTORADIOBUTTON "Medium Pitch", IDD_RB2, 10, 60, 62, 12 
    AUTORADIOBUTTON "Low Pitch", IDD_RB3, 10, 80, 48, 12 
    > 
    
    TabDialog3 DIALOG 2, 16, 158, 106 
    STYLE WS_POPUP i WS_CHILD | WS_VISIBLE 
    { 
    LTEXT "Choose Page Size", 2, 10, 10, 70, 12 
    AUTORADIOBUTTON "Small", IDD_RB4, 10, 40, 48, 12 
    AUTORADIOBUTTON "Medium", IDD_RB5, 10, 60, 48, 12 
    AUTORADIOBUTTON "Large", IDD_RB6, 10, 80, 48, 12 > 

    Библиотечный файл IDS.H используется тот же, что и ранее. Вот текст файла 
TABDEMO.H, содержащего объявления порождаемых классов: 

    //  TABDEMO.H 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    ( 
    CTabCtrl m Tab; // объект закладок 
    public: 
    CMainWin () ; 


    12. Строки состояния, закладки и окна просмотра деревьев              309 


    afx_msg void OnTabChange(NMHDR *hdr, LRESULT *NotUsed) ; 
    DECLARE_MESSAGE_MAP() 
    }; 
    
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    { 
    public: 
    BOOL Initlnstance (); 
    ); 
    // Это класс для создания первого диалогового окна 
    class CTabDialogI : public CDialog 
    ( 
    
    CSpinButtonCtrl m_Spin; // объект поля с прокруткой 
    CStatusBarCtrl m_Status; // объект строки состояния 
    public: 
    CTabDialogI () : CDialog() () 
    BOOL OnInitDialog (); 
    
    afx_msg void OnVScroll(UINT SBCode, UINT Pos, 
    CScrollBar *SB) ; 
    afx_msg void OnCBl(); 
    afx_msg void OnCB2(); 
    afx msg void OnResetO; 
    DECLARE_MESSAGE_MAP() 
    
    // Это класс для создания второго диалогового окна 
    class CTabDialog2 : public CDialog 
    ( 
    public: 
    CTabDialog2 () : CDialogО {) 
    DECLARE_MESSAGE_MAP()                                             ; 
    1; 
    
    // Это класс для создания третьего диалогового окна 
    class CTabDialog3 : public CDialog                                   i 
    {                                                                          
                                                       I 
    public: 
    CTabDialog3 () : CDialog () {)                                        . 
    DECLARE_MESSAGE_MAP() 
    }; 


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

    Это текст самой программы: 

    // Использование закладок 
    #include <afxwin.h> 
    #include <afxcmn.h> 
    #include <string.h> 
    #include "ids.h" 
    #include "tabdemo.h" 
    
    // создание немодальных диалоговых окон 
    CTabDialogI DiagObI; 
    CTabDialog2 Diag0b2; 
    CTabDialogS Diag0b3; 
    
    // указатель на диалоговое окно 
    CDialog *CurDialog; 
    
    CMainWin::CMainWin() 
    ( 
    RECT r; 
    TC_ITEM tci; 
    r.left = r.top = 10; 
    r.bottom = 250; r.right = 275; 
    
    Create(NULL, "Using A Tab Control", WS_OVERLAPPEDWINDOW, r, 
    NULL, NULL) ; 
    InitCommonControls () ; 
    
    // получение размеров рабочей области окна 
    GetClientRect(&r) ; 
    
    // создание закладок 
    m_Tab.Create(WS_VISIBLE | WS_CHILD, r, this, ID_TAB) ; 
    tci.mask = TCIF_TEXT; 
    tci.ilmage = -1; 
    tci.pszText = "One"; 
    m_Tab.Insertltem(0, &tci); 
    tci.pszText = "Two"; 
    m_Tab. Insertltemd, &tci) ; 
    tci.pszText = "Three"; 
    m_Tab.Insertltem(2, &tci); 
    
    // активизация диалогового окна первой закладки 
    CurDialog = SDiagObI; 
    DiagObI.Create("TabDialogI", this) ; 
    ( 


    12. Строки состояния, закладки и окна просмотра деревьев              311 


    // Инициализация приложения 
    BOOL CApp::Initlnstance () 
    
    (                                                                   | 
    m pMainWnd = new CMainWin;                                        i 
    m_pMainWnd->ShowWindow(m_nCmdShow);                               j 
    m pMainWnd->UpdateWindow(); 
    return TRUE; 
    ; 
    
    // Это очередь сообщений приложения 
    BEGIN__MESSAGE_MAP(CMainWin, CFrameWnd) 
    
    ON_NOTIFY(TCN_SELCHANGE, ID_TAB, OnTabChange) 
    END_MESSAGE_MAP() 
    
    // Далее следуют очереди сообщений диалоговых окон закладок 
    BEGIN_MESSAGE_MAP(CTabDialogI, CDialog) 
    ON_WM_VSCROLL() 
    ON_COMMAND(IDD_CB1, OnCBl) 
    ON_COMMAND(IDD_CB2, OnCB2) 
    
    ON_COMMAND(IDD_RESET, OnReset) 
    END_MESSAGE_MAP() 
    
    BEGIN_MESSAGE_MAP(CTabDialog2, CDialog) 
    END_MESSAGE_MAP() 
    
    BEGIN_MESSAGE_MAP(CTabDialog3, CDialog) 
    END_MESSAGE_MAP() 
    // Инициализация первого диалогового окна 
    BOOL CTabDialogI::OnInitDialog() 
    ( 
    RECT r; 
    int i; 
    int parts[NUMPARTS]; 
    CDialog::OnInitDialog(); // Вызов функции базового класса 
    
    // создание поля с прокруткой 
    г.left = г.top = 10; 
    г.right = г.bottom = 50; 
    m_Spin.Create(WS_VISIBLE | WS_CHILD | WS_BORDER I 
    UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 
    r, this, IDD_SPIN) ; 
    
    // задание диапазона поля с прокруткой 
    m_Spin.SetRange(0, 20); 
    
    // создание приятельского окна 
    m_Spin.SetBuddy(GetDlgItem(IDD_EB1)) ; 
    
    // задание начального состояния поля с прокруткой 
    m_Spin.SetPos(10) ; 


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


    m_Status.SetText("Option 2: ON", 2, 0) ; 
    else 
    m_Status.SetText("Option 2: OFF", 2, 0) ; 
    i 
    
    // Обработка сообщения IDD_RESET 
    afx_msg void CTabDialogI::OnReset() 
    ( 
    CButton *cbptr = (CButton *) GetDlgItem(IDD_CBl) ; 
    cbptr->SetCheck(0) ; 
    cbptr = (CButton *) GetDlgItem(IDD_CB2) ; 
    cbptr->SetCheck (0) ; 
    m_Spin.SetPos(10); 
    m_Status.SetText("Spin Ctrl: 10", 0, 0) ; 
    m_Status.SetText("Option 1: OFF", 1, 0) ; 
    m_Status.SetText("Option 2: OFF", 2, 0) ; 
    1 
    // Обработка команд выбора закладок 
    afx_msg void CMainWin::OnTabChange(NMHDR *hdr, LRESULT *NotUsed) 
    ( 
    if(CurDialog) CurDialog->DestroyWindow() ; 
    
    switch(m_Tab.GetCurSel ()) { 
    case 0: 
    DiagObI.Create("TabDialogI", this) ; 
    CurDialog = &DiagObl; 
    break; 
    case 1: 
    Diag0b2.Create("TabDialog2" , this) ; 
    CurDialog = &DiagOb2; 
    break; 
    case 2: 
    Diag0b3.Create("TabDialog3", this) ; 
    CurDialog = &DiagOb3; 
    break; 
    CApp App; // создание экземпляра приложения 


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


    Рис. 12.3. Второй пример работы с закладками 

    Наиболее интересная часть программы — функция OnTabChangeQ. Каждый раз при 
выборе новой закладки выполняется два действия. Во-первых, с помощью функции 
DestroyWindow() закрывается диалоговое окно, связанное с текущей закладкой. 
(Вы, очевидно, помните, что именно так должны закрываться немодальные окна.) 
Затем с помощью функции CreateQ создается диалоговое окно, связанное с новой 
закладкой. (Эта функция предназначена для создания именно немодальных окон.) 
    Прежде чем читать дальше, поэкспериментируйте с данной программой. 
Попробуйте, например, добавить к закладкам всплывающие подсказки. 

    Окна просмотра деревьев 

    Следующим элементом, который мы рассмотрим, будет окно просмотра деревьев. 
Такие элементы предназначены для отображения древовидных структур данных. 
Подобным образом представляются, например, списки файлов в программе Explorer 
в Windows 95. Поскольку деревья являются иерархическими структурами, окна 
просмотра деревьев могут использоваться только для отображения иерархической 
информации. Эти элементы чрезвычайно мощны и поддерживают огромное число 
операций. Фактически, о них можно было бы написать целую книгу! Поэтому в 
данном параграфе будут рассмотрены только основные принципы работы с окнами 
просмотра деревьев. Имея общее представление, вы в дальнейшем вполне сможете 
самостоятельно изучить более сложные вопросы. В MFC окна просмотра деревьев 
описываются с помощью класса CTreeCtrl. 
    Примечание: Окна просмотра деревьев также могут создаваться с помощью 
класса CTreeView. Но в большинстве ситуаций, как правило, используются объекты 
типа CTreeCtrl. Класс CTreeView полезен в тех случаях, когда окно просмотра 
деревьев должно стать главным окном программы. 

    12. Строки состояния, закладки и окна просмотра деревьев              315 


    Создание окна просмотра деревьев 

    Для создания окна просмотра деревьев необходимо объявить объект типа 
CTreeCtrl, а затем вызвать функцию CreateQ, которая имеет такой прототип: 
    BOOL CTreeCtrl::Create(DWORD dwSkyle, const RECT bSizePos, CWnd * Owner, 
UINT ID) ; 
    Здесь параметр dwStyle определяет стиль создаваемого окна. Обычно он 
включает стандартные стили WS_CHILD и WS_VISIBLE. Существует также ряд 
специальных стилей, которые можно указывать при создании окна просмотра 
деревьев (табл. 12.4): 


    Таблица 12.4. Стили окна просмотра деревьев 

    Константа               Стиль 

    TVS_EDITLABELS      Пользователь может менять (т.е. редактировать) 
текстовые метки, связанные с элементами дерева 
    TVS_HASLINES        Ветви дерева соединяются линиями 
    TVS_LIN ESATROOT    Ветви дерева соединяются с корнем дерева 
    TVS_HASBUTTONS     Рядом с каждой ветвью отображается кнопка "+" или "—", 
позволяющая раскрывать или сворачивать структуру ветви 

    При указании стилей TVS_HASLINES и TVSJLINESATROOT к каждому элементу 
дерева будет проведена линия. Благодаря этому вся структура примет 
"древовидную" форму. При указании стиля TVS_HASBUTTONS к дереву добавляются 
стандартные кнопки "+" и "—". Они содержат "+", если соответствующая ветвь 
может быть раскрыта по крайней мере еще на один уровень, или "—", если ветвь 
уже полностью раскрыта. На этих кнопках можно также выполнять щелчок мыши. 
Обычно при создании окна просмотра деревьев указываются все три этих стиля. 
    Структура RECT, на которую ссылается параметр SiwPos, содержит размеры и 
положение окна. Указатель на родительское окно передается в параметре Owner. 
Параметр ID является идентификатором окна. Функция CreateQ возвращает 
ненулевое значение при успешном завершении и ноль — в противном случае. 
    Сразу после создания окно просмотра деревьев еще пусто. Теперь необходимо 
добавить элементы в дерево, как это описывается в следующем параграфе. 

    Добавление элементов в дерево 

    Как было только что сказано, дерево создается пустым. Чтобы добавить в 
него элементы, необходимо использовать функцию Insertltem(). Имеется несколько 
перегруженных вариантов этой функции. Нужный нам вариант имеет следующий прототип: 

    HTREEITEM CTreeCtrl::Insertltem(TV_INSERTSTRUCT *Item); 

    Здесь параметр Item является указателем на структуру типа TV_INSERTSTRUCT, 
которая содержит описание добавляемого в дерево элемента. При успешном 
завершении функция возвращает дескриптор элемента, а в случае неудачи — NULL. 
Как известно, при работе с MFC дескрипторы используются редко. Данный случай — исключение. 

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


    Тип TV_INSERTSTRUCT определен следующим образом: 
    typedef struct _TV_INSERTSTRUCT { 
    HTREEITEM hParent; 
    HTREEITEM hInsertAfter; 
    TV_ITEM item; 
    ) TV_INSERTSTRUCT; 

    Здесь в поле hParent содержится дескриптор элемента, являющегося предком 
создаваемого (в дереве). Если создаваемый элемент не имеет предка, в этом поле 
должно находиться значение TVI_ROOT. Значение поля hInsertAfter определяет, 
каким образом новый элемент будет добавлен в дерево. Если оно содержит 
дескриптор некоторого элемента, то новый элемент будет добавлен после 
указанного. В противном случае в поле hInsertAfter может содержаться одно из 
следующих значений (табл. 12.5): 

    Таблица 12.5. Значения поля hfnsertAfter 

    Значение                Смысл 

    TVI_FIRST             Элемент вставляется в начало списка 
    TVI_LAST             Элемент вставляется в конец списка 
    TVI_SORT             Элемент вставляется в список в алфавитном порядке 

    Содержимое поля item описывает создаваемый элемент. Оно представляет собой 
структуру типа TV_ITEM, определенную следующим образом: 

    typedef struct _TV_ITEM ( 
    UINT mask; 
    HTREEITEM hitem; 
    UINT state; 
    UINT stateMask; 
    LPSTR pszText; 
    int cchTextMax; 
    int ilmage; 
    int iSelectedImage; 
    int cChildren; 
    LPARAM 1 Pa ram; 
    } TV_ITEM; 

    Значение поля mask определяет, какие из следующих полей структуры ТУ_ГГЕМ 
содержат требуемую информацию. Значения этого поля могут быть такими (табл. 12.6): 

    Таблица 12.6. Значения тля mask 

    Значение                Смысл 

    TV[F_HANDLE           Информация содержится в поле hitem 
    TVIF_STATE              Информация содержится в полях state и stateMask 
    TVIF_TEXT              Информация содержится в полях pszText и cchTextMax 
    TVIF_IMAGE             Информация содержится в поле ilmage 


    12. Строки состояния, закладки и окна просмотра деревьев              317 


    Значение                Смысл 

    TVIF_SELECTEDIMAGE   Информация содержится в поле iSelectedImage 
    TVIF_CHILDREN         Информация содержится в поле cChiIdren 
    TVIF_PARAM             Информация содержится в поле IParam 

    В поле state записано состояние элемента дерева. Вот наиболее 
распространенные состояния (табл. 12.7): 

    Таблица 12.7. Состояния элементов дерева 

    Константа                 Состояние 

    TVIS_DISABLED           Элемент недоступен 
    TVIS_DROPHILITED       Элемент выделен цветом, так как над ним выполняется 
операция перетаскивания 
    TVIS_EXPANDED          Ветвь, находящаяся под элементом, полностью 
раскрыта. (Применяется только для узлов дерева.) 
    TVIS_EXPANDEDONCE     Ветвь, находящаяся под элементом, раскрыта на один 
(или несколько) уровней. (Применяется только для узлов дерева) 
    TVIS_FOCUSED           Элемент получил фокус ввода 
    TVIS_SELECTED          Элемент выбран 

    Поле stateMask определяет, какое состояние элемента необходимо получить 
или установить. Это поле также может содержать одно или несколько значений из 
предыдущей таблицы. 
    При добавлении элемента в дерево поле pszText должно указывать на 
текстовую строку, которая будет представлять элемент в дереве. При получении 
информации об элементе это поле должно указывать на массив, в который будет 
записана данная строка. В этом случае значение поля cchTextMax определяет 
размер массива. В других ситуациях значение поля cchTextMax игнорируется. 
    Если с окном просмотра деревьев связан список изображений, то в поле 
ilmage будет содержаться индекс изображения, связанного с данным элементом. 
Если такого списка нет, то в поле ilmage должно быть записано -1. Если список 
существует, то поле iSelectedImage содержит индекс изображения выбранного элемента. 
    При получении информации об элементе поле cChiIdren будет содержать число 
потомков данного элемента (в дереве). Поле IParam содержит информацию, 
определенную пользователем. 
    Как видите, следует сохранять дескриптор, возвращаемый функцией 
Insertltem(), так как он необходим для идентификации элементов дерева. Он 
также используется рядом других функций класса CTreeCtrI. 

    Раскрытие и сворачивание ветвей дерева 

    Когда дерево полностью определено, его можно различным образом 
просматривать (т.е. отображать различные ветви дерева). Для раскрытия или 
сворачивания ветвей дерева предназначена функция Expand(), имеющая такой прототип: 

    BOOL CTreeCtrI::Expand(HTREEITEM hitem, UINT How) ; 

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

    Здесь параметр h/fem является дескриптором выбранной ветви. Параметр How 
определяет производимое над ветвью действие. Он может принимать одно из 
следующих значений (табл. 12.8): 

    Таблица 12.8. Значения параметра How 

    Значение                Действие 

    TVE_COLLAPSE          Все ветви более низкого уровня, относящиеся к данной 
ветви, сворачиваются 
    TVE_COLLAPSERESET    Вся структура дерева сворачивается 
    TVE_EXPAND            Все ветви более низкого уровня, относящиеся к данной 
ветви, раскрываются 
    TVE_TOGGLE            Состояние ветви (раскрыта или свернута) изменяется 
на противоположное 

    Необходимо помнить, что данные действия (кроме TVE_COLLAPSERESET) 
выполняются только над ветвями, которые порождаются от элемента, определяемого 
параметром hitem. Например, действие TVE_COLLAPSE выполняется над ветвями, 
порождаемыми от элемента hitem, а не над всем деревом (если только данный 
элемент не является корнем дерева). 
    Функция Expand() возвращает ненулевое значение при успешном завершении и 
ноль — в противном случае. 

    Некоторые функции работы с деревьями 

    Как и другие общие элементы управления, окна просмотра деревьев 
поддерживают множество операций. Соответственно, класс CTreeCtrl содержит 
множество функций, предназначенных для работы с данными окнами. Ниже приведено 
описание некоторых из них. 
    Чтобы определить, какой элемент дерева в данный момент выбран, необходимо 
использовать функцию GetSelectedItemQ, которая имеет следующий прототип: 

    HTREEITEM CTreeCtrl::GetSelectedItem() ; 

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

    HTREEITEM CTreeCtrl::Select(HTREEITEM hitem, UINT How) ; 

    Здесь параметр hitem является дескриптором выбираемого элемента. Значение 
параметра How определяет точный тип производимого действия. Если он равен 
TVGN_CARET, то элемент просто выбирается. Если он равен TVGN_DROPHILITE, то 
элемент еще и выделяется цветом для операции перетаскивания. Если этот 
параметр равен TVGN_FIRSTVISIBLE, то дерево прокручивается в окне таким 
образом, чтобы выбранный элемент стал первым. Функция возвращает ненулевое 
значение при успешном завершении и ноль — в противном случае. 

    12. Строки состояния, закладки и окна просмотра деревьев              319 

    Для получения информации об элементе предназначена функция Getltem(),! 
имеющая такой прототип: 

    BOOL CTreeCtrl::Getltem(TV_ITEM *Item}; 

    Здесь параметр Item является указателем на структуру типа TV_ITEM. Перед 
вызовом в поле hiteni этой структуры должен быть записан дескриптор требуемого 
элемента, а в поле mask — признак того, какая именно информация должна быть 
получена. После завершения функции в данной структуре будет содержаться 
запрошенная информация. Функция возвращает ненулевое значение при успешном 
завершении и ноль — в противном случае. 
    Для задания информации об элементе предназначена функция Setltem(), 
которая имеет следующий прототип: 

    BOOL CTreeCtrl::Setltem(TV_ITEM *Ttem); 

    Здесь параметр Item является указателем на структуру типа TV_ITEM. Перед 
вызовом в поле hitem этой структуры должен быть записан дескриптор требуемого 
элемента, а в поле mask — признак того, какую именно информацию необходимо 
изменить. Функция возвращает ненулевое значение при успешном завершении и ноль 
— в противном случае. 
    Для удаления элемента из дерева необходимо использовать функцию 
DeleteItemQ. Ее прототип таков: 

    BOOL CTreeCtrl::Deleteltem(HTREE hitem}; 

    Здесь параметр h Item является дескриптором удаляемого элемента. Чтобы 
удалить все дерево, необходимо указать значение TVI_ROOT. Функция также 
возвращает ненулевое значение при успешном завершении и ноль — в противном случае. 

    Идентификационные сообщения окон просмотра деревьев 

    При выполнении какого-либо действия над окном просмотра деревьев программе 
посылается сообщение WM_NOTIFY. С окнами просмотра деревьев связано несколько 
идентификационных кодов (или идентификационных сообщений). Вот наиболее 
распространенные из них (табл. 12.9): 

    Таблица 12.9. Идентификационные коды 

    Код                        Значение                             ''\ 

    TVN_DELETEITEM           Был удален элемент дерева 
    TVN_ITEMEXPANDING       Ветвь будет раскрыта или свернута 
    TVN_ITEMEXPANDED        Ветвь только что была раскрыта или свернута 
    TVN_SELCHANGING         Будет выбран новый элемент 
    TVN_SELCHANGED          Был выбран новый элемент 

    Все эти сообщения должны обрабатываться функциями такого вида: 

    afx_msg void Имя_сообщения(ЫМНОК *hdr, LRESULT *Result); 


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

    При обработке сообщений, поступающих от окон просмотра деревьев, параметр 
hdr будет указывать на структуру типа NM_TREEVIEW. Этот тип определен 
следующим образом: 

    typedef struct _NM_TREEVIEW ( 
    NMHDR hdr; 
    UINT action; 
    TV_ITEM itemOld; 
    TV_ITEM itemNew; 
    POINT ptDrag; 
    ) NM_TREEVIEW; 

    В поле hdr находится стандартная структура типа NMHDR (см. главу 10). 
Идентификационный код будет записан в поле code этой структуры. Дескриптор 
окна, от которого поступило сообщение, передается в поле hwndFrom структуры 
NMHDR. Поле action структуры NM_TREEVIEW содержит информацию, специфичную для 
данного идентификационного кода. В структурах itemOld и itemNew содержится 
информация соответственно о предыдущем выбранном элементе (если таковой был) и 
о новом выбранном элементе (опять-таки, если он имеется). Положение курсора 
мыши на момент передачи сообщения приводится в поле ptDrag. 
    Для сообщений TVN_SELCHANGING и TVNJSELCHANGED структура itemOld описывает 
предыдущий выбранный элемент, а структура itemNew — новый выбранный элемент. 
Для сообщений TVN_ITEMEXPANDING и TVN_ITEMEXPANDED в структуре itemNew 
содержится информация о предке данной ветви (в дереве). Для сообщения 
TVN_DELETEITEM в структуре itemOld содержится информация об удаленном элементе. 
    Для сообщений TVN_SELCHANGING и TVNJTEMEXPANDING функция-обработчик должна 
устанавливать значение переменной, на которую указывает параметр Result, в 
ноль, чтобы разрешить выполнение действия, или не в ноль, чтобы запретить его. 
В других сообщениях этот параметр может игнорироваться. 

    Программа работы с окнами просмотра деревьев 

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

    ; TREE.RC 
    #include <afxres.h> 
    tinclude "treeids.h" 
    TreeMenu MENU 1 
    POPUP "&Demo" 
    { 
    MENUITEM "SExpand One\tF2", IDM_EXPAND 


    12. Строки состояния, закладки и окна просмотра деревьев              321 


    MENUITEM "Expand &All\tF3", IDM_EXPANDALL 
    MENUITEM "&Collapse\tF4", IDM_COLLAPSE 
    MENUITEM "E&xit\tF5", IDM_EXIT 
    ) 
    
    MENUITEM "SHelp", IDM_HELP 
    ) 
    TreeMenu ACCELERATORS 
    ( 
    VK_F2, IDM_EXPAND, VIRTKEY 
    VK_F3, IDM_EXPANDALL, VIRTKEY 
    VK_F4, IDM_COLLAPSE, VIRTKEY 
    VK_F5, IDM_EXIT, VIRTKEY 
    VK_F1, IDM_HELP, VIRTKEY 
    } 

    Вот текст библиотечного файла TREEIDS.H: 

    // TREEIDS.H 
    #define IDM_EXPAND     100 
    #define IDM_EXPANDALL 101 
    #define IDM_COLLAPSE   102 
    #define IDM_EXIT       103 
    #define IDM_HELP       104 
    #define ID_TREE        200 
    #define NUM            5 

    Это текст файла TREE.H, содержащего объявления порождаемых классов: 

    // TREE.H 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    { 
    CTreeCtrl rn Tree; // объект окна просмотра деревьев 
    void InitTree() ; 
    public: 
    CMainWin () ; 
    
    // объявления обработчиков меню 
    afx msg void OnExpandO; 
    afx msg void OnExpandAll() ; 
    afx msg void OnCollapse () ; 
    afx msg void OnExitO; 
    afx_msg void OnHelpO; 
    afx msg void OnPaintO; 
    afx_msg void OnTreeChange(NMHDR *hdr, LRESULT *NotUsed) ; 


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


    DECLARE_MESSAGE_MAP () 
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    ( 
    public: 
    BOOL Initlnstance () ; 
    ); 
    Ниже приведен текст самой программы: 
    // Работа с окнами просмотра деревьев 
    
    finclude <afxwin.h> 
    tinclude <afxcmn.h> 
    tinclude <string.h> 
    tinclude "tree.h" 
    tinclude "treeids.h" 
    HTREEITEM hTreeCtrl[NUM]; 
    HTREEITEM hTreeCurrent; 
    CMainWin::CMainWin() 
    { 
    Create(NULL, "Using A Tree View Control", 
    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "TreeMenu") ; 
    
    // Загрузка таблицы быстрого доступа 
    if( [LoadAccelTable("TreeMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    InitCommonControls() ; 
    RECT r; 
    
    // создание окна просмотра деревьев 
    г.left = г.top = 0; 
    г.right = 120; г.bottom = 100; 
    m_Tree.Create(WS__VISIBLE I WS_CHILD | WS_BORDER | 
    TVS_HASLINES | TVS_HASBUTTONS I TVS_LINESATROOT, 
    r, this, IDJTREE); 
    InitTree() ; 
    ) 
    // Инициализация окна просмотра деревьев 
    void CMainWin::InitTree() 
    { 
    TV_INSERTSTRUCT tvs; 
    TV_ITEM tvi; 
    tvs.hInsertAfter = TVI_LAST; 
    tvi.mask = TVXF_TEXT; 


    12. Строки состояния, закладки и окна просмотра деревьев              323 


    tvi.pszText = "One"; 
    tvs.hParent = TVI_ROOT; 
    tvs.item = tvi; 
    hTreeCtrl[0] = rn_Tree.Insertltem(&tvs) ; 
    hTreeCurrent = hTreeCtrl[0]; // первый элемент является текущим 
    tvi.pszText = "Two"; 
    tvs.hParent = hTreeCtrl[0]; 
    tvs.item = tvi; 
    hTreeCtrl[1] = m_Tree.Insertltem(&tvs); 
    tvi.pszText = "Three"; 
    tvs.hParent = hTreeCtrl[1]; 
    tvs.item = tvi; 
    hTreeCtrl[2] = m__Tree.Insertltem(&tvs); 
    tvi.pszText = "Four"; 
    tvs.hParent = hTreeCtrl[2]; 
    tvs.item = tvi; 
    hTreeCtrl[3] = m_Tree.Insertltem(&tvs); 
    tvi.pszText = "Five"; 
    tvs.hParent = hTreeCtrl[2]; 
    tvs.item = tvi; 
    hTreeCtrl[4] = mJTree.Insertltem(&tvs); 
    } 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance() 
    ( 
    m pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmd3how) ; 
    m_pMainWnd->UpdateWindow() ; 
    return TRUE; 
    ) 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_COMMAND(IDM_EXPAND, OnExpand) 
    ON_COMMAND(IDM_EXPANDALL, OnExpandAll) 
    ON_COMMAND(IDM_COLLAPSE, OnCollapse) 
    ON_NOTIFY(TVN_SELCHANGED, ID_TREE, OnTreeChange) 
    ON_WM_PAINT() 
    ON_COMMAND(IDM_EXIT, OnExit) 
    
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    
    // Обработка сообщения WM_PAINT 
    afx msg void CMainWin::OnPaint() 
    t 
    CPaintDC DC(this) ; 
    TV_ITEM tvi; 
    char strl[40] ; 


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


    char str2[255]; 
    tvi.hltem = hTreeCurrent; 
    tvi.pszText = strl; 
    tvi.cchTextMax = sizeof(strl) - 1; 
    tvi.mask = TVIF_TEXT | TVIF_HANDLE; 
    m__Tree.GetItem(&tvi) ; 
    wsprintf(str2, "Current selection: %s", tvi.pszText); 
    DC.TextOut(2, 150, str2, strlen(str2)); 
    ) 
    // Обработка сообщения IDM_EXPAND 
    afx msg void CMainWin::OnExpand() 
    { m Tree.Expand(hTreeCurrent, TVE EXPAND); 
    )   
    
    // Обработка сообщения IDM_EXPANDALL 
    afx msg void CMainWin::OnExpandAll() { 
    int i; 
    
    for(i=0; i<NUM; i++) 
    m__Tree.Expand(hTreeCtrl[i] , TVE_EXPAND) ; 
    1      t 
    
    // Обработка сообщения IDM_COLLAPSE 
    afx msg void CMainWin::OnCollapse() 
    1 
    m_Tree.Expand(hTreeCurrent, TVE_COLLAPSE) ; 
    } 
    
    // Обработка сообщения IDM_EXIT 
    afx_msg void CMainWin::OnExit () 
    ( 
    int response; 
    response = MessageBox("Quit the Program?", "Exit", MB_YESNO) ; 
    if(response " IDYES) 
    SendMessage(WM_CLOSE); // завершение приложения 
    } 
    
    // Обработка сообщения IDM_HELP 
    afx msg void CMainWin::OnHelp() 
    ( 
    MessageBox("Tree View", "Help"); 
    } 
    // Обработка команд выбора элементов дерева 


    12. Строки состояния, закладки и окна просмотра деревьев              325 


    afx_msg void CMainWin::OnTreeChange(NMHDR *hdr, LRESULT *NotUsed) { 
    hTreeCurrent = m_Tree.GetSelectedItem(); 
    InvalidateRect(NULL) ; 
    ) 
    CApp App; // создание экземпляра приложения 


    Рис. 12.4. Пример работы с окном просмотра деревьев 

    В функции InitTree() программы инициализируется окно просмотра деревьев. 
Обратите внимание, что дескриптор каждого элемента сохраняется в массиве 
hTreeCtrl. Это позволяет впоследствии легко отображать все дерево. В 
переменной hTreeCurrent хранится информация о текущем выбранном элементе. Этот 
дескриптор используется при раскрывании или сворачивании текущей ветви через 
команды меню. 
    Каждый раз при выборе нового элемента дерева программе посылается 
сообщение WM_NOTIFY. Оно обрабатывается с помощью функции OnTreeChange(), 
которая сначала с помощью функции GetSelectedItem() получает информацию о 
выбранном элементе, а затем вызывает перерисовку окна. Обработчик OnPaintQ с 
помощью функции GetItemO получает имя нового выбранного элемента, которое 
затем отображается в главном окне программы. 
    Обратите особое внимание на текст обработчика OnExpandAlI(). В нем, путем 
вызова функции ExpandQ для каждого элемента дерева, раскрывается все дерево, 
целиком. Для этого используются дескрипторы, хранящиеся в массиве hTreeCtrl. 
    В данном примере были рассмотрены лишь основы работы с окнами просмотра 
деревьев. Множество чрезвычайно мощных функций совершенно не было затронуто. 
Например, можно перетаскивать элементы из одного дерева в другое. Изучить эти 
и другие, не рассмотренные здесь, функции вы сможете самостоятельно. 

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