Практикум прикладного программирования на MFC и C++ в среде VS.NET

Модальные окна страниц свойств MFC

Разбить на страницы
Показывать лекцию целиком
Файлы к данной лабораторной работе, Вы можете скачать здесь.

Это приложение демонстрирует некоторые из аспектов применения модальных окон свойств на примере разработки диалогового окна Find. Создаваемое диалоговое окно Find будет похоже на применяемое в других приложениях, за одним исключением: оно будет состоять из двух вкладок, в одной из которых будет задаваться запрос поиска (вкладка Find ), а результат поиска будет отражаться в другой вкладке (другой странице свойств - вкладке Result ).

Создание заготовки диалогового приложения

  • Перейдите в свой каталог выполнения лабораторных работ
  • Создайте новый проект и назовите его
  • Вкладки мастера создания приложений заполните в соответствии с таблицей
    Таблица настройки мастера создания заготовки приложения
    Свойство проекта Значение
    Вкладка Application Type
    Application Type Single document
    Document/View architecture support Yes
    Resource language: Английский (США)
    Project style: MFC standard
    Use of MFC: Use MFC in a static library
    Вкладка Advanced Features
    Все сбросить!
    Остальные вкладки
    Оставить все установки по умолчанию!
  • Создание ресурса вкладки Find

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

  • Раскрывающийся список Combo Box для выбора языка ( Language )
  • Два поля редактирования Edit Control:
  • Автор ( Author )
  • Издатель ( Publisher )
  • Три текстовых метки Static Text для перечисленных элементов управления
  • Кнопку Button для начала поиска
  • Создадим данный ресурс:

  • Чтобы не забыть, смените режим построения приложения с Debug на Release
  • В панели Resource View оболочки щелкните в любом месте правой кнопкой мыши и выполните команду контекстного меню Add Resource
  • В окне
  • В панели Resource View оболочки вызовите новый ресурс на редактирование и через панель Properties измените идентификатор ресурса на IDD_PROPPAGE_FIND
  • Измените значение свойства Caption на Find Book (это будет названием вкладки, а не заголовком диалогового окна)
  • Убедитесь в том, что свойства
  • Style установлено в Child (дочерний)
  • Border установлено в Thin
  • Disabled установлен в True
  • Поместите на форму диалогового ресурса элемент управления Combo Box и отредактируйте так
  • Присвойте его идентификатору ID значение IDC_COMBO_LANGUAGE
  • Установите свойство Type в значение Drop List
  • Свойство Data заполните некоторыми фиктивными данными в виде строки названий языков программирования, разделенных точкой_с_запятой, например, Visual C++/MFC;Microsoft C#/.NET;JScript.NET
  • Поместите на форму диалогового ресурса элемент Edit Control и отредактируйте так
  • Присвойте его идентификатору ID значение IDC_EDIT_AUTHOR
  • Установите свойство Disabled = True
  • Поместите на форму диалогового ресурса элемент Edit Control и отредактируйте так
  • Присвойте его идентификатору ID значение IDC_EDIT_PUBLISHER
  • Установите свойство Disabled = True
  • Поместите на форму диалогового ресурса текстовые метки и отредактируйте их свойства так
  • Caption = Language:
  • Caption = Author:
  • Caption = Publisher:
  • Align Text = Right
  • Выровняйте метки по правой границе, пользуясь панелью инструментов оболочки Dialog Editor
  • Поместите на форму диалогового ресурса кнопку Button и отредактируйте ее свойства так
  • ID = IDC_BUTTON_FIND
  • Caption = Find
  • Щелкните по кнопке Test Dialog панели инструментов Dialog Editor оболочки
  • Ресурс страницы свойств IDD_PROPPAGE_FIND должен выглядеть так

    Тест не имеет системной кнопки закрытия окна, поэтому для его закрытия воспользуйтесь комбинацией клавиш Alt+F4.

    Создание ресурса вкладки Result

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

    Создадим данный ресурс.

  • В панели Resource View оболочки щелкните в любом месте правой кнопкой мыши и выполните команду контекстного меню Add Resource
  • В окне
  • В панели Resource View оболочки вызовите новый ресурс на редактирование и через панель Properties измените идентификатор ресурса на IDD_PROPPAGE_RESULT
  • Измените значение свойства Caption на Results: %ld books found - это будет названием вкладки с отображением количества (спецификатор формата %ld - long decimal ) найденных книг
  • Убедитесь в том, что свойства
  • Style установлено в Child (дочерний)
  • Border установлено в Thin
  • Disabled установлен в True
  • Поместите на форму диалогового ресурса элемент управления List Box и отредактируйте так
  • Присвойте его идентификатору ID значение IDC_LIST_BOOKS
  • Установите свойство Horizontal Scroll = True
  • Установите свойство Vertical Scrollbar = True
  • Поместите на форму диалогового ресурса кнопку Button и отредактируйте ее свойства так
  • ID = IDC_BUTTON_GOTO
  • Caption = Go to
  • Щелкните по кнопке Test Dialog панели инструментов Dialog Editor оболочки
  • Ресурс страницы свойств IDD_PROPPAGE_RESULT должен выглядеть так

    Для закрытия теста воспользуйтесь комбинацией клавиш Alt+F4.

    Создание программной поддержки ресурсов

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

    Программная поддержка ресурса Find

  • Вызовите на редактирование ресурс IDD_PROPPAGE_FIND
  • Щелкните в любом месте на форме ресурса правой кнопкой мыши и через контекстное меню выполните команду Add Class
  • Заполните мастер так, как показано на рисунке
  • После нажатия кнопки Finish мастер создаст код класса поддержки страницы свойств Find
  • Для создания ассоциированных переменных для элементов управления нужно щелкать правой кнопкой мыши на этих элементах, вызывать контекстное меню и выполнять команду Add Variable.

  • Создайте управляющую переменную для раскрывающегося списка, заполнив, мастер так
  • Создайте управляющую переменную для кнопки
  • Программная поддержка ресурса Result

  • Вызовите на редактирование ресурс IDD_PROPPAGE_RESULT
  • Щелкните в любом месте на форме ресурса правой кнопкой мыши и через контекстное меню выполните команду Add Class
  • Заполните мастер так, как показано на рисунке
  • После нажатия кнопки Finish мастер создаст код класса поддержки страницы свойств Result
  • Для создания ассоциированных переменных для элементов управления диалогового окна Result нужно щелкать правой кнопкой мыши на этих элементах, вызывать контекстное меню и выполнять команду Add Variable.

  • Создайте управляющую переменную для списка, заполнив, мастер так
  • Создайте управляющую переменную для кнопки
  • Создание строкового ресурса

    Создадим строковый ресурс для конструктора класса-контейнера страниц свойств.

  • Откройте панель Resource View и вызовите на редактирование ресурс String Table
  • Создайте новую строку с настройками
  • ID=IDS_FIND
  • Value=129
  • Редактирование ресурса меню

  • Вызовите на редактирование ресурс меню через панель
  • Удалите все пункты из категории
  • Добавьте между категорией
  • Добавление обработчика пункта меню Find Book

    Добавьте в класс CModalDemoView обработчик события выбора пункта меню Find Book. Для этого:

  • В редакторе ресурса меню щелкните на пункте меню
  • Настройте выделения мастера создания заготовки обработчика так
  • Нажмите кнопку Add and Edit (Добавить и редактировать)

    Мастер создаст в файле ModalDemoView.cpp заготовку функции-обработчика, которая должна выглядеть так

    Заготовка обработчика пункта меню Find Book
    void CModalDemoView::OnBooksFindbook()
    {
      // TODO: Add your command handler code here
    }
  • Добавьте в начало файла ModalDemoView.cpp последними после других директив #include операторы подключения заголовочных файлов, описывающих классы страниц
    Подключение заголовков к файлу ModalDemoView.cpp
    // ModalDemoView.cpp : implementation of the CModalDemoView class
    //
      
    #include "stdafx.h"
    #include "ModalDemo.h"
      
    #include "ModalDemoDoc.h"
    #include "ModalDemoView.h"
    #include ".\modaldemoview.h"
      
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
      
    #include "FindPage.h"
    #include "ResultPage.h"
      
    // CModalDemoView
    ................................................
  • Отредактируйте обработчик пункта меню Find Book так

    Код обработчика OnBooksFindbook()
    void CModalDemoView::OnBooksFindbook()
    {
      CPropertySheet sheet(IDS_FIND);
      CFindPage pageFind;
      CResultPage pageResult;
      
      sheet.AddPage(pageFind);
      sheet.AddPage(pageResult);
      sheet.DoModal();
    }

    В коде обработчика вначале создается экземпляр класса CPropertySheet на стеке, в который конструктор передает заголовок окна свойств через строковый ресурс IDS_FIND, определенный нами ранее в String Table. Затем создаются экземпляры двух страниц свойств, которые добавляются к окну свойств. В завершение окно свойств выводится на экран в модальном режиме.

  • Постройте проект и убедитесь, что все работает в рамках выполненного
  • Модификация стандартного окна свойств

    Выполним работу по настройке страниц свойств в соответствии с нашими пожеланиями.

    Удаление стандартных кнопок

    При отображении модального окна свойств автоматически создаются несколько кнопок. Но иногда в приложении эти стандартные кнопки не нужны.

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

    Стандартные кнопки, создаваемые MFC, всегда имеют идентификаторы, показанные в таблице

    Идентификаторы стандартных кнопок
    Кнопка Идентификатор
    OK IDOK
    Cancel IDCANCEL
    Help IDHELP
    Apply ID_APPLY_NOW
  • В панели
  • Появится окно настройки
  • После щелчка по кнопке Open появится окно мастера добавления класса, которое заполните так

    После щелчка по кнопке Finish создастся класс, производный от MFC -класса CPropertySheet и с именем CFindSheet. Этот класс расширяет возможности стандартного базового класса CPropertySheet и мы можем в этом расширении внести свои дополнения. Код нового класса мы будем использовать в файле ModalDemoView.cpp, поэтому подключим с помощью директивы #include описание класса к этому файлу.

  • Вынесите подключение заголовочного файла FindSheet.h в начало файла ModalDemoView.cpp после подключений заголовков файлов страниц свойств
    Подключение класса CFindSheet к файлу ModalDemoView.cpp
    // ModalDemoView.cpp : implementation of the CModalDemoView class
    //
      
    #include "stdafx.h"
    #include "ModalDemo.h"
      
    #include "ModalDemoDoc.h"
    #include "ModalDemoView.h"
    #include ".\modaldemoview.h"
      
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
      
    #include "FindPage.h"
    #include "ResultPage.h"
    #include "FindSheet.h"
      
    // CModalDemoView
      
    IMPLEMENT_DYNCREATE(CModalDemoView, CView)
    ..............................................
  • Измените обработчик пункта меню Find Book так, чтобы вместо экземпляра стандартного класса CPropertySheet создавался экземпляр созданного нами класса-расширения CFindSheet
    Изменение обработчика пункта меню
    void CModalDemoView::OnBooksFindbook()
    {
    //  CPropertySheet sheet(IDS_FIND);
      CFindSheet sheet(IDS_FIND);
      CFindPage pageFind;
      CResultPage pageResult;
      
      sheet.AddPage(pageFind);
      sheet.AddPage(pageResult);
      sheet.DoModal();
    }

    Теперь скроем ненужные кнопки IDCANCEL и ID_APPLY_NOW в окне свойств, переопределив функцию OnInitDialog() в классе CFindSheet.

  • Откройте панель Class View, найдите и выделите класс CFindSheet
  • Сразу после выделения откройте панель
  • Создайте переопределенную функцию, которую заполните следующим кодом
    Переопределенная функция OnInitDialog()
    BOOL CFindSheet::OnInitDialog()
    {
      BOOL bResult = CPropertySheet::OnInitDialog();
      
      // TODO:  Add your specialized code here
      
      int ids[] = {  IDCANCEL,
              ID_APPLY_NOW };
      int len = sizeof(ids) / sizeof(ids[0]);
      for(int i = 0; i < len; i++){
        CWnd *pWnd = GetDlgItem(ids[i]);
        ASSERT(pWnd);
        if(pWnd) 
          pWnd->ShowWindow(FALSE);
      }
      
      return bResult;
    }
  • Постройте проект и убедитесь, что двух кнопок больше нет, мы их скрыли перед появлением окна свойств на экране
  • Перемещение стандартных кнопок

    Оставшаяся нескрытой кнопка OK расположена на том же самом месте, где она находилась и ранее до сокрытия двух других кнопок. Переместим оставшуюся кнопку в другое место. Для этого воспользуемся функцией-членом класса CWnd::MoveWindow(), предварительно рассчитав координаты нового положения кнопки.

  • Дополните переопределенную функцию CFindSheet::OnInitDialog() следующим кодом
    Дополнение функции кодом перемещения кнопки
    BOOL CFindSheet::OnInitDialog()
    {
      BOOL bResult = CPropertySheet::OnInitDialog();
      
      // TODO:  Add your specialized code here
      
      int ids[] = {  IDCANCEL,
              ID_APPLY_NOW };
      int len = sizeof(ids) / sizeof(ids[0]);
      for(int i = 0; i < len; i++){
        CWnd *pWnd = GetDlgItem(ids[i]);
        ASSERT(pWnd);
        if(pWnd) 
          pWnd->ShowWindow(FALSE);
      }
      
      CWnd *pBtnOk = GetDlgItem(IDOK);
      ASSERT(pBtnOk);
      
      CRect rectSheet;
      GetWindowRect(rectSheet); // Размер окна свойств
      
      CRect rectBtnOk;
      pBtnOk->GetWindowRect(rectBtnOk); // Размер окна кнопки
      
      // Выяснить расстояние между нижней частью кнопки
      // и нижней частью окна свойств
      int iBorder = rectSheet.bottom - rectBtnOk.bottom;
      
      // Изменить размеры окна свойств
      rectSheet.right += rectBtnOk.Width() + iBorder;
      rectSheet.bottom = rectBtnOk.top;
      MoveWindow(rectSheet);
      
      // Найти первую страницу
      CPropertyPage *pPage = GetPage(0);
      ASSERT(pPage);
      CRect rectPage;
      pPage->GetWindowRect(rectPage); // Размер страницы свойств
      
      // Сохранить ширину и высоту кнопки OK
      int cxOk = rectBtnOk.Width();
      int cyOk = rectBtnOk.Height();
      
      // Переместить кнопку OK
      rectBtnOk.top = rectPage.top;
      rectBtnOk.bottom = rectPage.top + cyOk;
      rectBtnOk.left = rectSheet.right - (cxOk + iBorder);
      rectBtnOk.right = rectBtnOk.left + cxOk;
      ScreenToClient(rectBtnOk);
      pBtnOk->MoveWindow(rectBtnOk);
      
      return bResult;
    }
  • Постройте приложение и убедитесь, что кнопка переместилась на нужное место, а окно свойств изменило размеры
  • Результат должен выглядеть так

    Изменение надписей стандартных кнопок

    Надпись на кнопке должна описывать действия, осуществляемые при щелчке на ней. Увидев в диалоговом окне Find Book кнопку OK, пользователь вряд ли сможет догадаться, что случится, если он щелкнет по ней. Приведет ли щелчок на кнопке OK к закрытию диалогового окна или началу поиска - из названия не ясно. Заменим надпись на кнопке более содержательной.

    Еще раз вспомним, что диалоговый ресурс, который можно править в редакторе диалоговых окон, у нас имеется только для страниц свойств. Мы же в данный момент учимся изменять структуру диалогового окна свойств, которое стандартно сгенерировано MFC -библиотекой и не имеет ресурса в приложении, который мы могли бы визуально отредактировать. Поэтому мы вынуждены добиваться необходимых визуальных результатов программно.

  • Добавьте в конец все той же функции OnInitDialog() класса CFindSheet следующий код
    Дополнение функции кодом изменения надписи на кнопке OK
    BOOL CFindSheet::OnInitDialog()
    {
    ...........................................
      ScreenToClient(rectBtnOk);
      pBtnOk->MoveWindow(rectBtnOk);
      
      pBtnOk->SetWindowText(_T("Close"));
      
      return bResult;
    }
  • Запустите приложение и убедитесь в результате - теперь на кнопке другая надпись
  • Отключение вкладок

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

  • Создать переменную для хранения индекса текущей вкладки
  • Создать массив переменных для хранения индексов всех отключенных в данный момент вкладок
  • Создать обработчик уведомляющего сообщения TCN_SELCHANGING или TCN_SELCHANGE позволяющего выяснить индекс текущей вкладки
  • Разместить в этом обработчике код, принимающий решение о необходимости активизации вкладки
  • В качестве альтернативы можно воспользоваться сообщением
  • Создание переменной хранения индекса текущей вкладки

  • Добавьте в конец объявления класса CFindSheet в файле FindSheet.h следующий код
    Объявление переменной хранения индекса текущей вкладки
    class CFindSheet : public CPropertySheet
    {
    .............................
    protected:
      int m_iLastActivePage;
    };
  • Создание массива индексов отключенных вкладок

    В следующих двух таблицах приведены классы массивов, поддерживаемые MFC, и описание их общедоступных методов

    Классы массивов MFC
    Класс Описание
    CArray Шаблонный класс для создания пользовательских классов массивов
    CByteArray Массив с данными типа байт
    CDWordArray Массив с данными типа DWORD (двойное слово)
    CObArray Массив может содержать классы, производные от класса CObject
    CPtArray Массив указателей на любые объекты и типы данных
    CStringArray Массив объектов типа CString
    CUIntArray Массив данных типа UINT ( unsigned int )
    CWordArray Массив данных типа WORD (слово)
    Методы классов массивов MFC
    Класс Описание
    array[int index] Оператор ображения к значению элемента массива по его индексу. Индексация массива начинается с нуля
    array.GetAt(int index) Возвращает элемент массива по его индексу. Индексация массива начинается с нуля
    array.SetAt(int index, element) Записывает элемент массива по его индексу. Тип данных записываемого элемента должен соответствовать типу данных всего массива. Вызов функции не приводит к росту количества элементов массива.
    array.InsertAt(int index, element) Вставляет новый элемент перед элементом index. Размерность массива увеличивается на единицу.
    array.Add(element) Добавляет новый элемент в конец массива. Добавляемый элемент должен иметь тип данных всего массива.
    array.RemoveAt(int index, int count=1) Удаляет count элементов, начиная с позиции index. Если второй аргумент не указан, то удаляется один элемент.
    array.RemoveAll() Удаляет все элементы массива и присваивает свойству количества элементов массива значение ноль.
    В массивах классов CObArray и CPtArray элементы как объекты не удалаются. Из массивов удаляются только указатели на объекты. При использовании функций RemoveAt() и RemoveAll() необходимо сначала удалить сами объекты, а затем удалить из массива их указатели.
    array.GetSize() Возвращает количество элементов массива.
    array.GetCount() Возвращает количество элементов массива.
    array.GetUpperBound() Возвращает индекс последнего элемента массива.
    array.SetSize(int iNewSize, iGrowBy) Управляет размером массива.
    array.Copy(source) Копирует source в array
    array.Append(source) Присоединяет source в конец array
    В случае с массивами классов CObArray и CPtArray функции Copy() и Append() копируют в массив указатели, а не сами объекты. В результате, изменение значения объекта в одном из массивов приведет к изменению значения соответствующего объекта в другом массиве. Кроме того, при удалении элемента в одном из массивов указатель в другом массиве станет недействительным.

    Создадим стандартный массив MFC -класса CUIntArray, в котором будут содержаться индексы всех вкладок окна свойств, заблокированных в данный момент.

  • Добавьте в конец объявления класса CFindSheet в файле FindSheet.h следующий код
    Создание массива индексов отключенных вкладок
    class CFindSheet : public CPropertySheet
    {
    .....................................................
    protected:
      int m_iLastActivePage;
      CUIntArray m_arrDisabledPages;
    };
  • Добавление функции-члена, отключающей определенные вкладки

    Добавим в класс CFindSheet функцию-член, позволяющую отключать определенные вкладки. Подлежащие отключению вкладки указаны в передаваемом этой функции аргументе переменной длины ( vararg ), представляющем собой список с завершающим терминатором -1. Эта функция добавляет переданные индексы вкладок в массив m_arrDisabledPages, затем маркирует вкладку присоединением к ее названию текста "-Disabled".

    Эту дополнительную работу с добавлением маркера "-Disabled" нужно выполнять потому, что не существует способа по настоящему сделать вкладку недоступной с помощью изменения ее цвета на серый. Поэтому Microsoft рекомендует просто добавлять к заголовку вкладки маркирующий текст.

  • Выделите в панели
  • Нажмите кнопку Add, затем кнопку Finish

    Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции.

  • Исправьте прототип и заголовок функции для возможности передачи аргументов переменной длины, чтобы они смотрелись так
    Правка в файле FindSheet.h
    class CFindSheet : public CPropertySheet
    {
    ...............................................
    public:
      // Отключение вкладок
      void DisablePage(int iFirstPage, ...);
    };
    Правка в файле FindSheet.cpp
    // Отключение вкладок
    void CFindSheet::DisablePage(int iFirstPage, ...)
    {
    }
  • Наполните созданную функцию следующим кодом
    Заполнение функции кодом
    // Отключение вкладок
    void CFindSheet::DisablePage(int iFirstPage, ...)
    {
      int iPage = iFirstPage;
      
      va_list marker;
      va_start (marker, iFirstPage);
      int nArgs = 0;
      while(iPage != -1){// Список заканчивается -1
        // Добавить индекс страницы 
        // в массив отключенных страниц, 
        // если его там нет
        int count = (int)m_arrDisabledPages.GetCount();
        bool flag = false;
        for(int i = 0; i < count; i++)
          if(m_arrDisabledPages.GetAt(i) == (UINT)iPage){
            flag = true;
            break;
          }
        if(!flag)
          m_arrDisabledPages.Add(iPage);
      
        SetDisabledText(iPage);  // Наша функция маркирует вкладку
      
        // Получить индекс следующей страницы
        iPage = va_arg(marker, UINT);
        nArgs++; // счетчик вкладок
        ASSERT(nArgs < 100); // Страж зацикливания
      }
    }
  • Изменение заголовка вкладки

    Создадим функцию SetDisabledText(), вызов которой мы предусмотрели в предыдущей функции.

  • Выделите в панели
  • Нажмите кнопку Add, затем кнопку Finish. Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции
  • Перед заголовком реализации функции добавьте макроопределение, а в тело функции код
    Маркировка вкладок
    
    #define DISABLED_TEXT " - Disabled"
      
    // Маркировка текста заголовков вкладок
    void CFindSheet::SetDisabledText(int iPage)
    {
      CTabCtrl *pTab = GetTabControl();
      ASSERT(pTab);
      TC_ITEM ti;
      char szText[100];
      ti.mask = TCIF_TEXT;
      ti.pszText = szText;
      ti.cchTextMax = 100;
      VERIFY(pTab->GetItem(iPage, ti));
      strcat(szText, DISABLED_TEXT);
      VERIFY(pTab->SetItem(iPage, ti));
    }
  • Обработка уведомляющих сообщений отключенных вкладок

    Получив средства отображения состояния отключенных вкладок (доступна или не доступна), добавим функцию, которая фактически отключает возможность выбора пользователем заблокированной вкладки. Эту задачу выполнит виртуальная функция OnNotify() базового класса CPropertySheet, которую мы переопределим в нашем классе-наследнике CFindSheet.

  • Выделите в панели Class View класс CFindSheet и перейдите в панель Properties
  • Установите в панели
  • Наполните созданную функцию следующим кодом

    Переопределение виртуальной функции OnNotify()
    BOOL CFindSheet::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
      NMHDR *pNmh = (NMHDR*)lParam;
      ASSERT(pNmh);
      if(pNmh->code == TCN_SELCHANGING)
        m_iLastActivePage = GetActiveIndex();
      else if(pNmh->code == TCN_SELCHANGE){
        int iCurrPage = GetActiveIndex();
        if(IsPageDisabled(iCurrPage))
          PostMessage(PSM_SETCURSEL, m_iLastActivePage);
      }
      
      return CPropertySheet::OnNotify(wParam, lParam, pResult);
    }

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

  • Выделите в панели
  • Нажмите кнопку Add, затем кнопку Finish
  • Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции.

  • Наполните созданную функцию следующим кодом
    Проверка доступности вкладок
    // Определяет, числится ли выбираемая
    // пользователем вкладка в недоступных
    BOOL CFindSheet::IsPageDisabled(int iPage)
    {
      BOOL bFoundEntry = FALSE;
      int iSize = (int)m_arrDisabledPages.GetSize();
      
      int i = 0;
      while(i < iSize  !bFoundEntry)
        if(m_arrDisabledPages.GetAt(i) == (UINT)iPage)
          bFoundEntry = TRUE;
        else
          i++;
      
      return bFoundEntry;
    }
  • Пока ничего не было найдено, вкладка Result должна быть пользователю недоступной. Мы для этого создали соответствующий код. Теперь осталось проверить его работу.

  • Найдите функцию-член OnInitDialog() класса CFindSheet и добавьте в конец следующий код
    Сделать недоступной вкладку Result с индексом 1
    BOOL CFindSheet::OnInitDialog()
    {
    ..............................................
      pBtnOk->SetWindowText(_T("Close"));
      
      DisablePage(1, -1);
      
      return bResult;
    }
  • Постройте приложение и убедитесь, что в начальный момент вкладка Result недоступна
  • Подключение вкладок

    Имея возможность отключать вкладки окна свойств нужно дать возможность включать их. Для этого необходимо добавить функцию EnablePage(), восстанавливающую доступ к вкладке, и функцию SetEnabledText(), удаляющую из ее заголовка маркер " - Disabled".

  • В панели Class View выделите класс CFindSheet, поскольку добавлять функцию-член мы будем именно в этот класс
  • Через контекстное меню по щелчку правой кнопкой мыши на классе
  • Нажмите кнопку Add, затем кнопку Finish. Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции
  • Наполните созданную функцию следующим кодом
    Функция включения доступа к вкладке
    // Включение вкладки
    void CFindSheet::EnablePage(int iPage)
    {
      BOOL bFoundEntry = FALSE;
      int iSize = (int)m_arrDisabledPages.GetSize();
      
      int i = 0;
      while(i < iSize  !bFoundEntry)
        if(m_arrDisabledPages.GetAt(i) == (UINT)iPage)
          bFoundEntry = TRUE;
        else
          i++;
      
      if(bFoundEntry){
        m_arrDisabledPages.RemoveAt(i);
        SetEnabledText(iPage);
      }
    }
  • Снова в панели Class View выделите класс CFindSheet
  • Через контекстное меню по щелчку правой кнопкой мыши на классе
  • Нажмите кнопку Add, затем кнопку Finish

    Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции.

  • Наполните созданную функцию следующим кодом
    Удаление маркера " - Disabled" из заголовка вкладки
    // Удаление маркера " - Disabled" из заголовка вкладки
    void CFindSheet::SetEnabledText(int iPage)
    {
      CTabCtrl *pTab = GetTabControl();
      ASSERT(pTab);
      TC_ITEM ti;
      char szText[100];
      ti.mask = TCIF_TEXT;
      ti.pszText = szText;
      ti.cchTextMax = 100;
      VERIFY(pTab->GetItem(iPage, ti));
      // Функция strstr() осуществляет поиск в строке szText
      // первого вхождения подстроки DISABLED_TEXT
      char *pFound = strstr(szText, DISABLED_TEXT);
      if(pFound){
        *pFound = '\0'; // Устанавливаем метку конца заголовка
        VERIFY(pTab->SetItem(iPage, ti));
      }
    }
  • Проверить работу кода включения доступности второй вкладки можно с помощью обработчика кнопки Find первой вкладки. В реальном приложении вкладка Result должна стать доступной лишь после того, как фактически выполненный поиск увенчается успехом. В нашем случае вызов кода разрешения и запрещения доступа к вкладке существенно упрощен.

    Класс CFindSheet является родительским по отношению к классам вкладок CFindPage и CResultPage. Мы создадим обработчик кнопки Find первой вкладки, в котором получим указатель на объект родительского класса, а потом адресуемся к проверяемой функции EnablePage() родительского класса.

  • Откройте панель Resource View оболочки и вызовите на редактирование ресурс IDD_PROPPAGE_FIND
  • Выделите кнопку
  • Наполните созданную функцию следующим кодом
    Обработчик кнопки Find
    void CFindPage::OnBnClickedButtonFind()
    {
      // TODO: Add your control notification handler code here
      CFindSheet *pParentSheet = (CFindSheet*)GetParent();
      
      ASSERT(pParentSheet->IsKindOf(RUNTIME_CLASS(CFindSheet)));
      
      pParentSheet->EnablePage(1);// Вторая вкладка
    }
  • В начало файла FindPage.cpp добавьте директиву #include, подключающую к этому файлу заголовочный файл с описанием класса CFindSheet, используемого в обработчике
    Подключение заголовочного файла FindSheet.h к файлу с обработчиком кнопки FindPage.cpp
    // FindPage.cpp : implementation file
    //
      
    #include "stdafx.h"
    #include "ModalDemo.h"
    #include "FindPage.h"
    #include ".\findpage.h"
      
    #include "FindSheet.h"
      
    // CFindPage dialog
      
    IMPLEMENT_DYNAMIC(CFindPage, CPropertyPage)
    CFindPage::CFindPage()
      : CPropertyPage(CFindPage::IDD)
    {
    }
      
    CFindPage::~CFindPage()
    {
    }
    ..................................................
  • Постройте приложение и убедитесь, что вкладка Result становится доступной после щелчка по кнопке Find
  • Динамическое изменение заголовка вкладки

    В зависимости от результатов поиска по команде из первой вкладке нужно изменить заголовок и доступность второй вкладки. Количество найденных книг для простоты свяжем с индексом элемента в раскрывающемся списке интерфейса первой вкладки. Выбранный индекс списка nHint при нажатии кнопки Find будет передаваться в функцию CFindSheet::SetResult(nHits), которая установит заголовок и доступность второй вкладки Result. Функция будет в большей части повторять код предыдущей функции CFindSheet::SetEnabledText(int iPage) .

  • В начало файла FindSheet.cpp добавьте два макроопределения
    Макроопределения в начале файла FindSheet.cpp
    // FindSheet.cpp : implementation file
    //
    
    #include "stdafx.h"
    #include "ModalDemo.h"
    #include "FindSheet.h"
    #include ".\findsheet.h"
      
    #define RESULT_TAB_INDEX 1
    #define RESULT_TAB_CAPTION "Result: %ld books found"
      
    ......................................................
  • Для класса
  • Нажмите кнопку Add, затем кнопку Finish

    Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции.

  • Наполните созданную функцию следующим кодом
    Функция SetResult()
    // Изменение содержимого и доступности заголовка вкладки
    void CFindSheet::SetResult(int nHits)
    {
      // Изменение текста заголовка вкладки
      CTabCtrl *pTab = GetTabControl();
      ASSERT(pTab);
      TC_ITEM ti;
      char szText[100];
      ti.mask = TCIF_TEXT;
      ti.pszText = szText;
      ti.cchTextMax = 100;
      VERIFY(pTab->GetItem(RESULT_TAB_INDEX, ti));
      sprintf(szText, RESULT_TAB_CAPTION, nHits);
      VERIFY(pTab->SetItem(RESULT_TAB_INDEX, ti));
      
      // Управление доступностью вкладки
      if(nHits <= 0)
        DisablePage(RESULT_TAB_INDEX, -1);
      else
        EnablePage(RESULT_TAB_INDEX);
    }
  • Перейдите в файл FindPage.cpp и найдите обработчик кнопки Find, который измените так
    Изменение обработчика кнопки Find
    void CFindPage::OnBnClickedButtonFind()
    {
      // TODO: Add your control notification handler code here
      CFindSheet *pParentSheet = (CFindSheet*)GetParent();
      
      ASSERT(pParentSheet->IsKindOf(RUNTIME_CLASS(CFindSheet)));
       
    //  pParentSheet->EnablePage(1);// Вторая вкладка
      
      // Демонстрация управления вкладкой Result по индексу списка
      int iIndex = m_cboLanguages.GetCurSel();
      pParentSheet->SetResult(iIndex);
    }
  • Постройте приложение и убедитесь, что:
  • количество "найденных" книг равно индексу элемента, выбранного в раскрывающемся списке
  • динамически меняется заголовок вкладки
  • доступность вкладки зависит от успеха "поиска"
  • Все работает, как мы задумали. Внешний вид приложения должен быть примерно таким

    Страницы:
    Файлы к данной лабораторной работе, Вы можете скачать здесь.

    Это приложение демонстрирует некоторые из аспектов применения модальных окон свойств на примере разработки диалогового окна Find. Создаваемое диалоговое окно Find будет похоже на применяемое в других приложениях, за одним исключением: оно будет состоять из двух вкладок, в одной из которых будет задаваться запрос поиска (вкладка Find ), а результат поиска будет отражаться в другой вкладке (другой странице свойств - вкладке Result ).

    Создание заготовки диалогового приложения

  • Перейдите в свой каталог выполнения лабораторных работ
  • Создайте новый проект и назовите его
  • Вкладки мастера создания приложений заполните в соответствии с таблицей
    Таблица настройки мастера создания заготовки приложения
    Свойство проекта Значение
    Вкладка Application Type
    Application Type Single document
    Document/View architecture support Yes
    Resource language: Английский (США)
    Project style: MFC standard
    Use of MFC: Use MFC in a static library
    Вкладка Advanced Features
    Все сбросить!
    Остальные вкладки
    Оставить все установки по умолчанию!
  • Создание ресурса вкладки Find

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

  • Раскрывающийся список Combo Box для выбора языка ( Language )
  • Два поля редактирования Edit Control:
  • Автор ( Author )
  • Издатель ( Publisher )
  • Три текстовых метки Static Text для перечисленных элементов управления
  • Кнопку Button для начала поиска
  • Создадим данный ресурс:

  • Чтобы не забыть, смените режим построения приложения с Debug на Release
  • В панели Resource View оболочки щелкните в любом месте правой кнопкой мыши и выполните команду контекстного меню Add Resource
  • В окне
  • В панели Resource View оболочки вызовите новый ресурс на редактирование и через панель Properties измените идентификатор ресурса на IDD_PROPPAGE_FIND
  • Измените значение свойства Caption на Find Book (это будет названием вкладки, а не заголовком диалогового окна)
  • Убедитесь в том, что свойства
  • Style установлено в Child (дочерний)
  • Border установлено в Thin
  • Disabled установлен в True
  • Поместите на форму диалогового ресурса элемент управления Combo Box и отредактируйте так
  • Присвойте его идентификатору ID значение IDC_COMBO_LANGUAGE
  • Установите свойство Type в значение Drop List
  • Свойство Data заполните некоторыми фиктивными данными в виде строки названий языков программирования, разделенных точкой_с_запятой, например, Visual C++/MFC;Microsoft C#/.NET;JScript.NET
  • Поместите на форму диалогового ресурса элемент Edit Control и отредактируйте так
  • Присвойте его идентификатору ID значение IDC_EDIT_AUTHOR
  • Установите свойство Disabled = True
  • Поместите на форму диалогового ресурса элемент Edit Control и отредактируйте так
  • Присвойте его идентификатору ID значение IDC_EDIT_PUBLISHER
  • Установите свойство Disabled = True
  • Поместите на форму диалогового ресурса текстовые метки и отредактируйте их свойства так
  • Caption = Language:
  • Caption = Author:
  • Caption = Publisher:
  • Align Text = Right
  • Выровняйте метки по правой границе, пользуясь панелью инструментов оболочки Dialog Editor
  • Поместите на форму диалогового ресурса кнопку Button и отредактируйте ее свойства так
  • ID = IDC_BUTTON_FIND
  • Caption = Find
  • Щелкните по кнопке Test Dialog панели инструментов Dialog Editor оболочки
  • Ресурс страницы свойств IDD_PROPPAGE_FIND должен выглядеть так

    Тест не имеет системной кнопки закрытия окна, поэтому для его закрытия воспользуйтесь комбинацией клавиш Alt+F4.

    Создание ресурса вкладки Result

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

    Создадим данный ресурс.

  • В панели Resource View оболочки щелкните в любом месте правой кнопкой мыши и выполните команду контекстного меню Add Resource
  • В окне
  • В панели Resource View оболочки вызовите новый ресурс на редактирование и через панель Properties измените идентификатор ресурса на IDD_PROPPAGE_RESULT
  • Измените значение свойства Caption на Results: %ld books found - это будет названием вкладки с отображением количества (спецификатор формата %ld - long decimal ) найденных книг
  • Убедитесь в том, что свойства
  • Style установлено в Child (дочерний)
  • Border установлено в Thin
  • Disabled установлен в True
  • Поместите на форму диалогового ресурса элемент управления List Box и отредактируйте так
  • Присвойте его идентификатору ID значение IDC_LIST_BOOKS
  • Установите свойство Horizontal Scroll = True
  • Установите свойство Vertical Scrollbar = True
  • Поместите на форму диалогового ресурса кнопку Button и отредактируйте ее свойства так
  • ID = IDC_BUTTON_GOTO
  • Caption = Go to
  • Щелкните по кнопке Test Dialog панели инструментов Dialog Editor оболочки
  • Ресурс страницы свойств IDD_PROPPAGE_RESULT должен выглядеть так

    Для закрытия теста воспользуйтесь комбинацией клавиш Alt+F4.

    Создание программной поддержки ресурсов

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

    Программная поддержка ресурса Find

  • Вызовите на редактирование ресурс IDD_PROPPAGE_FIND
  • Щелкните в любом месте на форме ресурса правой кнопкой мыши и через контекстное меню выполните команду Add Class
  • Заполните мастер так, как показано на рисунке
  • После нажатия кнопки Finish мастер создаст код класса поддержки страницы свойств Find
  • Для создания ассоциированных переменных для элементов управления нужно щелкать правой кнопкой мыши на этих элементах, вызывать контекстное меню и выполнять команду Add Variable.

  • Создайте управляющую переменную для раскрывающегося списка, заполнив, мастер так
  • Создайте управляющую переменную для кнопки
  • Программная поддержка ресурса Result

  • Вызовите на редактирование ресурс IDD_PROPPAGE_RESULT
  • Щелкните в любом месте на форме ресурса правой кнопкой мыши и через контекстное меню выполните команду Add Class
  • Заполните мастер так, как показано на рисунке
  • После нажатия кнопки Finish мастер создаст код класса поддержки страницы свойств Result
  • Для создания ассоциированных переменных для элементов управления диалогового окна Result нужно щелкать правой кнопкой мыши на этих элементах, вызывать контекстное меню и выполнять команду Add Variable.

  • Создайте управляющую переменную для списка, заполнив, мастер так
  • Создайте управляющую переменную для кнопки
  • Создание строкового ресурса

    Создадим строковый ресурс для конструктора класса-контейнера страниц свойств.

  • Откройте панель Resource View и вызовите на редактирование ресурс String Table
  • Создайте новую строку с настройками
  • ID=IDS_FIND
  • Value=129
  • Редактирование ресурса меню

  • Вызовите на редактирование ресурс меню через панель
  • Удалите все пункты из категории
  • Добавьте между категорией
  • Добавление обработчика пункта меню Find Book

    Добавьте в класс CModalDemoView обработчик события выбора пункта меню Find Book. Для этого:

  • В редакторе ресурса меню щелкните на пункте меню
  • Настройте выделения мастера создания заготовки обработчика так
  • Нажмите кнопку Add and Edit (Добавить и редактировать)

    Мастер создаст в файле ModalDemoView.cpp заготовку функции-обработчика, которая должна выглядеть так

    Заготовка обработчика пункта меню Find Book
    void CModalDemoView::OnBooksFindbook()
    {
      // TODO: Add your command handler code here
    }
  • Добавьте в начало файла ModalDemoView.cpp последними после других директив #include операторы подключения заголовочных файлов, описывающих классы страниц
    Подключение заголовков к файлу ModalDemoView.cpp
    // ModalDemoView.cpp : implementation of the CModalDemoView class
    //
      
    #include "stdafx.h"
    #include "ModalDemo.h"
      
    #include "ModalDemoDoc.h"
    #include "ModalDemoView.h"
    #include ".\modaldemoview.h"
      
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
      
    #include "FindPage.h"
    #include "ResultPage.h"
      
    // CModalDemoView
    ................................................
  • Отредактируйте обработчик пункта меню Find Book так

    Код обработчика OnBooksFindbook()
    void CModalDemoView::OnBooksFindbook()
    {
      CPropertySheet sheet(IDS_FIND);
      CFindPage pageFind;
      CResultPage pageResult;
      
      sheet.AddPage(pageFind);
      sheet.AddPage(pageResult);
      sheet.DoModal();
    }

    В коде обработчика вначале создается экземпляр класса CPropertySheet на стеке, в который конструктор передает заголовок окна свойств через строковый ресурс IDS_FIND, определенный нами ранее в String Table. Затем создаются экземпляры двух страниц свойств, которые добавляются к окну свойств. В завершение окно свойств выводится на экран в модальном режиме.

  • Постройте проект и убедитесь, что все работает в рамках выполненного
  • Модификация стандартного окна свойств

    Выполним работу по настройке страниц свойств в соответствии с нашими пожеланиями.

    Удаление стандартных кнопок

    При отображении модального окна свойств автоматически создаются несколько кнопок. Но иногда в приложении эти стандартные кнопки не нужны.

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

    Стандартные кнопки, создаваемые MFC, всегда имеют идентификаторы, показанные в таблице

    Идентификаторы стандартных кнопок
    Кнопка Идентификатор
    OK IDOK
    Cancel IDCANCEL
    Help IDHELP
    Apply ID_APPLY_NOW
  • В панели
  • Появится окно настройки
  • После щелчка по кнопке Open появится окно мастера добавления класса, которое заполните так

    После щелчка по кнопке Finish создастся класс, производный от MFC -класса CPropertySheet и с именем CFindSheet. Этот класс расширяет возможности стандартного базового класса CPropertySheet и мы можем в этом расширении внести свои дополнения. Код нового класса мы будем использовать в файле ModalDemoView.cpp, поэтому подключим с помощью директивы #include описание класса к этому файлу.

  • Вынесите подключение заголовочного файла FindSheet.h в начало файла ModalDemoView.cpp после подключений заголовков файлов страниц свойств
    Подключение класса CFindSheet к файлу ModalDemoView.cpp
    // ModalDemoView.cpp : implementation of the CModalDemoView class
    //
      
    #include "stdafx.h"
    #include "ModalDemo.h"
      
    #include "ModalDemoDoc.h"
    #include "ModalDemoView.h"
    #include ".\modaldemoview.h"
      
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
      
    #include "FindPage.h"
    #include "ResultPage.h"
    #include "FindSheet.h"
      
    // CModalDemoView
      
    IMPLEMENT_DYNCREATE(CModalDemoView, CView)
    ..............................................
  • Измените обработчик пункта меню Find Book так, чтобы вместо экземпляра стандартного класса CPropertySheet создавался экземпляр созданного нами класса-расширения CFindSheet
    Изменение обработчика пункта меню
    void CModalDemoView::OnBooksFindbook()
    {
    //  CPropertySheet sheet(IDS_FIND);
      CFindSheet sheet(IDS_FIND);
      CFindPage pageFind;
      CResultPage pageResult;
      
      sheet.AddPage(pageFind);
      sheet.AddPage(pageResult);
      sheet.DoModal();
    }

    Теперь скроем ненужные кнопки IDCANCEL и ID_APPLY_NOW в окне свойств, переопределив функцию OnInitDialog() в классе CFindSheet.

  • Откройте панель Class View, найдите и выделите класс CFindSheet
  • Сразу после выделения откройте панель
  • Создайте переопределенную функцию, которую заполните следующим кодом
    Переопределенная функция OnInitDialog()
    BOOL CFindSheet::OnInitDialog()
    {
      BOOL bResult = CPropertySheet::OnInitDialog();
      
      // TODO:  Add your specialized code here
      
      int ids[] = {  IDCANCEL,
              ID_APPLY_NOW };
      int len = sizeof(ids) / sizeof(ids[0]);
      for(int i = 0; i < len; i++){
        CWnd *pWnd = GetDlgItem(ids[i]);
        ASSERT(pWnd);
        if(pWnd) 
          pWnd->ShowWindow(FALSE);
      }
      
      return bResult;
    }
  • Постройте проект и убедитесь, что двух кнопок больше нет, мы их скрыли перед появлением окна свойств на экране
  • Перемещение стандартных кнопок

    Оставшаяся нескрытой кнопка OK расположена на том же самом месте, где она находилась и ранее до сокрытия двух других кнопок. Переместим оставшуюся кнопку в другое место. Для этого воспользуемся функцией-членом класса CWnd::MoveWindow(), предварительно рассчитав координаты нового положения кнопки.

  • Дополните переопределенную функцию CFindSheet::OnInitDialog() следующим кодом
    Дополнение функции кодом перемещения кнопки
    BOOL CFindSheet::OnInitDialog()
    {
      BOOL bResult = CPropertySheet::OnInitDialog();
      
      // TODO:  Add your specialized code here
      
      int ids[] = {  IDCANCEL,
              ID_APPLY_NOW };
      int len = sizeof(ids) / sizeof(ids[0]);
      for(int i = 0; i < len; i++){
        CWnd *pWnd = GetDlgItem(ids[i]);
        ASSERT(pWnd);
        if(pWnd) 
          pWnd->ShowWindow(FALSE);
      }
      
      CWnd *pBtnOk = GetDlgItem(IDOK);
      ASSERT(pBtnOk);
      
      CRect rectSheet;
      GetWindowRect(rectSheet); // Размер окна свойств
      
      CRect rectBtnOk;
      pBtnOk->GetWindowRect(rectBtnOk); // Размер окна кнопки
      
      // Выяснить расстояние между нижней частью кнопки
      // и нижней частью окна свойств
      int iBorder = rectSheet.bottom - rectBtnOk.bottom;
      
      // Изменить размеры окна свойств
      rectSheet.right += rectBtnOk.Width() + iBorder;
      rectSheet.bottom = rectBtnOk.top;
      MoveWindow(rectSheet);
      
      // Найти первую страницу
      CPropertyPage *pPage = GetPage(0);
      ASSERT(pPage);
      CRect rectPage;
      pPage->GetWindowRect(rectPage); // Размер страницы свойств
      
      // Сохранить ширину и высоту кнопки OK
      int cxOk = rectBtnOk.Width();
      int cyOk = rectBtnOk.Height();
      
      // Переместить кнопку OK
      rectBtnOk.top = rectPage.top;
      rectBtnOk.bottom = rectPage.top + cyOk;
      rectBtnOk.left = rectSheet.right - (cxOk + iBorder);
      rectBtnOk.right = rectBtnOk.left + cxOk;
      ScreenToClient(rectBtnOk);
      pBtnOk->MoveWindow(rectBtnOk);
      
      return bResult;
    }
  • Постройте приложение и убедитесь, что кнопка переместилась на нужное место, а окно свойств изменило размеры
  • Результат должен выглядеть так

    Изменение надписей стандартных кнопок

    Надпись на кнопке должна описывать действия, осуществляемые при щелчке на ней. Увидев в диалоговом окне Find Book кнопку OK, пользователь вряд ли сможет догадаться, что случится, если он щелкнет по ней. Приведет ли щелчок на кнопке OK к закрытию диалогового окна или началу поиска - из названия не ясно. Заменим надпись на кнопке более содержательной.

    Еще раз вспомним, что диалоговый ресурс, который можно править в редакторе диалоговых окон, у нас имеется только для страниц свойств. Мы же в данный момент учимся изменять структуру диалогового окна свойств, которое стандартно сгенерировано MFC -библиотекой и не имеет ресурса в приложении, который мы могли бы визуально отредактировать. Поэтому мы вынуждены добиваться необходимых визуальных результатов программно.

  • Добавьте в конец все той же функции OnInitDialog() класса CFindSheet следующий код
    Дополнение функции кодом изменения надписи на кнопке OK
    BOOL CFindSheet::OnInitDialog()
    {
    ...........................................
      ScreenToClient(rectBtnOk);
      pBtnOk->MoveWindow(rectBtnOk);
      
      pBtnOk->SetWindowText(_T("Close"));
      
      return bResult;
    }
  • Запустите приложение и убедитесь в результате - теперь на кнопке другая надпись
  • Отключение вкладок

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

  • Создать переменную для хранения индекса текущей вкладки
  • Создать массив переменных для хранения индексов всех отключенных в данный момент вкладок
  • Создать обработчик уведомляющего сообщения TCN_SELCHANGING или TCN_SELCHANGE позволяющего выяснить индекс текущей вкладки
  • Разместить в этом обработчике код, принимающий решение о необходимости активизации вкладки
  • В качестве альтернативы можно воспользоваться сообщением
  • Создание переменной хранения индекса текущей вкладки

  • Добавьте в конец объявления класса CFindSheet в файле FindSheet.h следующий код
    Объявление переменной хранения индекса текущей вкладки
    class CFindSheet : public CPropertySheet
    {
    .............................
    protected:
      int m_iLastActivePage;
    };
  • Создание массива индексов отключенных вкладок

    В следующих двух таблицах приведены классы массивов, поддерживаемые MFC, и описание их общедоступных методов

    Классы массивов MFC
    Класс Описание
    CArray Шаблонный класс для создания пользовательских классов массивов
    CByteArray Массив с данными типа байт
    CDWordArray Массив с данными типа DWORD (двойное слово)
    CObArray Массив может содержать классы, производные от класса CObject
    CPtArray Массив указателей на любые объекты и типы данных
    CStringArray Массив объектов типа CString
    CUIntArray Массив данных типа UINT ( unsigned int )
    CWordArray Массив данных типа WORD (слово)
    Методы классов массивов MFC
    Класс Описание
    array[int index] Оператор ображения к значению элемента массива по его индексу. Индексация массива начинается с нуля
    array.GetAt(int index) Возвращает элемент массива по его индексу. Индексация массива начинается с нуля
    array.SetAt(int index, element) Записывает элемент массива по его индексу. Тип данных записываемого элемента должен соответствовать типу данных всего массива. Вызов функции не приводит к росту количества элементов массива.
    array.InsertAt(int index, element) Вставляет новый элемент перед элементом index. Размерность массива увеличивается на единицу.
    array.Add(element) Добавляет новый элемент в конец массива. Добавляемый элемент должен иметь тип данных всего массива.
    array.RemoveAt(int index, int count=1) Удаляет count элементов, начиная с позиции index. Если второй аргумент не указан, то удаляется один элемент.
    array.RemoveAll() Удаляет все элементы массива и присваивает свойству количества элементов массива значение ноль.
    В массивах классов CObArray и CPtArray элементы как объекты не удалаются. Из массивов удаляются только указатели на объекты. При использовании функций RemoveAt() и RemoveAll() необходимо сначала удалить сами объекты, а затем удалить из массива их указатели.
    array.GetSize() Возвращает количество элементов массива.
    array.GetCount() Возвращает количество элементов массива.
    array.GetUpperBound() Возвращает индекс последнего элемента массива.
    array.SetSize(int iNewSize, iGrowBy) Управляет размером массива.
    array.Copy(source) Копирует source в array
    array.Append(source) Присоединяет source в конец array
    В случае с массивами классов CObArray и CPtArray функции Copy() и Append() копируют в массив указатели, а не сами объекты. В результате, изменение значения объекта в одном из массивов приведет к изменению значения соответствующего объекта в другом массиве. Кроме того, при удалении элемента в одном из массивов указатель в другом массиве станет недействительным.

    Создадим стандартный массив MFC -класса CUIntArray, в котором будут содержаться индексы всех вкладок окна свойств, заблокированных в данный момент.

  • Добавьте в конец объявления класса CFindSheet в файле FindSheet.h следующий код
    Создание массива индексов отключенных вкладок
    class CFindSheet : public CPropertySheet
    {
    .....................................................
    protected:
      int m_iLastActivePage;
      CUIntArray m_arrDisabledPages;
    };
  • Добавление функции-члена, отключающей определенные вкладки

    Добавим в класс CFindSheet функцию-член, позволяющую отключать определенные вкладки. Подлежащие отключению вкладки указаны в передаваемом этой функции аргументе переменной длины ( vararg ), представляющем собой список с завершающим терминатором -1. Эта функция добавляет переданные индексы вкладок в массив m_arrDisabledPages, затем маркирует вкладку присоединением к ее названию текста "-Disabled".

    Эту дополнительную работу с добавлением маркера "-Disabled" нужно выполнять потому, что не существует способа по настоящему сделать вкладку недоступной с помощью изменения ее цвета на серый. Поэтому Microsoft рекомендует просто добавлять к заголовку вкладки маркирующий текст.

  • Выделите в панели
  • Нажмите кнопку Add, затем кнопку Finish

    Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции.

  • Исправьте прототип и заголовок функции для возможности передачи аргументов переменной длины, чтобы они смотрелись так
    Правка в файле FindSheet.h
    class CFindSheet : public CPropertySheet
    {
    ...............................................
    public:
      // Отключение вкладок
      void DisablePage(int iFirstPage, ...);
    };
    Правка в файле FindSheet.cpp
    // Отключение вкладок
    void CFindSheet::DisablePage(int iFirstPage, ...)
    {
    }
  • Наполните созданную функцию следующим кодом
    Заполнение функции кодом
    // Отключение вкладок
    void CFindSheet::DisablePage(int iFirstPage, ...)
    {
      int iPage = iFirstPage;
      
      va_list marker;
      va_start (marker, iFirstPage);
      int nArgs = 0;
      while(iPage != -1){// Список заканчивается -1
        // Добавить индекс страницы 
        // в массив отключенных страниц, 
        // если его там нет
        int count = (int)m_arrDisabledPages.GetCount();
        bool flag = false;
        for(int i = 0; i < count; i++)
          if(m_arrDisabledPages.GetAt(i) == (UINT)iPage){
            flag = true;
            break;
          }
        if(!flag)
          m_arrDisabledPages.Add(iPage);
      
        SetDisabledText(iPage);  // Наша функция маркирует вкладку
      
        // Получить индекс следующей страницы
        iPage = va_arg(marker, UINT);
        nArgs++; // счетчик вкладок
        ASSERT(nArgs < 100); // Страж зацикливания
      }
    }
  • Изменение заголовка вкладки

    Создадим функцию SetDisabledText(), вызов которой мы предусмотрели в предыдущей функции.

  • Выделите в панели
  • Нажмите кнопку Add, затем кнопку Finish. Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции
  • Перед заголовком реализации функции добавьте макроопределение, а в тело функции код
    Маркировка вкладок
    
    #define DISABLED_TEXT " - Disabled"
      
    // Маркировка текста заголовков вкладок
    void CFindSheet::SetDisabledText(int iPage)
    {
      CTabCtrl *pTab = GetTabControl();
      ASSERT(pTab);
      TC_ITEM ti;
      char szText[100];
      ti.mask = TCIF_TEXT;
      ti.pszText = szText;
      ti.cchTextMax = 100;
      VERIFY(pTab->GetItem(iPage, ti));
      strcat(szText, DISABLED_TEXT);
      VERIFY(pTab->SetItem(iPage, ti));
    }
  • Обработка уведомляющих сообщений отключенных вкладок

    Получив средства отображения состояния отключенных вкладок (доступна или не доступна), добавим функцию, которая фактически отключает возможность выбора пользователем заблокированной вкладки. Эту задачу выполнит виртуальная функция OnNotify() базового класса CPropertySheet, которую мы переопределим в нашем классе-наследнике CFindSheet.

  • Выделите в панели Class View класс CFindSheet и перейдите в панель Properties
  • Установите в панели
  • Наполните созданную функцию следующим кодом

    Переопределение виртуальной функции OnNotify()
    BOOL CFindSheet::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
      NMHDR *pNmh = (NMHDR*)lParam;
      ASSERT(pNmh);
      if(pNmh->code == TCN_SELCHANGING)
        m_iLastActivePage = GetActiveIndex();
      else if(pNmh->code == TCN_SELCHANGE){
        int iCurrPage = GetActiveIndex();
        if(IsPageDisabled(iCurrPage))
          PostMessage(PSM_SETCURSEL, m_iLastActivePage);
      }
      
      return CPropertySheet::OnNotify(wParam, lParam, pResult);
    }

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

  • Выделите в панели
  • Нажмите кнопку Add, затем кнопку Finish
  • Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции.

  • Наполните созданную функцию следующим кодом
    Проверка доступности вкладок
    // Определяет, числится ли выбираемая
    // пользователем вкладка в недоступных
    BOOL CFindSheet::IsPageDisabled(int iPage)
    {
      BOOL bFoundEntry = FALSE;
      int iSize = (int)m_arrDisabledPages.GetSize();
      
      int i = 0;
      while(i < iSize  !bFoundEntry)
        if(m_arrDisabledPages.GetAt(i) == (UINT)iPage)
          bFoundEntry = TRUE;
        else
          i++;
      
      return bFoundEntry;
    }
  • Пока ничего не было найдено, вкладка Result должна быть пользователю недоступной. Мы для этого создали соответствующий код. Теперь осталось проверить его работу.

  • Найдите функцию-член OnInitDialog() класса CFindSheet и добавьте в конец следующий код
    Сделать недоступной вкладку Result с индексом 1
    BOOL CFindSheet::OnInitDialog()
    {
    ..............................................
      pBtnOk->SetWindowText(_T("Close"));
      
      DisablePage(1, -1);
      
      return bResult;
    }
  • Постройте приложение и убедитесь, что в начальный момент вкладка Result недоступна
  • Подключение вкладок

    Имея возможность отключать вкладки окна свойств нужно дать возможность включать их. Для этого необходимо добавить функцию EnablePage(), восстанавливающую доступ к вкладке, и функцию SetEnabledText(), удаляющую из ее заголовка маркер " - Disabled".

  • В панели Class View выделите класс CFindSheet, поскольку добавлять функцию-член мы будем именно в этот класс
  • Через контекстное меню по щелчку правой кнопкой мыши на классе
  • Нажмите кнопку Add, затем кнопку Finish. Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции
  • Наполните созданную функцию следующим кодом
    Функция включения доступа к вкладке
    // Включение вкладки
    void CFindSheet::EnablePage(int iPage)
    {
      BOOL bFoundEntry = FALSE;
      int iSize = (int)m_arrDisabledPages.GetSize();
      
      int i = 0;
      while(i < iSize  !bFoundEntry)
        if(m_arrDisabledPages.GetAt(i) == (UINT)iPage)
          bFoundEntry = TRUE;
        else
          i++;
      
      if(bFoundEntry){
        m_arrDisabledPages.RemoveAt(i);
        SetEnabledText(iPage);
      }
    }
  • Снова в панели Class View выделите класс CFindSheet
  • Через контекстное меню по щелчку правой кнопкой мыши на классе
  • Нажмите кнопку Add, затем кнопку Finish

    Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции.

  • Наполните созданную функцию следующим кодом
    Удаление маркера " - Disabled" из заголовка вкладки
    // Удаление маркера " - Disabled" из заголовка вкладки
    void CFindSheet::SetEnabledText(int iPage)
    {
      CTabCtrl *pTab = GetTabControl();
      ASSERT(pTab);
      TC_ITEM ti;
      char szText[100];
      ti.mask = TCIF_TEXT;
      ti.pszText = szText;
      ti.cchTextMax = 100;
      VERIFY(pTab->GetItem(iPage, ti));
      // Функция strstr() осуществляет поиск в строке szText
      // первого вхождения подстроки DISABLED_TEXT
      char *pFound = strstr(szText, DISABLED_TEXT);
      if(pFound){
        *pFound = '\0'; // Устанавливаем метку конца заголовка
        VERIFY(pTab->SetItem(iPage, ti));
      }
    }
  • Проверить работу кода включения доступности второй вкладки можно с помощью обработчика кнопки Find первой вкладки. В реальном приложении вкладка Result должна стать доступной лишь после того, как фактически выполненный поиск увенчается успехом. В нашем случае вызов кода разрешения и запрещения доступа к вкладке существенно упрощен.

    Класс CFindSheet является родительским по отношению к классам вкладок CFindPage и CResultPage. Мы создадим обработчик кнопки Find первой вкладки, в котором получим указатель на объект родительского класса, а потом адресуемся к проверяемой функции EnablePage() родительского класса.

  • Откройте панель Resource View оболочки и вызовите на редактирование ресурс IDD_PROPPAGE_FIND
  • Выделите кнопку
  • Наполните созданную функцию следующим кодом
    Обработчик кнопки Find
    void CFindPage::OnBnClickedButtonFind()
    {
      // TODO: Add your control notification handler code here
      CFindSheet *pParentSheet = (CFindSheet*)GetParent();
      
      ASSERT(pParentSheet->IsKindOf(RUNTIME_CLASS(CFindSheet)));
      
      pParentSheet->EnablePage(1);// Вторая вкладка
    }
  • В начало файла FindPage.cpp добавьте директиву #include, подключающую к этому файлу заголовочный файл с описанием класса CFindSheet, используемого в обработчике
    Подключение заголовочного файла FindSheet.h к файлу с обработчиком кнопки FindPage.cpp
    // FindPage.cpp : implementation file
    //
      
    #include "stdafx.h"
    #include "ModalDemo.h"
    #include "FindPage.h"
    #include ".\findpage.h"
      
    #include "FindSheet.h"
      
    // CFindPage dialog
      
    IMPLEMENT_DYNAMIC(CFindPage, CPropertyPage)
    CFindPage::CFindPage()
      : CPropertyPage(CFindPage::IDD)
    {
    }
      
    CFindPage::~CFindPage()
    {
    }
    ..................................................
  • Постройте приложение и убедитесь, что вкладка Result становится доступной после щелчка по кнопке Find
  • Динамическое изменение заголовка вкладки

    В зависимости от результатов поиска по команде из первой вкладке нужно изменить заголовок и доступность второй вкладки. Количество найденных книг для простоты свяжем с индексом элемента в раскрывающемся списке интерфейса первой вкладки. Выбранный индекс списка nHint при нажатии кнопки Find будет передаваться в функцию CFindSheet::SetResult(nHits), которая установит заголовок и доступность второй вкладки Result. Функция будет в большей части повторять код предыдущей функции CFindSheet::SetEnabledText(int iPage) .

  • В начало файла FindSheet.cpp добавьте два макроопределения
    Макроопределения в начале файла FindSheet.cpp
    // FindSheet.cpp : implementation file
    //
    
    #include "stdafx.h"
    #include "ModalDemo.h"
    #include "FindSheet.h"
    #include ".\findsheet.h"
      
    #define RESULT_TAB_INDEX 1
    #define RESULT_TAB_CAPTION "Result: %ld books found"
      
    ......................................................
  • Для класса
  • Нажмите кнопку Add, затем кнопку Finish

    Мастер добавит заготовку в конец файла FindSheet.cpp, а в описание класса добавит прототип функции.

  • Наполните созданную функцию следующим кодом
    Функция SetResult()
    // Изменение содержимого и доступности заголовка вкладки
    void CFindSheet::SetResult(int nHits)
    {
      // Изменение текста заголовка вкладки
      CTabCtrl *pTab = GetTabControl();
      ASSERT(pTab);
      TC_ITEM ti;
      char szText[100];
      ti.mask = TCIF_TEXT;
      ti.pszText = szText;
      ti.cchTextMax = 100;
      VERIFY(pTab->GetItem(RESULT_TAB_INDEX, ti));
      sprintf(szText, RESULT_TAB_CAPTION, nHits);
      VERIFY(pTab->SetItem(RESULT_TAB_INDEX, ti));
      
      // Управление доступностью вкладки
      if(nHits <= 0)
        DisablePage(RESULT_TAB_INDEX, -1);
      else
        EnablePage(RESULT_TAB_INDEX);
    }
  • Перейдите в файл FindPage.cpp и найдите обработчик кнопки Find, который измените так
    Изменение обработчика кнопки Find
    void CFindPage::OnBnClickedButtonFind()
    {
      // TODO: Add your control notification handler code here
      CFindSheet *pParentSheet = (CFindSheet*)GetParent();
      
      ASSERT(pParentSheet->IsKindOf(RUNTIME_CLASS(CFindSheet)));
       
    //  pParentSheet->EnablePage(1);// Вторая вкладка
      
      // Демонстрация управления вкладкой Result по индексу списка
      int iIndex = m_cboLanguages.GetCurSel();
      pParentSheet->SetResult(iIndex);
    }
  • Постройте приложение и убедитесь, что:
  • количество "найденных" книг равно индексу элемента, выбранного в раскрывающемся списке
  • динамически меняется заголовок вкладки
  • доступность вкладки зависит от успеха "поиска"
  • Все работает, как мы задумали. Внешний вид приложения должен быть примерно таким

    Вернуться к учебному плану