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

Однодокументный интерфейс MFC

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

Цель работы

  • Ознакомиться с архитектурой "Документ/Вид", используемой для создания однодокументных и многодокументных приложений
  • Научиться создавать каркасы однодокументных приложений
  • Научиться отделять данные от их представления
  • Научиться инкапсулировать данные в собственные классы C++
  • Научиться организовывать взаимодействие данных и меню
  • Посадить все опции меню одной и той же категории на один комбинированный обработчик с распознаванием нажатой опции
  • Создать собственную панель инструментов.
  • Научиться присоединять панель инструментов к рамке приложения
  • Отображать и скрывать панель инструментов с помощью элемента меню
  • Помещать поле со списком на панель инструментов
  • Отображать описание элементов панели инструментов в строке состояния
  • Добавлять собственные элементы строки состояния
  • Применение концепции Документ/Вид

    В основе приложений MFC лежит концепция document object/view window (Документ/Вид). Обычно документ представляет собой отдельный файл, который приложение способно открыть. В нем хранятся данные, управляемые приложением. Отображение данных и интерфейс работы с ними обеспечивает представление. Представление способно отображать данные документа частично и в разной форме, выполняя заданный срез документа.

    Отношение между документом и представлением строится по принципу "один ко многим". То есть один документ (одно содержание) может иметь несколько представлений (несколько форм), но каждое из этих представлений отображает одни и те же данные.

    Программно такая концепция реализуется механизмом SDI (Single Document Interface) - однодокументный интерфейс. Управление документом осуществляется объектом на основе производного класса от базового класса CDocument, а управление интерфейсами - на основе базового класса CView.

    Основой для реализации представления является фреймовое окно, определяющее границы приложения и называемое окном-рамкой. Оно порождается классом CFrameWnd. Фреймовое окно является главным окном приложения и играет роль родительского по отношению к содержащимся в нем окнам. Неотъемлемой частью фреймового окна является его заголовок и меню. Клиентскую область фреймового окна занимают всевозможные дочерние окна, в том числе окно панели инструментов, окно строки состояния, окно представления и др.

    В приложениях SDI, созданных мастером MFC Application Wizard, исходный код реализации фреймового окна (реализация класса CMainFrame ) размещается в файлах проекта MainFrm.h и MainFrm.cpp. Класс CMainFrame наследует практически все свои функциональные возможности от базового класса CFrameWnd.

    В свою очередь, фреймовым окном управляет каркас приложения, порожденный классом CWinApp. Каркас приложения не имеет визуального представления, его задача - обеспечить взаимодействие окна-рамки и дочерних фреймовых окон. Это взаимодействие осуществляется с помощью сообщений. Модель отношений между базовыми классами приложения SDI приведена на рисунке

    Класс CDocument обеспечивает стандартное поведение объектов документа MFC -приложения. Независимо от того, создается однодокументное приложение или многодокументное, мастер AppWizard создает только один класс документа, производный от базового класса CDocument.

    Классы массивов MFC

    В библиотеке 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() копируют в массив указатели, а не сами объекты. В результате, изменение значения объекта в одном из массивов приведет к изменению значения соответствующего объекта в другом массиве. Кроме того, при удалении элемента в одном из массивов указатель в другом массиве станет недействительным.

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

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

  • Войдите в свой каталог выполнения лабораторных работ
  • Создайте новый проект MFC Application и назовите его DrawSDI
  • Настройте мастер приложений Таблица настройки мастера создания заготовки приложения Свойство проекта Значение Вкладка Application Type Application Type: Single document Document/View architecture support Включено Resource language: Английский (США) Project style: MFC standard Use of MFC: Use MFC in a static library Вкладка Compound Document Support Compound document support: None Вкладка Document Template Strings File extension: dhc Вкладка Database Support Database support: None Вкладка User Interface Features Main frame styles: Minimize box Maximize box System menu Initial status bar Toolbars: Standard docking Вкладка Advanced Features Все оставить по умолчанию Вкладка Generated Classes Base class: CView Все остальное Оставить по умолчанию
  • После настройки вкладки мастера должны иметь такой вид

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

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

  • В панели Class View найдите верхний узел с названием DrawSDI и для него выполните опцию Add Class контекстного меню
  • В диалоговом окне
  • Появившееся окно мастера добавления класса заполните так
  • Щелкните по кнопке Finish
  • Мастер создаст заготовки для объявления и определения класса и разместит их в отдельных файлах MyLine.h и MyLine.cpp
  • Отредактируйте эти заготовки так, чтобы класс мог хранить координаты начальной и конечной точек линии. Для этого воспользуйтесь мастером добавления переменных, предварительно выделяя в панели Class View класс MyLine. Переменные определите с модификатором доступа private
  • Для того, чтобы класс мог нарисовать линию между двумя точками, добавьте в него с помощью мастера
  • Не забудьте нажать кнопку Add перед нажатием кнопки Finish

    В итоге должно получиться следующее

    Файл MyLine.h
    #pragma once
    #include "afx.h"
    #include "atltypes.h"
    
    class MyLine :
      public CObject
    {
    public:
      MyLine(CPoint, CPoint);
      ~MyLine(void);
    private:
      // Начальная точка линии
      CPoint m_pointBegin;
      // Конечная точка линии
      CPoint m_pointEnd;
    public:
      // Функция-член класса MyLine для рисования линий
      void Draw(CDC* pDC);
    };
    Файл MyLine.cpp
    #include "StdAfx.h"
    #include ".\myline.h"
      
    MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
    {
      m_pointBegin = pointFrom;
      m_pointEnd = pointTo;
    }
      
    MyLine::~MyLine(void)
    {
    }
      
    // Функция-член класса MyLine для рисования линий
    void MyLine::Draw(CDC* pDC)
    {
      // Нарисовать линию
      pDC->MoveTo(m_pointBegin);
      pDC->LineTo(m_pointEnd);
    }

    Конструирование класса документа

    Экземпляры объекта MyLine можно сохранять в объекте документа в простом динамическом массиве, реализуемом в MFC -библиотеке классом CObArray. Объект этого класса массива изменяет свой размер динамически в зависимости от количества находящихся в нем элементов. Этот класс-массив может содержать в себе любой объект, являющийся наследником класса CObject, размер массива ограничивается только количеством свободной памяти в компьютере.

    Создадим в классе CDrawSDIDoc приложения переменную-экземпляр класса CObArray для хранения линий-экземпляров созданного класса MyLine. Назовем ее m_Lines. Для этого:

  • В панели Class View выделите класс CDrawSDIDoc, через опцию Add Variable вызовите мастер и заполните его так

  • Таким же образом добавьте в класс CDrawSDIDoc функцию-член AddLine() для добавления объектов линий в массив, идентифицируемый только что созданной переменной. Мастер заполните так

    Мастер внесет следующие добавления в класс CDrawSDIDoc

    Файл DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ..............................................
    
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Добавить линию
      MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
    };
    Файл DrawSDIDoc.cpp
    // Добавить линию
    MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
    {
      return NULL;
    }
  • Отредактируйте заготовку функции AddLine() так

    Файл DrawSDIDoc.cpp
    // Добавить линию
    MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
    {
      MyLine* pLine = NULL; // Создали указатель на стеке
      
      try // Попытка
      {
        // Создать экземпляр объекта-линии на куче
        pLine = new MyLine(pointBegin, pointEnd); // Конструктор
        // Добавить указатель на новую линию в массив объектов
        m_Lines.Add(pLine);
        // Отметить документ как измененный
        SetModifiedFlag();
      }
      catch(CMemoryException* pErr) // Ловушка на недостаток памяти
      {
        // Сообщили пользователю
        AfxMessageBox("Недостаточно памяти", MB_ICONSTOP | MB_OK);
        // Уничтожим объект-линию, на котором сгенерировалось исключение
        if(pLine){
          delete pLine;
          pLine = NULL;
        }
      
        // Удалить объект с исключением
        pErr->Delete();
      }
      
      return pLine; // Вернуть указатель на объект-линию
    }
  • Подключите объявление класса MyLine к классу CDrawSDIDoc вначале файла DrawSDIDoc.h так

    Файл DrawSDIDoc.h
    // DrawSDIDoc.h : interface of the CDrawSDIDoc class
    //
      
      
    #pragma once
    #include "afxcoll.h"
      
    #include "MyLine.h"
      
    class CDrawSDIDoc : public CDocument
    {
    ...........................................
    };
  • Добавим в класс CDrawSDIDoc еще две публичные функции-члена, играющих роль функций доступа к закрытой переменной m_Lines:

  • GetLineCount() - возвращает количество объектов-линий, хранящихся в массиве m_Lines класса CObArray
  • GetLine() - возвращает указатель на линию, хранящийся в массиве m_Lines, по индексу массива
  • Обе функции доступа будут нужны в классе представления CDrawSDIView для отображения линий, хранящихся как закрытые в классе документа CDrawSDIDoc.

    Заполните мастера добавления функций так

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

    Добавления, выполненные мастерами, должны быть такими

    Добавления в файле DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ..........................................
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Добавить линию
      MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
      // Количество нарисованных линий
      int GetLineCount(void);
      // Возвращает указатель на объект-линию по индексу
      MyLine* GetLine(int index);
    };
    Добавления в файле DrawSDIDoc.cpp
    // Количество нарисованных линий
    int CDrawSDIDoc::GetLineCount(void)
    {
      return 0;
    }
      
    // Возвращает указатель на объект-линию по индексу
    MyLine* CDrawSDIDoc::GetLine(int index)
    {
      return NULL;
    }
  • Заполните код функций так

    Код функций доступа в файле DrawSDIDoc.cpp
    // Количество нарисованных линий
    int CDrawSDIDoc::GetLineCount(void)
    {
      // Возвратить количество элементов в массиве
      return (int)m_Lines.GetCount();
    }
      
    // Возвращает указатель на объект-линию по индексу
    MyLine* CDrawSDIDoc::GetLine(int index)
    {
      // Возвратить указатель на объект-линию, хранящийся 
      // в указанной позиции в массиве CObArray m_Lines
      return (MyLine*)m_Lines[index];
    }
  • Постройте приложение, чтобы своевременно выловить возможные ошибки
  • Конструирование класса представления

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

  • Введите частную переменную m_pointPrevPos в класс CDrawSDIView для хранения предыдущей точки рисования

  • В панели Class View выберите класс CDrawSDIView
  • Используя вкладку Properties в режиме Messages, добавьте к классу CDrawSDIView функции для обработки сообщений операционной системы

  • WM_LBUTTONDOWN - для начала рисования и захвата мыши
  • WM_LBUTTONUP - для завершения рисования и освобождения мыши
  • WM_MOUSEMOVE - для выполнения рисования и запоминания координат линий в классе документа
  • Добавление переменной и обработчиков в класс CDrawSDIView
    class CDrawSDIView : public CView
    {
    ...................................................
    private:
      // Предыдущая точка рисования
      CPoint m_pointPrevPos;
    public:
      afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
      afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
      afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    };
    Регистрация обработчиков в карте сообщений DrawSDIView.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIView, CView)
      // Standard printing commands
      ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
      ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
      ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
      ON_WM_LBUTTONDOWN()
      ON_WM_LBUTTONUP()
      ON_WM_MOUSEMOVE()
    END_MESSAGE_MAP()
  • Отредактируйте функции-обработчики, как показано ниже

    Код функций-обработчиков в файле DrawSDIView.cpp
    void CDrawSDIView::OnLButtonDown(UINT nFlags, CPoint point)
    {
      // Захватим мышь, чтобы другое приложение не могло
      // перезахватить мышь в момент, когда она
      // покинет пределы окна приложения
      SetCapture(); // мышь только для нас!!!
      
      // Сохраним точку начала рисования
      m_pointPrevPos = point;
      
      CView::OnLButtonDown(nFlags, point);
    }
      
    void CDrawSDIView::OnLButtonUp(UINT nFlags, CPoint point)
    {
      // Если мышь была ранее захвачена нашим приложением,
      // то отдадим ее, чтобы другие приложения
      // тоже могли использовать мышь
      if(GetCapture() == this)
        ReleaseCapture();
      
      CView::OnLButtonUp(nFlags, point);
    }
      
    void CDrawSDIView::OnMouseMove(UINT nFlags, CPoint point)
    {
      // Если нажата левая кнопка мыши, то рисовать
      if((nFlags  MK_LBUTTON) == MK_LBUTTON){
        // Если мышь захвачена нами, то рисовать
        if(GetCapture() == this){
          // Получить контекст устройства
          // клиентской области окна приложения
          CClientDC dc(this);
          // Добавить линию к документу и вернуть на нее 
          // указатель как на объект, чтобы воспользоваться
          // ее функцией-членом для рисования
          MyLine* pLine = 
            GetDocument()->AddLine(m_pointPrevPos, point);
      
          // Нарисовать текущую линию
          pLine->Draw(dc);
      
          // Сохранить текущую точку в качестве предыдущей
          m_pointPrevPos = point;
        } // end of if
      } // end of if
      
      CView::OnMouseMove(nFlags, point);
    }
  • Постройте приложение, порисуйте и поперемещайте клиента заграницы экрана

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

  • Добавьте в функцию OnDraw() код прорисовки всех сохраненных линий (не забудьте раскомментировать аргумент указателя CDC* в заголовке функции), как показано в листинге

    Код функции OnDraw() в файле DrawSDIView.cpp
    void CDrawSDIView::OnDraw(CDC* pDC)
    {
      CDrawSDIDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      if (!pDoc)
        return;
      
      // TODO: add draw code for native data here
      
      // Получить количество линий в документе
      int count = pDoc->GetLineCount();
      
      // Если есть линии, то нарисовать их все
      if(count){
        for(int i = 0; i < count; i++){
          MyLine* pLine = pDoc->GetLine(i);
          pLine->Draw(pDC);
        }
      } 
    }
  • Постройте приложение и убедитесь, что теперь все восстанавливается после разрушения окна приложения
  • Сохранение и загрузка рисунков

    Когда в классе документа CDrawSDIDoc срабатывает функция OnNewDocument(), то данные в массиве m_Lines необходимо уничтожить и освободить занимаемую ими память. Это ни в коем случае нельзя делать в функции OnNewDocument(), (хотя принципиально это и можно сделать), потому, что по логике своего предназначения эта функция должна инициализировать данные нового документа.

    Удаление текущего рисунка

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

  • В панели Class View выберите класс CDrawSDIDoc
  • Во вкладке Properties включите режим Overrides (Переопределение)
  • Найдите виртуальную функцию DeleteContents класса CDocument и создайте переопределение этой функции

  • Заполните переопределенную функцию DeleteContents() так

    Код переопределения виртуальной функции DeleteContents() в файле DrawSDIDoc.cpp
    void CDrawSDIDoc::DeleteContents()
    {
      // Получить количество линий в массиве объектов
      int count = (int)m_Lines.GetCount();
      
      // Попытка выполнить удаление объектов
      try 
      {
        if(count){ // есть что удалять
      
          // Освободить память на куче по каждому объекту
          for(int i = 0; i < count; i++)
            delete (MyLine*)m_Lines.GetAt(i);
      
          // Установить массив в исходное состояние
          m_Lines.RemoveAll();
        }
      }
      // Ловушка исключения о нарушении памяти
      catch(CMemoryException* pErr) 
      {
        // Отобразить сообщение о причине исключения
        pErr->ReportError();
      
        // Удалить объект, вызвавший исключение
        pErr->Delete();
      }
      
      CDocument::DeleteContents();
    }
  • Сохранение документа в файл и загрузка из файла

    Добавим возможность сохранения документа. Воспользуемся функцией сериализации Serialize() класса CDrawSDIDoc, которую перепишем так, чтобы она только передавала сообщение о преобразовании в последовательную форму объекту-массиву m_Lines. В самом же объекте-массиве m_Lines предусмотрим свою функцию сериализации, в которой и осуществим сохранение документа с использованием функциональных возможностей потока ввода-вывода C++.

  • В классе CDrawSDIDoc найдите функцию Serialize() преобразования в последовательную форму
  • Удалите все содержимое функции и перепишите ее так

    Функция сериализации класса CDrawSDIDoc
    void CDrawSDIDoc::Serialize(CArchive ar)
    {
      // Передать объекту-массиву сообщение
      // о преобразовании в последовательную форму
      m_Lines.Serialize(ar);
    }
  • Добавьте новую функцию-член к классу MyLine, выделив класс MyLine в панели Class View и заполнив мастер

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

  • Отредактируйте функцию так

    Функция сериализации класса MyLine
    // Преобразование в последовательную форму объекта-массива
    void MyLine::Serialize(CArchive  ar)
    {
      CObject::Serialize(ar);
      
      if(ar.IsStoring())
        ar << m_pointBegin << m_pointEnd; // Сохранить
      else
        ar >> m_pointBegin >> m_pointEnd; // Восстановить
    }
  • Постройте приложение и попробуйте сохранить документ на диск. Система выдаст сообщение, что невозможно сохранить документ

  • Исправим это...

    Зарегистрируем класс MyLine, как способный самостоятельно выполнять сериализацию. Для этого:

  • В начало объявления класса MyLine в файле MyLine.h и перед конструктором класса в файле MyLine.cpp добавьте по строке регистрации и конструктор по умолчанию

    Конструктор по умолчанию теперь нужно определить самим, поскольку определен общий конструктор, который делает невозможным вызов автоматического конструктора по умолчанию.

    Добавление в объявление класса MyLine в файле MyLine.h
    class MyLine : public CObject
    {
    public:
      DECLARE_SERIAL(MyLine);
      MyLine(){}; // Констуктор по умолчанию
      MyLine(CPoint, CPoint);
      ~MyLine(void);
    private:
      // Начальная точка линии
      CPoint m_pointBegin;
      // Конечная точка линии
      CPoint m_pointEnd;
    public:
      // Функция-член класса MyLine для рисования линий
      void Draw(CDC* pDC);
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Преобразование в последовательную форму объекта-массива
      void Serialize(CArchive  ar);
    };
    Добавление вначало файла MyLine.cpp
    #include "StdAfx.h"
    #include ".\myline.h"
      
    IMPLEMENT_SERIAL(MyLine, CObject, 1)
      
    MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
    {
      m_pointBegin = pointFrom;
      m_pointEnd = pointTo;
    }
      
    MyLine::~MyLine(void)
    {
    }
    ....................................................
  • Постройте приложение и убедитесь, что документы сохраняются на диске и загружаются с диска. Вот один из восстановленных шедевров

  • Реконструкция приложения для добавления цвета и толщины линий

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

    Изменения в классе MyLine

  • Добавьте переменные в класс MyLine и функцию доступа

    Добавления в файл MyLine.h
    class MyLine : public CObject
    {
    public:
      DECLARE_SERIAL(MyLine);
      MyLine(){}
      MyLine(CPoint, CPoint);
      ~MyLine(void);
    private:
      // Начальная точка линии
      CPoint m_pointBegin;
      // Конечная точка линии
      CPoint m_pointEnd;
    public:
      // Функция-член класса MyLine для рисования линий
      void Draw(CDC* pDC);
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Преобразование в последовательную форму объекта-массива
      void Serialize(CArchive  ar);
    private:
      // Для хранения цвета
      COLORREF color;
      // Для хранения толщины линий
      int widthLine;
    public:
      // Функция доступа
      void Set(COLORREF c, int w)
      {
        color = c;
        widthLine = w;
      }
    };
  • Инициализируйте переменные в общем конструкторе
    Добавления в файл MyLine.cpp
    MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
    {
      m_pointBegin = pointFrom;
      m_pointEnd = pointTo;
      
      color = RGB(0, 0, 0);
      widthLine = 1;
    }
  • Дополните функцию рисования Draw класса MyLine

    Добавления в файл MyLine.cpp
    // Функция-член класса MyLine для рисования линий
    void MyLine::Draw(CDC* pDC)
    {
      // Создать перо
      CPen currPen(PS_SOLID, widthLine, color);// Конструктор пера
      // Установить новое перо в качестве рисующего объекта
      CPen* pOldPen = pDC->SelectObject(currPen);
      
      // Нарисовать линию
      pDC->MoveTo(m_pointBegin);
      pDC->LineTo(m_pointEnd);
      
      // Восстановить прежнее перо
      pDC->SelectObject(pOldPen);
    }
  • Измените функцию MyLine::Serialize()

    Добавления в файл MyLine.cpp
    // Преобразование в последовательную форму объекта-массива
    void MyLine::Serialize(CArchive  ar)
    {
      CObject::Serialize(ar);
      
      if(ar.IsStoring()) // Сохранить
        ar << m_pointBegin << m_pointEnd 
           << (DWORD)color << widthLine; 
      else // Восстановить
        ar >> m_pointBegin >> m_pointEnd 
           >> (DWORD)color >> widthLine;
    }
  • Постройте приложение и проверьте - пока ничего измениться не должно
  • Изменения в классе CDrawSDIDoc

  • Добавьте в класс документа CDrawSDIDoc массив таблицы цветов, индекс массива таблицы цветов и толщину линии. Код приведен ниже

    Добавления в файл DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ................................................
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Добавить линию
      MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
      // Количество нарисованных линий
      int GetLineCount(void);
      // Возвращает указатель на объект-линию по индексу
      MyLine* GetLine(int index);
      virtual void DeleteContents();
    private:
      int m_iColor; // индекс массива таблицы цветов
      int m_iWidth;  // толщина линии
    public:
      // Объявление таблицы цветов
      static const COLORREF m_Colors[8];
    };
  • Добавьте инициализацию переменных m_iColor и m_iWidth в функции OnNewDocument()

    Добавление в функцию OnNewDocument() файла DrawSDIDoc.cpp
    BOOL CDrawSDIDoc::OnNewDocument()
    {
      if (!CDocument::OnNewDocument())
        return FALSE;
      
      // TODO: add reinitialization code here
      // (SDI documents will reuse this document)
      // Инициализировать начальный цвет черным
      // Толщину линии 1
      m_iColor = 0;
      m_iWidth = 1;
      
      return TRUE;
    }
  • Добавьте в файл DrawSDIDoc.cpp таблицу цветов сразу после таблицы сообщений

    Добавления таблицы цветов в файл DrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
    END_MESSAGE_MAP()
      
    const COLORREF CDrawSDIDoc::m_Colors[] = {
      RGB(0, 0, 0),    // Black - черный
      RGB(0, 0, 255),    // Blue - синий
      RGB(0, 255, 0),    // Green - зеленый
      RGB(0, 255, 255),  // Cyan - голубой
      RGB(255, 0, 0),    // Red - красный
      RGB(255, 0, 255),  // Magenta - сиреневый
      RGB(255, 255, 0),  // Yellow - желтый
      RGB(255, 255, 255)  // White - белый
    };
  • Измените функцию AddLine() класса CDrawSDIDoc так, чтобы перед сохранением объекта MyLine в него добавлялась информация о цвете и толщине линии

    Добавления в функцию AddLine() файла DrawSDIDoc.cpp
    // Добавить линию
    MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
    {
      MyLine* pLine = NULL; // Создали указатель на стеке
      
      try // Попытка
      {
        // Создать экземпляр объекта-линии на куче
        pLine = new MyLine(pointBegin, pointEnd); // Конструктор
        // Добавить информацию о текущем цвете и толщине линии
        pLine->Set(m_Colors[m_iColor], m_iWidth);
        // Добавить указатель на новую линию в массив объектов
        m_Lines.Add(pLine);
        // Отметить документ как измененный
        SetModifiedFlag();
      }
      catch(CMemoryException* pErr) // Ловушка на недостаток памяти
      {
        // Сообщили пользователю
        AfxMessageBox("Недостаточно памяти", MB_ICONSTOP | MB_OK);
        // Уничтожим объект-линию, на котором сгенерировалось исключение
        if(pLine){
          delete pLine;
          pLine = NULL;
        }
      
        // Удалить объект с исключением
        pErr->Delete();
      }
      
      return pLine; // Вернуть указатель на объект-линию
    }
  • Изменение меню

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

  • Добавить категорию для выбора цвета
  • Добавить категорию для выбора толщины линии
  • Добавить обработчики для каждого нового пункта меню
  • Добавить обработчики, которые бы проверяли, какая позиция меню представляет текущий цвет и текущую толщину линии
  • Изменение ресурса меню

  • Через вкладку Resource View вызовите ресурс меню на редактирование
  • Отредактируйте ресурс меню в соответствии с рисунками, которые получены для режима Edit IDs оболочки
  • Мастер создал макроопределения. Чтобы их увидеть, в панели Solution Explorer необходимо файл приложения Resource.h. Файл должен содержать, примерно, следующий код

    Файл приложения Resource.h
    //{{NO_DEPENDENCIES}}
    // Microsoft Visual C++ generated include file.
    // Used by DrawSDI.rc
    //
    #define IDD_ABOUTBOX                    100
    #define IDP_OLE_INIT_FAILED             100
    #define IDR_MAINFRAME                   128
    #define IDR_DrawSDITYPE                 129
    #define ID_COLOR_BLACK                  32771
    #define ID_COLOR_BLUE                   32772
    #define ID_COLOR_GREEN                  32773
    #define ID_COLOR_CYAN                   32774
    #define ID_COLOR_RED                    32775
    #define ID_COLOR_MAGENTA                32776
    #define ID_COLOR_YELLOW                 32777
    #define ID_COLOR_WHITE                  32778
    #define ID_WIDTH_WIDTH                  32779
    #define ID_WIDTH_1                      32780
    #define ID_WIDTH_2                      32781
    #define ID_WIDTH_3                      32782
    #define ID_WIDTH_4                      32783
    #define ID_WIDTH_5                      32784
    #define ID_WIDTH_6                      32785
    #define ID_WIDTH_7                      32786
    #define ID_WIDTH_8                      32787
    ...............................................

    Добавление обработчиков меню

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

    Первым способом является вызов в редакторе меню через команду Add Event Handler контекстного меню соответствующего пункта

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

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

    Первый тип сообщения ( COMMAND ) Windows генерирует каждый раз, когда пользователь выбирает соответствующий пункт меню. При этом сообщение передается со значением ID идентификатора пункта в качестве параметра сообщения. Сообщение типа UPDATE_COMMAND_UI не является чистым сообщением Windows, это запрос MFC на обновление внешнего вида отдельных пунктов меню перед их показом пользователю. Мы создадим обработчики для обеих сообщений каждого пункта, добавленного нами в меню.

  • Откройте вкладку Class View и выберите класс CDrawSDIDoc
  • В панели Properties установите режим Events и найдите идентификатор ID_COLOR_BLACK
  • Раскройте узел событий для пункта меню как объекта и для каждого из сообщений COMMAND и UPDATE_COMMAND_UI создайте обработчики, которые заполните так

    Обработчики меню для выбора цвета Black
    void CDrawSDIDoc::OnColorBlack()
    {
      m_iColor = 0;
    }
      
    void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      // Отобразить пункт меню (если TRUE) с флажком
      pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
    }
  • Аналогичным образом создайте обработчики для выбора всех других цветов

    Обработчики меню для выбора остальных цветов
    void CDrawSDIDoc::OnColorBlue()
    {
      m_iColor = 1;
    }
      
    void CDrawSDIDoc::OnUpdateColorBlue(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iColor == 1 ? TRUE : FALSE);
    }
      
    .........................................................
      
    void CDrawSDIDoc::OnColorWhite()
    {
      m_iColor = 7;
    }
      
    void CDrawSDIDoc::OnUpdateColorWhite(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iColor == 7 ? TRUE : FALSE);
    }
  • Подобным образом создайте обработчики для выбора через меню толщины линий рисования

    Обработчики меню для выбора толщины линий
    void CDrawSDIDoc::OnWidth1()
    {
      m_iWidth = 1;
    }
      
    void CDrawSDIDoc::OnUpdateWidth1(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iWidth == 1 ? TRUE : FALSE);
    }
      
    .....................................................
      
    void CDrawSDIDoc::OnWidth8()
    {
      m_iWidth = 8;
    }
      
    void CDrawSDIDoc::OnUpdateWidth8(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iWidth == 8 ? TRUE : FALSE);
    }
  • Построение комбинированных обработчиков

    Мы видим, что количество обработчиков пунктов меню очень большое. Имеется возможность скомбинировать обработчики меню каждой категории, существенно уменьшив количество обработчиков и сделав код более изящным. Для этого применяют групповые сообщения Windows с параметрами.

    Карта сообщений для индивидуальных обработчиков

    Если посмотреть карту сообщений в файле DrawSDIDoc.cpp, то можно увидеть, что обработчики и идентификаторы пунктов меню связаны так

    Карта сообщений в файле CDrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND(ID_COLOR_BLACK, OnColorBlack)
      ON_UPDATE_COMMAND_UI(ID_COLOR_BLACK, OnUpdateColorBlack)
      ON_COMMAND(ID_COLOR_BLUE, OnColorBlue)
      ON_UPDATE_COMMAND_UI(ID_COLOR_BLUE, OnUpdateColorBlue)
      ON_COMMAND(ID_COLOR_GREEN, OnColorGreen)
      ON_UPDATE_COMMAND_UI(ID_COLOR_GREEN, OnUpdateColorGreen)
      ON_COMMAND(ID_COLOR_CYAN, OnColorCyan)
      ON_UPDATE_COMMAND_UI(ID_COLOR_CYAN, OnUpdateColorCyan)
      ON_COMMAND(ID_COLOR_RED, OnColorRed)
      ON_UPDATE_COMMAND_UI(ID_COLOR_RED, OnUpdateColorRed)
      ON_COMMAND(ID_COLOR_MAGENTA, OnColorMagenta)
      ON_UPDATE_COMMAND_UI(ID_COLOR_MAGENTA, OnUpdateColorMagenta)
      ON_COMMAND(ID_COLOR_YELLOW, OnColorYellow)
      ON_UPDATE_COMMAND_UI(ID_COLOR_YELLOW, OnUpdateColorYellow)
      ON_COMMAND(ID_COLOR_WHITE, OnColorWhite)
      ON_UPDATE_COMMAND_UI(ID_COLOR_WHITE, OnUpdateColorWhite)
      ON_COMMAND(ID_WIDTH_1, OnWidth1)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_1, OnUpdateWidth1)
      ON_COMMAND(ID_WIDTH_2, OnWidth2)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_2, OnUpdateWidth2)
      ON_COMMAND(ID_WIDTH_3, OnWidth3)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_3, OnUpdateWidth3)
      ON_COMMAND(ID_WIDTH_4, OnWidth4)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_4, OnUpdateWidth4)
      ON_COMMAND(ID_WIDTH_5, OnWidth5)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_5, OnUpdateWidth5)
      ON_COMMAND(ID_WIDTH_6, OnWidth6)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_6, OnUpdateWidth6)
      ON_COMMAND(ID_WIDTH_7, OnWidth7)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_7, OnUpdateWidth7)
      ON_COMMAND(ID_WIDTH_8, OnWidth8)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_8, OnUpdateWidth8)
    END_MESSAGE_MAP()

    Обратите внимание, что каждое макроопределение карты сообщений, сгенерированное мастером Add Event Handler, имеет в заголовке только два параметра. Но существуют макроопределения, которые мастером не генерируются и которые нужно вводить вручную, но зато они имеют три параметра и способны регистрировать на один обработчик диапазон идентификаторов. Применительно к нашей задаче они могут выглядеть так

    Возможная таблица сообщений с диапазоном в файле CDrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnUpdateWidthUI)
    END_MESSAGE_MAP()

    Обработчикам передаются значения ID пунктов меню

    Написанные нами ранее в приложении обработчики, на примере выбора одного цвета, имеют вид

    Обработчики сообщений файла CDrawSDIDoc.cpp
    void CDrawSDIDoc::OnColorBlack()
    {
      m_iColor = 0;
    }
      
    void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      // Отобразить пункт меню (если TRUE) с флажком
      pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
    }
    ....................................................

    Мастер при создании первого обработчика типа OnColorBlack() не предусмотрел в нем передачу аргументов, однако этот обработчик может принимать аргумент в виде значения идентификатора нажатого пункта меню. Во второй обработчик типа OnUpdateColorBlack(CCmdUI *pCmdUI) в качестве аргумента передается указатель на объект, в свойстве pCmdUI->m_nID которого содержится значение идентификатора соответствующего пункта меню. Учитывая это, мы можем создать комбинированные обработчики для каждой категории меню.

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

    Дополнительные пункты меню категории Width
    Название пункта Толщина пера
    Medium (Средняя) 16
    Thick (Толстое) 24
    Very Thick (Очень толстое) 32
  • Через панель Resource View вызовите редактор меню
  • Добавьте в конец категории Width три новых пункта так, чтобы вначале названия пунктов были введены цифрами 16, 24, 32. Это нужно для того, чтобы редактор автоматически сформировал имена идентификаторов похожими на предыдущие пункты
  • Измените названия уже сгенерированных пунктов на слова, приведенные в таблице выше
  • Должно получиться что-то подобное в режиме Edit IDs редактора меню

    Новые комбинированные обработчики

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

  • Прототипы расположены объявлении класса CDrawSDIDoc в файле DrawSDIDoc.h
  • Определения размещены в конце файла DrawSDIDoc.cpp
  • Связь идентификаторов пунктов меню и имен соответствующих обработчиков прописана в карте сообщений класса CDrawSDIDoc в начале файла DrawSDIDoc.cpp
  • Для того, чтобы нам не организовывать в комбинированном обработчике условия проверки значений передаваемых идентификаторов, необходимо учесть, чтобы значения идентификаторов в каждой из категорий шли в порядке возрастания с шагом единица. Для этого:

  • Откройте через панель Solution Explorer файл Resource.h

    Он должен иметь примерно такой вид

    Идетификаторы пунктов меню в файле Resource.h
    ...............................................  
    #define ID_COLOR_BLACK                  32771
    #define ID_COLOR_BLUE                   32772
    #define ID_COLOR_GREEN                  32773
    #define ID_COLOR_CYAN                   32774
    #define ID_COLOR_RED                    32775
    #define ID_COLOR_MAGENTA                32776
    #define ID_COLOR_YELLOW                 32777
    #define ID_COLOR_WHITE                  32778
    #define ID_WIDTH_WIDTH                  32779
    #define ID_WIDTH_1                      32780
    #define ID_WIDTH_2                      32781
    #define ID_WIDTH_3                      32782
    #define ID_WIDTH_4                      32783
    #define ID_WIDTH_5                      32784
    #define ID_WIDTH_6                      32785
    #define ID_WIDTH_7                      32786
    #define ID_WIDTH_8                      32787
    #define ID_WIDTH_16                     32788
    #define ID_WIDTH_24                     32789
    #define ID_WIDTH_32                     32790
    ...............................................
  • В файле DrawSDIDoc.h уберите прототипы всех созданных ранее обработчиков для категорий меню Color и Width и введите прототипы комбинированных обработчиков отдельно для каждой категории в конец класса

    Код должен выглядеть так

    Прототипы комбинированных обработчиков в файле DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ................................................
    public:
      // Объявление таблицы цветов
      static const COLORREF m_Colors[8];
      
      // Комбинированные обработчики Color и Width
      afx_msg void OnColorCommand(UINT nID);
      afx_msg void OnUpdateColorUI(CCmdUI *pCmdUI);
      afx_msg void OnWidthCommand(UINT nID);
      afx_msg void OnUpdateWidthUI(CCmdUI *pCmdUI);
    };
  • В файле DrawSDIDoc.cpp карта сообщений после редактирования должна выглядеть так

    Карта сообщений в файле DrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI)
    END_MESSAGE_MAP()
  • Уберите в файле DrawSDIDoc.cpp код всех прежних обработчиков для категорий Color и Width и введите новый код обобщенных обработчиков так

    Новый код обработчиков сообщений в файле DrawSDIDoc.cpp
    
    // Срабатывает при нажатии на любой пункт меню Color
    void CDrawSDIDoc::OnColorCommand(UINT nID)
    {
      m_iColor = nID - ID_COLOR_BLACK;
    }
      
    // Срабатывает для каждого пункта перед раскрытием меню Color
    void CDrawSDIDoc::OnUpdateColorUI(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      bool check = pCmdUI->m_nID == m_iColor + ID_COLOR_BLACK;
      // Какой цвет установлен, такой пункт
      // меню и отобразить с флажком
      pCmdUI->SetCheck(check ? TRUE : FALSE);
    }
      
    // Срабатывает при нажатии на любой пункт меню Width
    void CDrawSDIDoc::OnWidthCommand(UINT nID)
    {
      switch(nID){
        case ID_WIDTH_16: m_iWidth = 16; break;
        case ID_WIDTH_24: m_iWidth = 24; break;
        case ID_WIDTH_32: m_iWidth = 32; break;
        default: m_iWidth = nID - ID_WIDTH_1 + 1;
      }
    }
      
    // Срабатывает для каждого пункта перед раскрытием меню Width
    void CDrawSDIDoc::OnUpdateWidthUI(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      
      bool check;
      int pos = 0;
      if(m_iWidth <= 8)
        pos = m_iWidth - 1;  //0-7
      else if(m_iWidth == 16)  
        pos = m_iWidth - 8;  //8
      else if(m_iWidth == 24)
        pos = m_iWidth - 15;//9
      else if(m_iWidth == 32)
        pos = m_iWidth - 22;//10
      // Какая толщина установлена, такой пункт
      // меню и отобразить с флажком
      check = pCmdUI->m_nID == pos + ID_WIDTH_1;
      pCmdUI->SetCheck(check ? TRUE : FALSE);
    }
  • Постройте приложение и проверьте его работоспособность
  • Оформите только About, но ни в коем случае не оформляйте строку заголовка окна и не русифицируйте ресурс String Table. Может смениться кодировка и придется тексты интерфейсов переколачивать заново вручную
  • Панели инструментов и строка состояния MFC

    В этой части мы наделим наше приложение еще большими возможностями, добавив панель инструментов и строку состояния.

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

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

    Добавление в приложение нового ресурса панели инструментов IDR_TBCOLOR

  • Откройте вкладку Resource View и через контекстное меню на узле Toolbar добавьте новый ресурс Toolbar, нажав кнопку New

    Появится окно графического редактора с заготовкой панели инструментов

    Одновременно в узле Toolbar вкладки Resource View приложения появится новый ресурс с автоматически сгенерированным идентификатором

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

    Чтобы не работать с карандашом, который включен по умолчанию, можно переключить инструмент рисования на кисть, предварительно вызвав через контекстное меню панели инструментов оболочки панель Image Editor

  • Убедитесь, что ресурс новой панели русифицирован, чтобы можно было заполнять в него тексты на русском
  • Выделите по очереди каждую кнопку новой панели и через вкладку оболочки Properties задайте свойства кнопок в соответствии с таблицей

    Свойство Prompt состоит из длинной (отображаемой в строке состояния) и короткой (всплывающей, когда пользователь задерживает курсор над кнопкой на время около 2 секунд) подсказок, разделенных управляющим символом "новая строка".

    ID Prompt
    ID_COLOR_BLACK Цвет рисования черный \n Черный
    ID_COLOR_BLUE Цвет рисования синий \n Синий
    ID_COLOR_GREEN Цвет рисования зеленый \n Зеленый
    ID_COLOR_CYAN Цвет рисования бирюзовый \n Бирюзовый
    ID_COLOR_RED Цвет рисования красный \n Красный
    ID_COLOR_MAGENTA Цвет рисования сиреневый \n Сиреневый
    ID_COLOR_YELLOW Цвет рисования желтый \n Желтый
    ID_COLOR_WHITE Цвет рисования белый \n Белый
  • Убедитесь, и это очень важно, что мы только что присвоили кнопкам панели инструментов IDR_TBCOLOR точно такие же идентификаторы, что и соответствующим пунктам меню категории Color
  • Они и должны совпадать, поскольку предназначены для одного и того же. А это значит, что обработчики команд специально для панели инструментов нам создавать не нужно - для одинаковых идентификаторов будут использованы обработчики команд меню.

    По умолчанию размер кнопок принят 16x15 пикселов. Использование изображений, размеры которых отличаются от принятых по умолчанию, вполне возможно, но об этом необходимо уведомить объект панели инструментов при помощи функции CToolBar::SetSizes().

  • Закройте окно графического редактора, чтобы случайно не испортить подготовленную панель инструментов
  • Любая панель инструментов хранится в виде растрового рисунка формата bmp в каталоге res проекта. Ее всегда можно открыть и отредактировать другим внешним редактором.

  • Выделите во вкладке
  • В нашем случае файл имеет имя toolbar1.bmp. Открытый во внешнем редакторе он имеет вид

    Присоединение панели инструментов IDR_TBCOLOR к рамке приложения

    Пока мы только заготовили ресурс панели инструментов. Теперь подсоединим его к приложению. Поскольку панели инструментов присоединяются к рамке, необходимо начать добавление и изменение кода именно в модуле рамки. Рамкой нашего приложения управляет класс CMainFrame, являющийся производным от базового класса CFrameWnd и расположенный в файлах MainFrm.h и MainFrm.cpp.

    У нас уже есть одна панель инструментов, автоматически сгенерированная мастером при создании заготовки приложения в самом начале выполнения лабораторной работы. В файле MainFrm.cpp можно увидеть, что она создается и прикрепляется к рамке окна приложения в функции OnCreate() - члене класса CMainFrame

    Функция OnCreate() файла MainFrm.cpp
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
      if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
      
      if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }
      
      if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
      {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
      }
      // TODO: Delete these three lines if you don't want the toolbar to be dockable
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      EnableDocking(CBRS_ALIGN_ANY);
      DockControlBar(m_wndToolBar);
      
      return 0;
    }

    В этой функции мы, подобным образом, создадим и прикрепим нашу новую панель. Но вначале для этого добавим в класс CMainFrame новую переменную типа CToolBar, которая и будет содержать новую панель. Эта переменная должна быть защищенной и назовем ее m_wndColorBar.

  • Щелкните правой кнопкой мыши на классе CMainFrame во вкладке Class View, вызовите мастер Add Variable, который заполните так

    Мастер добавит в объявление класса следующий код

    Объявление переменной-панели в классе CMainFrame файла MainFrm.h
    class CMainFrame : public CFrameWnd
    {
    ........................................................
    protected:
      afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
      DECLARE_MESSAGE_MAP()
      // Панель инструментов для цвета
      CToolBar m_wndColorBar;
    };
  • Добавьте к функции OnCreate() класса CMainFrame следующий код

    Добавления в функцию CMainFrame::OnCreate() файла MainFrm.cpp
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
      if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
      
      if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }
      
      // Создать инструментальную панель 
      // для выбора цвета и настроить ее
      if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }
      // Найти кнопку ID_COLOR_BLACK на инструментальной панели
        int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
      if(iTBCtrlID >= 0){
        // Установить все кнопки так, чтобы
        // они действовали как радиокнопки в группе
        for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
          m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
      }
      
      if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
      {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
      }
      
      // TODO: Delete these three lines if you don't want the toolbar to be dockable
      // Что делать: Удалите эти три строки, если инструментальную панель не нужно прикреплять
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
      EnableDocking(CBRS_ALIGN_ANY);
      
      DockControlBar(m_wndToolBar);
      DockControlBar(m_wndColorBar);// Наша!!!
      
      return 0;
    }
  • Постройте приложение и убедитесь, что панель ведет себя как нужно и переключает цвета точно также, как это делается через меню Color
  • Это происходит потому, что идентификаторы ресурсов кнопок и пунктов меню одни и те же.

    Анализ кода, добавленного в функцию CMainFrame::OnCreate() для IDR_TBCOLOR

    Класс панели инструментов CToolBar является лишь одним из нескольких классов, которые создают окна, пригодные для ввода команды и вывода информации. Базовым классом для всей этой группы элементов управления CToolBar, CStatusBar, CControlBar, CReBar, COleResizerBar, известных под общим названием панели управления, является класс CControlBar, производный от класса CWnd. Это очень важно, поскольку означает, что все панели инструментов как наследники класса CWnd, имеют все необходимые API -функции для работы с окнами.

    Класс CToolBar

    Класс CToolBar является наследником класса CControlBar, который в свою очередь является наследником класса CWnd. Соответственно класс CToolBar имеет все необходимые функциональные возможности для успешного управления программным способом панелями инструментов. Рассмотрим некоторые методы, в том числе и унаследованные, класса CToolBar.

    Некоторые методы класса CToolBar
    Метод Пояснения
    CreateEx() Создает панель инструментов
    LoadToolBar() Загружает ресурс панели инструментов
    CommandToIndex() Возражает индекс кнопки панели инструментов
    GetButtonStyle() Возвращает целочисленное значение стиля кнопки по ее индексу
    SetButtonStyle() Устанавливает стиль кнопки по ее индексу
    GetItemID() Возвращает идентификатор ресурса кнопки по ее индексу
    GetItenRect() Возвращает координаты размещения и размер кнопки
    GetButtonText() Возвращает содержимое текстовой подсказки кнопки по ее индексу в формате CString
    SetButtonText() Устанавливает для кнопки с индексом ее текстовую подсказку
    GetBarStyle() Возвращает стиль (переменную типа DWORD ), установленный ранее для панели инструментов
    SetBarStyle() Устанавливает стиль для панели инструментов
    GetCount() Возвращает количество элементов, расположенных на панели инструментов
    IsFloating() Возвращает булеву величину, указывающую, является ли в данный момент панель инструментов плавающей
    IsVisible() Возвращает булеву величину, указывающую, является ли в данный момент панель инструментов видимой
    GetDockingFrame() Возвращает указатель на фреймовое окно, которое является родительским для панели инструментов
    EnableDocking() Посылает уведомление панели инструментов и фреймовому окну о том, что пользователям разрешено самостоятельно перемещать панель инструментов и располагать ее в других частях фреймового окна.
    ShowWindow() Как член класса CWnd управляет видимостью панели инструментов как окном. При этом фон исчезнувшей панели инструментов не удается скрыть
    ShowControlBar() Управляет видимостью панели инструментов более умело с сокрытием и фона. Применяется совместно с функцией RecalcLayout()
    RecalcLayout() Заново создает макет в пределах рамки окна, т. е. перерасполагает заново все панели инструментов, строки состояния и все остальное в пределах рамки окна. Кроме того, данная функция передвигает созданную по умолчанию панель инструментов вверх и вниз при переключении состояния панели скрыть/отобразить
    GetButtonInfo() Возвращает информацию о кнопке
    SetButtonInfo() Устанавливает идентификатор, стиль и индекс пиктограммы для кнопки панели инструментов
    GetToolBarCtrl() Возвращает ссылку на объект класса CToolBarCtrl
    LoadBitmap() Загружает пиктограмму для панели инструментов
    SetBitmap() Устанавливает писктограмму для новой панели инструментов
    SetButtons() Устанавливает идентификаторы для кнопок панели инструментов
    SetHeight() Устанавливает высоту панели инструментов
    SetSizes() Устанавливает размеры кнопок панели инструментов

    Код создания панели инструментов IDR_TBCOLOR

    Добавленный нами ранее в функцию CMainFrame::OnCreate() фрагмент кода

    // Создать инструментальную панель 
      // для выбора цвета и настроить ее
      if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }

    содержит две функции-члена класса CToolBar

  • CreateEx() - создает панель инструментов с нужными свойствами в окне-рамке класса CMainFrame для экземпляра CToolBar m_wndColorBar
  • Параметры функции CreateEx() Параметр Пояснения первый параметр Первый параметр функции является указателем на родительское окно-рамку, к которому прикрепляется создаваемая панель инструментов. Он является обязательным, остальные же параметры могут быть опущены, так как они имеют значения по умолчанию второй параметр (необязательный) Второй параметр - это стиль расположения кнопок и других элементов управления на панели инструментов. третий параметр (необязательный) Третий параметр - это непосредственно стили самой панели инструментов. Этот параметр является комбинацией стилей окна и стилей элементов управления панели инструментов. Обычно используются только два или три стиля окна, а остальные являются стилями панели инструментов. Некоторые стили самой панели инструментов приведены ниже в таблице. четвертый параметр (необязательный) Четвертый параметр (отсутствует в нашем коде) - размер границы панели инструментов. Этот параметр задается как экземпляр класса стандартного прямоугольника CRect, задающего желаемую ширину и высоту панели инструментов. По умолчанию для всех измерений прямоугольника задается нуль, а это означает, что фактические размеры панели инструментов жестко не устанавливаются пятый параметр (необязательный) Пятый параметр (отсутствует в нашем коде) - идентификатор ( ID ) дочернего окна панели инструментов. По умолчанию он равен константе AFX_IDW_TOOLBAR, но при необходимости можно указать любой необходимый идентификатор
    Стили размещения кнопок относительно окна (второй параметр)
    Стиль Описание
    TBSTYLE_ALTDRAG Позволяет перемещать панель инструментов (перетаскивать ее при нажатии кнопки Alt )
    TBSTYLE_CHECK Задает режим поведения кнопок панели как флажков. Получаются кнопки с фиксацией, которые залипает при щелчке на них. Для возврата в исходное состояние необходимо щелкнуть повторно
    TBSTYLE_GROUP Кнопки панели рассматриваются как группа
    TBSTYLE_CUSTOMERASE Генерирует сообщение NM_CUSTOMDROW при стирании фона панели инструментов и кнопок, позволяя выбирать, когда и каким образом управлять процессом стирания фона
    TBSTYLE_FLAT Создает плоскую панель инструментов. Текст кнопки располагается под растровым изображением
    TBSTYLE_LIST Текст кнопки располагается справа от растрового изображения
    TBSTYLE_REGISTERDROP Используется при перетаскивании и опускании объектов на кнопки панели инструментов
    TBSTYLE_TOOLTIPS Создает всплывающую подсказку (элемент управления ToolTip ), которая может использоваться для отображения текста описания кнопок
    TBSTYLE_TRANSPARENT Создает прозрачную панель инструментов
    TBSTYLE_WRAPABLE Создает панель инструментов, которая может иметь несколько рядов кнопок
    Стили самой панели инструментов как окна (третий параметр)
    Стиль Описание
    WS_CHILD Панель инструментов создается как дочернее окно
    WS_VISIBLE Создается видимая сразу панель инструментов
    CBRS_ALIGN_TOP Позволяет прикреплять панель инструментов к верхней стороне рабочей области обрамляющего окна
    CBRS_ALIGN_BOTTOM Позволяет прикреплять панель инструментов к нижней стороне рабочей области обрамляющего окна
    CBRS_ALIGN_LEFT Позволяет прикреплять панель инструментов к левой стороне рабочей области обрамляющего окна
    CBRS_ALIGN_RIGHT Позволяет прикреплять панель инструментов к правой стороне рабочей области обрамляющего окна
    CBRS_ALIGN_ANY Позволяет прикреплять панель инструментов к любой стороне рабочей области обрамляющего окна
    CBRS_BORDER_TOP Если панель инструментов не прикреплена, помещает границу на верхний край панели инструментов
    CBRS_BORDER_BOTTOM Если панель инструментов не прикреплена, помещает границу на нижний край панели инструментов
    CBRS_BORDER_LEFT Если панель инструментов не прикреплена, помещает границу на левый край панели инструментов
    CBRS_BORDER_RIGHT Если панель инструментов не прикреплена, помещает границу на правый край панели инструментов
    CBRS_FLOAT_MULTI Позволяет помещать в одно плавающее мини-окно несколько панелей инструментов
    CBRS_TOOLTIPS Отображает всплывающие подсказки ( ToolTips ) для кнопок панелей инструментов
    CBRS_FLYBY Одновременно обновляет текст сообщения строки состояния и текст всплывающей подсказки ToolTip
    CBRS_GRIPPER Рисует область захвата на панели инструментов при ее перемещении
    CBRS_SIZE_DYNAMIC Позволяет изменять размеры окна панели инструментов
  • Код установки стилей кнопки

    Рассмотрим еще один фрагмент кода, добавленного нами ранее в функцию CMainFrame::OnCreate() файла MainFrm.cpp

    // Найти кнопку ID_COLOR_BLACK на инструментальной панели
        int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
       if(iTBCtrlID >= 0)
        {
        // Установить все кнопки так, чтобы
        // они действовали как радиокнопки в группе
        for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
          m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
       }

    Вначале применяется функция-член CommandToIndex(), которая возвращает индекс элемента управления, под которым он числится в панели инструментов как экземпляре класса CToolBar. Учитывая, что кнопка с таким идентификатором имеет наименьшее значение ID при графической разработки панели инструментов, в экземпляре класса она имеет наименьшее значение индекса (значение 0).

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

    Список возможных индивидуальных стилей кнопок панели инструментов
    Стиль Описание
    TBBS_AUTOSIZE Вычисляет ширину кнопки в зависимости от текста кнопки
    TBBS_BUTTON Создает стандартную кнопку (без фиксации)
    TBBS_CHECKBOX Создает кнопку, которая действует подобно флажку. Такая кнопка переключается из состояния "выбрана" в состояние "отменить выбор" и наоборот. Иными словами, это кнопка с фиксацией, которая залипает при щелчке на ней. Для возврата в исходное состояние необходимо щелкнуть на ней еще раз
    TBBS_CHECKGROUP Создает кнопку, которая действует подобно радиокнопкам (селективным кнопкам) - остается выбранной до выбора другой кнопки в группе. Фактически это комбинация стилей TBSTYLE_CHECK и TBSTYLE_GROUP
    TBBS_DROPDOWN Создается кнопку, представляющую собой раскрывающийся список
    TBBS_GROUP Создает кнопку, которая остается выбранной, пока не будет выбрана другая кнопка из группы
    TBBS_NOPREFIX Не связывает префикс акселератора (командной клавиши) с текстом кнопки
    TBBS_SEPARATOR Создает разделитель (небольшой интервал) между кнопками с обеих сторон

    Код закрепления панели инструментов IDR_TBCOLOR

    Рассмотрим следующий фрагмент кода, добавленного нами ранее в функцию CMainFrame::OnCreate() файла MainFrm.cpp

    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
      EnableDocking(CBRS_ALIGN_ANY);
      
      DockControlBar(m_wndToolBar);
      DockControlBar(m_wndColorBar);// Наша!!!

    По умолчанию положение панели инструментов можно изменить только программно. Но можно разрешить пользователям самостоятельно перемещать панели инструментов и располагать их в других частях фреймового окна. О том, что такая возможность разрешена, необходимо уведомить как саму панель инструментов, так и фреймовое окно. Для этого и вызываются функции CToolBar::EnableDocking() и CFrameWnd::EnableDocking() (разрешить прикрепление).

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

    Последняя функция DockControlBar(m_wndColorBar) кода - это функция фреймового окна, дающая команду изначально прикрепить панель инструментов, адрес на которую ей передается, к фрейму перед первым появлением окна на экране.

    Параметры, передаваемые функциями EnableDocking()
    Стиль Описание
    CBRS_ALIGN_TOP Прикрепляет панель инструментов к верхней стороне рабочей области обрамляющего окна
    CBRS_ALIGN_BOTTOM Прикрепляет панель инструментов к нижней стороне рабочей области обрамляющего окна
    CBRS_ALIGN_LEFT Прикрепляет панель инструментов к левой стороне рабочей области обрамляющего окна
    CBRS_ALIGN_RIGHT Прикрепляет панель инструментов к правой стороне рабочей области обрамляющего окна
    CBRS_ALIGN_ANY Позволяет прикреплять панель инструментов к любой стороне рабочей области обрамляющего окна
    CBRS_FLOAT_MULTI Позволяет помещать в одно плавающее мини-окно несколько панелей инструментов
    0 Не позволяет прикреплять панель инструментов к рамке

    Управление видимостью панели инструментов IDR_TBCOLOR

    Расширим меню View нашего приложения и добавим в него пункт включения/выключения панели инструментов IDR_TBCOLOR. Затем нужно создать код отображения и сокрытия панели инструментов. Здесь можно применить два варианта, первый из которых использует методы базового класса CWnd панели инструментов, второй - методы класса CToolBar.

    Вариант 1

    Прежде, чем отображать или скрывать панель инструментов, нужно выяснить ее текущее состояние (отображена/скрыта). Информацию о видимости панели инструментов содержит флаг WS_VISIBLE стиля ее окна. Для этого с помощью функции CWnd::GetStyle() необходимо получить сначала значение стиля окна панели инструментов, а затем сравнить его по маске со значением флага WS_VISIBLE.

    // Выяснить текущее состояние видимости
    bool bVisible = (m_wndColorBar.GetStyle()  WS_VISIBLE);

    На основании информации о том, скрыта панель инструментов или отображена, можно применить соответствующее действие

    // Показать или скрыть панель инструментов
    int nShow = (bVisible) ? SW_HIDE : SW_SHOWNORMAL;
    m_wndColorBar.ShowWindow(nShow);

    После этого обо всех программных изменениях панели инструментов необходимо уведомить фреймовое окно, которое повторно вычислит позиции всех панелей и перестроит их. Для этого достаточно вызова функции CFrameWnd::RecalcLayout()

    // Переконфигурировать элементы всех панелей инструментов,
    // строки состояния и всего остального в пределах фрейма
    RecalcLayout();

    Вариант 2

    Прежде, чем отображать или скрывать панель инструментов, нужно выяснить ее текущее состояние (отображена/скрыта). Информацию о видимости панели инструментов можно получить с помощью функции CControlBar::IsVisible(). Затем для отображения или скрытия панели инструментов можно применить функцию фреймового окна CFrameWnd::ShowControlBar(). Прототип этой функции

    void ShowControlBar(CControlBar* pBar, BOOL bShow, BOOL bDelay);
  • pBar - адрес переменной управляемой панели инструментов
  • bShow - булева переменная, которая определяет, показывать или скрывать панель инструментов ( TRUE - показать, FALSE - скрыть)
  • bDelay - булева переменная, которая определяет, показать/скрыть панель инструментов с задержкой ( TRUE ) или немедленно ( FALSE )
  • После этого обо всех программных изменениях панели инструментов необходимо уведомить фреймовое окно, которое повторно вычислит позиции всех панелей и перестроит их. Для этого достаточно вызова функции CFrameWnd::RecalcLayout()

    // Переконфигурировать элементы всех панелей инструментов,
    // строки состояния и всего остального в пределах фрейма
    RecalcLayout();

    Реализация управления видимостью

    Для добавления возможности управления видимостью нашей панели инструментов выполните следующие пошаговые инструкции:

  • Откройте ресурс меню на редактирование, русифицируйте его и добавьте в категорию Параметры для категории View ресурса меню Свойство Значение ID (идентификатор) ID_VIEW_COLORBAR Caption (надпись) Color Bar Prompt (подсказка) Показать или скрыть панель выбора цвета \n Показать/ Скрыть
  • Во вкладке Class View выделите класс CMainFrame
  • Во вкладке Properties установите режим Events
  • Найдите узел событий ID_VIEW_COLORBAR, раскройте его и добавьте обработчики для событий COMMAND и UPDATE_COMMAND_UI
  • Заполните обработчики так
    Обработчики меню View пункта Color Bar
    void CMainFrame::OnViewColorbar()
    {
    /***Студент Жук! Чтобы раскомментировать блок, добавьте спереди один слэш***
      // Вариант 1
      // Выяснить текущее состояние видимости
      bool bVisible = (m_wndColorBar.GetStyle()  WS_VISIBLE);
      // Показать или скрыть панель инструментов
      int nShow = (bVisible) ? SW_HIDE : SW_SHOWNORMAL;
      m_wndColorBar.ShowWindow(nShow);
    //*****************************************************/
      
    //***Студент Жук! Чтобы закомментировать блок, уберите спереди один слэш***
      // Вариант 2
      // Показать или скрыть панель инструментов
      ShowControlBar(m_wndColorBar
                      ,!m_wndColorBar.IsVisible()
                      ,FALSE);
    //*****************************************************/
      // Переконфигурировать элементы всех панелей инструментов,
      // строки состояния и всего остального в пределах фрейма
      RecalcLayout();// Можно и закомментировать, я разницу не нашел!
    }
      
    void CMainFrame::OnUpdateViewColorbar(CCmdUI *pCmdUI)
    {
      // В зависимости от видимости панели отобразить
      // пункт меню с отметкой флажком или без флажка
      pCmdUI->SetCheck(m_wndColorBar.IsVisible());
    }
  • Проследите за тем, чтобы обработчики были членами класса CMainFrame и попали в файл реализации MainFrm.cpp.
  • В первом обработчике реализованы два варианта управления отображением панели, рассмотренные выше. Один из вариантов закомментирован. Проверьте оба варианта, попеременно раскомментируя один и закомментируя другой. Обратите внимание на способ быстрого комментирования нужного блока, авось пригодится где-нибудь в "Силиконовой долине", где время - деньги.

    Добавление комбинированного списка к панели инстументов, отвечающего за установку толщины линии Width

    Добавим к панели инструментов комбинированный список, отвечающий за установку толщины пера рисования.

    Редактирование файла ресурсов проекта вручную

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

  • Перейдите на вкладку
  • Из контекстного меню этого ресурса откройте окно
  • Выберите текстовый редактор Source Code (Text) Editor, как показано на рисунке, и нажмите кнопку Open. Если появится окно сообщения с вопросом, хотим ли мы закрыть текущий редактор ресурсов (вкладку Resource View ),

    щелкните по кнопке Да ( Yes )

    А теперь будьте очень осторожными и случайно не испортите файл ресурсов, иначе это приведет к выводу из строя всего проекта приложения!!! (лучше вначале создать его копию)

  • Найдите раздел панели инструментов IDR_TBCOLOR и добавьте две строки SEPARATOR (разделитель) как показано ниже
    Прямое редактирование файла ресурсов DrawSDI.rc
    ....................................................  
    ///////////////////////////////////////////////////////////
    //
    // Toolbar
    //
      
    IDR_TBCOLOR TOOLBAR  16, 15
    BEGIN
        BUTTON      ID_COLOR_BLACK
        BUTTON      ID_COLOR_BLUE
        BUTTON      ID_COLOR_GREEN
        BUTTON      ID_COLOR_CYAN
        BUTTON      ID_COLOR_RED
        BUTTON      ID_COLOR_MAGENTA
        BUTTON      ID_COLOR_YELLOW
        BUTTON      ID_COLOR_WHITE
        SEPARATOR
        SEPARATOR
    END
    ...................................................
  • Закройте окно редактора с сохранением изменений
  • Теперь, если открыть вкладку Resource View, то можно увидеть предупреждение, что файл ресурсов редактировался напрямую через прочие редакторы

  • Сверните узел дерева с изображением папки и разверните вновь - предупреждение исчезнет
  • Прямое редактирование файла ресурсов было выполнено по трем причинам:

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

    Теперь нужно добавить текстовые строки как ресурс, которые будут загружены в комбинированный список при его создании. Следует помнить, что каждая строка комбинированного списка является отдельным ресурсом и должна иметь уникальный идентификатор с уникальным значением. Текстовые строки добавим через таблицу строк String Table ресурса приложения.

  • Через вкладку Resource View откройте узел String Table
  • Если вы не русифицировали этот ресурс и одновременно для кнопок панели инструментов вводили русские сообщения, то в файле ресурсов оболочка создала два раздела, относящиеся к таблице строк. Один из разделов с последними изменениями имеет вид

    Дополнительный раздел таблицы строк в DrawSDI.rc
    /////////////////////////////////////////////////////////////////////////////
    //
    // String Table
    //
      
    STRINGTABLE 
    BEGIN
        ID_COLOR_BLACK          "Цвет рисования черный\nЧерный"
        ID_COLOR_BLUE           "Цвет рисования синий\nСиний"
        ID_COLOR_GREEN          "Цвет рисования зеленый\nЗеленый"
        ID_COLOR_CYAN           "Цвет рисования бирюзовый\nБирюзовый"
        ID_COLOR_RED            "Цвет рисования красный\nКрасный"
        ID_COLOR_MAGENTA        "Цвет рисования сиреневый\nСиреневый"
        ID_COLOR_YELLOW         "Цвет рисования желтый\nЖелтый"
        ID_COLOR_WHITE          "Цвет рисования белый\nБелый"
    END
      
    STRINGTABLE 
    BEGIN
        ID_VIEW_COLORBAR        "Показать или скрыть панель выбора цвета\nПоказать/Скрыть"
    END
  • Двойным щелчком мыши на русифицированном ресурсе вызовите редактор таблицы строк. Добавляете новые строки в соответствии с таблицей, приведенной ниже. При этом следите за тем, чтобы значения поля Параметры для категории View ресурса меню ID Caption IDS_WIDTH_1 1 IDS_WIDTH_2 2 IDS_WIDTH_3 3 IDS_WIDTH_4 4 IDS_WIDTH_5 5 IDS_WIDTH_6 6 IDS_WIDTH_7 7 IDS_WIDTH_8 8 IDS_WIDTH_16 Medium IDS_WIDTH_24 Thick IDS_WIDTH_32 Very Thick
  • Должно получиться нечто подобное

    Теперь в файле ресурсов соответствующий раздел выглядит так

    Раздел таблицы строк в DrawSDI.rc после добавления
    //////////////////////////////////////////////////////////////
    //
    // String Table
    //
      
    STRINGTABLE 
    BEGIN
        ID_COLOR_BLACK          "Цвет рисования черный\nЧерный"
        ID_COLOR_BLUE           "Цвет рисования синий\nСиний"
        ID_COLOR_GREEN          "Цвет рисования зеленый\nЗеленый"
        ID_COLOR_CYAN           "Цвет рисования бирюзовый\nБирюзовый"
        ID_COLOR_RED            "Цвет рисования красный\nКрасный"
        ID_COLOR_MAGENTA        "Цвет рисования сиреневый\nСиреневый"
        ID_COLOR_YELLOW         "Цвет рисования желтый\nЖелтый"
        ID_COLOR_WHITE          "Цвет рисования белый\nБелый"
    END
      
    STRINGTABLE 
    BEGIN
        ID_VIEW_COLORBAR        "Показать или скрыть панель выбора цвета\nПоказать/Скрыть"
        IDS_WIDTH_1             "1"
        IDS_WIDTH_2             "2"
        IDS_WIDTH_3             "3"
        IDS_WIDTH_4             "4"
        IDS_WIDTH_5             "5"
        IDS_WIDTH_6             "6"
        IDS_WIDTH_7             "7"
        IDS_WIDTH_8             "8"
        IDS_WIDTH_16            "Medium"
        IDS_WIDTH_24            "Thick"
        IDS_WIDTH_32            "Very Thick"
    END

    Создание комбинированного списка на панели инструментов

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

  • Выберите вкладку
  • Нажмите кнопку
  • Для программной поддержки комбинированного списка создадим переменную m_ctrlWidth типа CComboBox как член класса CMainFrame. Для этого:

  • Во вкладке
  • Убедитесь в том, что мастер добавил переменную именно в класс CMainFrame файла MainFrm.h
  • Далее нам нужно будет выполнить такие действия:

  • Установить ширину и идентификатор ID заглушки-заполнителя комбинированного списка на панели инструментов равными соответственно ширине и идентификатору ID самого комбинированного списка
  • Определить размер и позицию комбинированного списка, получив для этого параметры заглушки-заполнителя на панели инструментов
  • Создать комбинированный список как программный объект, указав при этом панель инструментов в качестве его родительского окна
  • Загрузить текстовые строки в комбинированный список и установить индекс видимой начальной строки
  • Чтобы упорядочить этот процесс и не захламлять функцию OnCreate() класса фреймового окна CMainFrame не свойственным ей кодом, создадим отдельную функцию-член фрейма, а в функции OnCreate() разместим вызов этой вспомогательной упаковки кода. Вспомогательную функцию назовем CreateColorBar().

  • Выделите во вкладке Class View класс CMainFrame
  • Вызовите мастер
  • Перенесите (это значит полностью уберите со старого места!!!) соответствующий код из функции OnCreate() файла MainFrm.cpp в эту новую функцию (файл тот же!), а также добавьте новый код для создания комбинированного списка
  • Ниже приведен листинг функции, где красным выделен перенесенный код, а синим - добавленный новый код

    Вспомогательная функция в файле MainFrm.cpp
    // Вспомогательная упаковка кода создания панели инструментов
    bool CMainFrame::CreateColorBar(void)
    {
      // Создать инструментальную панель 
      // для выбора цвета и настроить ее
      if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
      {
        TRACE0("Failed to create toolbar\n");
        return FALSE;      // fail to create
      }
      // Найти кнопку ID_COLOR_BLACK на инструментальной панели
        int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
      if(iTBCtrlID >= 0){
        // Установить все кнопки так, чтобы
        // они действовали как радиокнопки в группе
        for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
          m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
      }
      
      // Добавить комбинированный список
      int nWidth = 100;
      int nHeight = 125;
      
      // Выбрать конфигурацию заглушки-заполнителя комбинированного списка
      m_wndColorBar.SetButtonInfo(9, IDC_CBWIDTH, TBBS_SEPARATOR, nWidth);
      
      // Получить высоту панели инструментов выбора цвета
      CRect rect;
      m_wndColorBar.GetItemRect(9, rect);
      rect.bottom = rect.top + nHeight;
      
      // Создать комбинированный список
      m_ctrlWidth.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST
                          ,rect, m_wndColorBar, IDC_CBWIDTH);
      
      // Заполнить комбинированный список текстами из ресурса String Table
      CString str;
      UINT id = IDS_WIDTH_1; // Важно, что значения идентификаторов
                               // расположены в порядке возрастания с шагом 1,
                             // поэтому можно организовать цикл
      for(; id - IDS_WIDTH_1 < 11; id++) // Перебираем 11 элементов
        if(str.LoadString(id))
          m_ctrlWidth.AddString((LPCTSTR)str);
      
      // Устанавливаем первый элемент видимым
      m_ctrlWidth.SetCurSel(0);
      
      return TRUE;
    }

    В приведенном коде используется функция

    // Выбрать конфигурацию заглушки-заполнителя комбинированного списка
      m_wndColorBar.SetButtonInfo(9, IDC_CBWIDTH, TBBS_SEPARATOR, nWidth);

    Первый параметр (9) - означает индекс размещаемого в панели элемента ( начинается с нуля ). Этим модифицируемым элементом-заглушкой будет ранее введенный нами последний сепаратор в файле ресурсов. Фрагмент файла ресурсов таков

    Идентификаторы элементов строки состояния в файле ресурсов
    /////////////////////////////////////////////////////////////////////////////
    //
    // Toolbar
    //
    
    IDR_TBCOLOR TOOLBAR  16, 15
    BEGIN
        BUTTON      ID_COLOR_BLACK
        BUTTON      ID_COLOR_BLUE
        BUTTON      ID_COLOR_GREEN
        BUTTON      ID_COLOR_CYAN
        BUTTON      ID_COLOR_RED
        BUTTON      ID_COLOR_MAGENTA
        BUTTON      ID_COLOR_YELLOW
        BUTTON      ID_COLOR_WHITE
        SEPARATOR
        SEPARATOR  // заглушка-заполнитель как элемент с индексом 9
    END

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

    Третий параметр - стандартная константа TBBS_SEPARATOR библиотеки MFC, представляющая разделитель панели инструментов.

    Четвертый аргумент - ширина заглушки-заполнителя для резервируемого места под комбинированный список.

  • Вновь дополните функцию CMainFrame::OnCreate() кодом вызова только что созданной упаковки CMainFrame::CreateColorBar(), после чего функция должна выглядеть так
    Вызов вспомогательной функции из OnCreate()
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
      if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
      
      if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }
      
      // Добавить инструментальную панель выбора цвета
      // вместе с комбинированным списком выбора толщины пера
      if(!CreateColorBar()){
        TRACE0("Failed to create color toolbar\n");
        return -1;
      }
      
      if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
      {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
      }
      
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
      EnableDocking(CBRS_ALIGN_ANY);
      
      DockControlBar(m_wndToolBar);
      DockControlBar(m_wndColorBar);// Наша!!!
      
      return 0;
    }
  • Постройте приложение и убедитесь, что список раскрывается, но ничего пока не делает
  • Мы создали комбинированный список как немодальное окно с помощью функции Create, которая выглядит так

    Функция Create в файле MainFrm.cpp
    // Вспомогательная упаковка кода создания панели инструментов
    bool CMainFrame::CreateColorBar(void)
    {
    ...............................................................
      // Создать комбинированный список
      m_ctrlWidth.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST
                          ,rect, m_wndColorBar, IDC_CBWIDTH);
    .......................................................................
      return TRUE;
    }

    Первым аргументом этой функции является сочетание стилей комбинированного списка, определяющих его внешние свойства и поведение. Часть флажков относятся к оконным стилям WS_ (window style) , поскольку комбинированный список наследует класс CWnd, а часть флажков специфицирует стиль только списка ( CBS_ - ComboBox Style ). Перечислим значения флажков стилей списка

    Стили комбинированного списка
    Стиль Описание
    WS_CHILD Указывает, что данное окно является дочерним (обязательный стиль)
    WS_VISIBLE Делает комбинированный список изначально видимым
    WS_DISABLED Отключает комбинированный список
    WS_VSCROLL Добавляет вертикальную прокрутку к раскрывающемуся списку
    WS_HSCROLL Добавляет горизонтальную прокрутку к раскрывающемуся списку
    WS_GROUP Группирует элементы управления
    WS_TABSTOP Включает комбинированный список в последовательность перехода по клавише Tab
    CBS_AUTOHSCROLL Обеспечивает прокрутку текста в поле редактирования при его заполнении
    CBS_DROPDOWN Подобен стилю CBS_SIMPLE, но не отображает список до тех пор, пока пользователь не выберет пиктограмму, следующую за полем редактирования
    CBS_DROPDOWNLIST Подобен стилю CBS_DROPDOWN. Однако заменяет поле редактирования статическим текстом, который отображает текущее выбранное значение элемента списка
    CBS_HASSTRINGS Указывает, что за перерисовку комбинированного списка отвечает владелец. Элементы в окне списка состоят из строк
    CBS_OEMCONVERT Текст, выводимый для отображения на экране в поле редактирования, конвертируется из набора символов ASCII в набор OEM, а затем, при передаче данных из комбинированного списка в программу - опять из набора OEM в набор символов ASCII. Набор отображаемых символов ASCII используется в MS-DOS, а OEM - в Windows. Символы с кодами 32-127 обычно совпадают в обеих системах. Другие же символы, используемые национальными кодировками, требуют конвертации. Для правильного отображения текста на экране в консольном режиме для этого используется функция CharToOem(char* strASCII, char* strOEM)
    CBS_OWNERDRAWFIXED Указывает, что владелец списка отвечает за перерисовку содержимого. Содержимое списка имеет фиксированную высоту
    CBS_OWNERDRAWVARIABLE Указывает, что владелец списка отвечает за перерисовку содержимого. Высота содержимого списка может изменяться
    CBS_SIMPLE Все время отображает список на экране
    CBS_SORT Автоматически сортирует строки в списке
    CBS_DISABLENOSCROLL Если список не содержит достаточно элементов для прокрутки, все равно отображается полоса прокрутки, хоть и в отключенном ( Disabled ) виде
    CBS_NOINTEGRALHEIGHT Указывает, что комбинированный список имеет точно указанный размер

    Обработка событий комбинированного списка, расположенного на панели инструментов

    При нажатии на кнопки панели инструментов нам не нужно передавать из класса CMainFrame, которому принадлежит панель инструментов, значение выбранного цвета пера в закрытую переменную m_iColor класса CDrawSDIDoc. Это делается через обработчики событий меню, которые перехватываются классом CDrawSDIDoc, поскольку кнопки панели и пункты меню Color имеют одинаковые идентификаторы.

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

  • Выделите класс CDrawSDIDoc во вкладке Class View и через контекстное меню вызовите мастер Add Function, который заполните так

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

  • Тело функции заполните так
    Функция доступа SetWidth() класса CDrawSDIDoc
    // Установить толщину пера
    void CDrawSDIDoc::SetWidth(int nWidth)
    {
      m_iWidth = nWidth;
    }
  • Теперь, имея указатель на текущий документ, мы сможем с помощью этой функции передать в документ значение толщины пера, установленное в комбинированном списке панели инструментов.

    Но как получить значение от комбинированного списка. Можно перхватывать события, которые генерирует комбинированный список при выборе пользователем нового элемента списка. Это событие имеет имя ON_CBN_SELCHANGE. Поскольку это сообщение генерируется в классе CMainFrame, перехватим его здесь. Для этого:

  • Введите вручную регистрацию события ON_CBN_SELCHANGE в карту сообщений класса, чтобы она выглядела так
    Карта сообщений класса CMainFrame в файле MainFrm.cpp
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
      ON_WM_CREATE()
      ON_COMMAND(ID_VIEW_COLORBAR, OnViewColorbar)
      ON_UPDATE_COMMAND_UI(ID_VIEW_COLORBAR, OnUpdateViewColorbar)
      ON_CBN_SELCHANGE(IDC_CBWIDTH, OnSelChangeWidth)
    END_MESSAGE_MAP()
  • Через мастер добавьте в класс
  • Заполните, обработчик кодом, как показано ниже
    Обработчик OnSelChangeWidth() в файле MainFrm.cpp
    // Функция-обработчик выбора элемента комбинированного списка
    void CMainFrame::OnSelChangeWidth(void)
    {
      int iIndex = m_ctrlWidth.GetCurSel();
      if(iIndex == CB_ERR)// Если получили индекс
        return;
      
      int iWidth = iIndex + 1;
      switch(iIndex + 1){ // Скорректировать толщину
        case  9: iWidth = 16; break;
        case 10: iWidth = 24; break;
        case 11: iWidth = 32;
      }
      
      // Получить активный документ
      CDrawSDIDoc* pDoc = (CDrawSDIDoc*)GetActiveDocument();
      if(pDoc) // Если получили указатель
        pDoc->SetWidth(iWidth); // Передать толщину в документ
    }
  • Поскольку класс документа CDrawSDIDoc в файле MainFrm.cpp не известен, включите в начало этого файла инструкцию #include. Следует иметь ввиду, что она должна располагаться в конце других инструкций #include, иначе могут появиться ошибки компиляции, поскольку предыдущие инструкции тоже могут содержать подобный код
    Инструкция включения файла DrawSDIDoc.h в файл MainFrm.cpp
    .........................................
    .........................................
    #include "DrawSDIDoc.h"
    // CMainFrame
    
    IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
    
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
      ON_WM_CREATE()
      ON_COMMAND(ID_VIEW_COLORBAR, OnViewColorbar)
      ON_UPDATE_COMMAND_UI(ID_VIEW_COLORBAR, OnUpdateViewColorbar)
      ON_CBN_SELCHANGE(IDC_CBWIDTH, OnSelChangeWidth)
    END_MESSAGE_MAP()
    .........................................
  • Постройте приложение и убедитесь, что список управляет толщиной линии, но при изменении толщины через меню значение списка синхронно не изменяется
  • Исправим это...

    Обновление комбинированного списка при вводе толщины пера через меню

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

    Функция-обработчик в файле DrawSDIDoc.cpp
    // Срабатывает при нажатии на любой пункт меню Width
    void CDrawSDIDoc::OnWidthCommand(UINT nID)
    {
      switch(nID){
        case ID_WIDTH_16: m_iWidth = 16; break;
        case ID_WIDTH_24: m_iWidth = 24; break;
        case ID_WIDTH_32: m_iWidth = 32; break;
        default: m_iWidth = nID - ID_WIDTH_1 + 1;
      }
    }

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

    Здесь необходимы следующие шаги:

  • Через обработчик меню вызвать функцию обновления комбинированного списка
  • В функции обновления, как члене документа, вначале получить указатель на активное окно представления View, поскольку сам документ не является оконным объектом и не происходит от базового класса CWnd
  • Затем через указатель окна представления получить указатель на обрамляющее это представление фреймовое окно-рамку, которому и принадлежит панель инструментов с комбинированным списком
  • И наконец, через полученный указатель на фреймовое окно обратиться к комбинированному списку и установить его в соответствие с выбранной через меню документа толщиной пера
  • Подключить к файлу документа заголовочный файл фреймового окна, поскольку будем добывать в документе указатель на фреймовое окно, а его порождающий класс CMainFrame в документе не виден
  • С учетом вышеизложенного выполните следующее:

  • Введите в документ функцию обновления комбинированного списка, назовите ее CDrawSDIDoc::UpdateComboBox(). Для этого для класса CDrawSDIDoc заполните мастер Add Function так

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

  • Мастер сгенерирует заготовку, которую заполните следующим кодом
    Функция UpdateComboBox() в файле DrawSDIDoc.cpp
    // Функция обновления комбинированного списка
    void CDrawSDIDoc::UpdateComboBox(int iIndex)
    {
      // Получить позицию активного вида в Z-последовательности видов
      POSITION pos = GetFirstViewPosition();
      if(!pos) // Позиция не получена, она нулевая
        return;
    
      // Получить указатель на верхнее представление (активный вид)
      CView* pView = GetNextView(pos);
      if(!pView) // Указатель не получен, он нулевой
        return;
    
      // Получить указатель на фреймовое окно с помощью вида
      CMainFrame* pFrame = (CMainFrame*)pView->GetTopLevelFrame();
      if(!pFrame) // Указатель не получен, он нулевой
        return;
    
      // Обновить комбинированный список на панели инструментов
      pFrame->m_ctrlWidth.SetCurSel(iIndex);
    }
  • Добавьте в начала файла DrawSDIDoc.cpp инструкцию препроцессору для подключения класса фреймового окна, объявление которого находится в файле приложения MainFrm.h. Имейте ввиду, что она должна располагаться в конце других инструкций #include, иначе могут появиться ошибки компиляции, поскольку предыдущие инструкции тоже могут содержать подобный код
    Инструкция препроцессору в файле DrawSDIDoc.cpp
    ...............................................................
    #include "MainFrm.h"
    // CDrawSDIDoc
      
    IMPLEMENT_DYNCREATE(CDrawSDIDoc, CDocument)
      
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI)
    END_MESSAGE_MAP()
    ...............................................................
  • Поместите вызов функции UpdateComboBox() в обработчике выбора пункта меню Width
    Вызов функции в обработчике меню файла DrawSDIDoc.cpp
    // Срабатывает при нажатии на любой пункт меню Width
    void CDrawSDIDoc::OnWidthCommand(UINT nID)
    {
      switch(nID){
        case ID_WIDTH_16: m_iWidth = 16; break;
        case ID_WIDTH_24: m_iWidth = 24; break;
        case ID_WIDTH_32: m_iWidth = 32; break;
        default: m_iWidth = nID - ID_WIDTH_1 + 1;
      }
      UpdateComboBox(nID - ID_WIDTH_1);
    }
  • Постройте приложение и убедитесь, что все работает правильно!!!..., кроме одного - при создании нового документа комбинированный список не устанавливается в начало
  • Чтобы это исправить, разместите вызов UpdateComboBox(0) еще и в функции OnNewDocument() класса CDrawSDIDoc (сделайте это самостоятельно!!!)
  • Есть еще одна неувязочка, которая может не понравиться ПОЛЬЗОВАТЕЛЮ. После прикрепления к левому или правому боку фреймового окна панель скрывает комбинированный список. На данном этапе исправим это, установив свойство панели прикрепляться только к верхнему краю, что не очень ограничит права пользователя. Для этого:

  • Измените в функции CMainFrame::OnCreate() соответствующий фрагмент кода
    Изменение свойства прикрепления панели в файле CMainFrm.cpp
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    ................................................................
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      EnableDocking(CBRS_ALIGN_ANY);
      m_wndColorBar.EnableDocking(CBRS_ALIGN_TOP);// Наша!!!
      EnableDocking(CBRS_ALIGN_TOP);
      
      DockControlBar(m_wndToolBar);
      DockControlBar(m_wndColorBar);// Наша!!!
      
      return 0;
    }
  • И последнее. Если потянуть панель в плавающем состоянии за нижний край, то список опять исчезает. Разозлимся и отключим у пользователя свет, газ и воду, а у панели - возможность изменять размер.

  • Просто закомментируйте код, как показано в листинге
    Отключение свойства панели изменять размер
    // Вспомогательная упаковка кода создания панели инструментов
    bool CMainFrame::CreateColorBar(void)
    {
      // Создать инструментальную панель 
      // для выбора цвета и настроить ее
      if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY /* | CBRS_SIZE_DYNAMIC */) ||
        !m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
      {
        TRACE0("Failed to create toolbar\n");
        return FALSE;      // fail to create
      }
    ..................................................
      // Устанавливаем первый элемент видимым
      m_ctrlWidth.SetCurSel(0);
      
      return TRUE;
    }
  • Строка состояния

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

    Анализ кода создания строки состояния, сгенерированного мастером

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

    Строка состояния MFC является окном немодального типа, наследуемым от класса CWnd, и поддерживается двумя классами:

  • CStatusBar - в основном используется в SDI и MDI приложениях
  • Некоторые методы класса CStatusBar Метод Описание CommandToIndex() Возвращает индекс индикатора по его идентификатору Create() Создает строку состояния как немодальное окно GetItemID() Возвращает идентификатор индикатора по его индексу GetItemRect() Возвращает параметры прямоугольника элемента по его индексу GetPaneInfo() Возвращает информацию об индикаторе GetPaneStyle() Возвращает стиль индикатора GetPaneText() Возвращает текст индикатора GetPaneBarCtrl() Возвращает ссылку на объект класса CStatusBarCtrl SetIndicators() Задает идентификатор индикатора SetPaneInfo() Задает для индикатора идентификатор, ширину и стиль SetPaneStyle() Задает стиль индикатора SetPaneText() Задает текст индикатора GetTextExtent() Возвращает размер индикатора (нужна только ширина)
  • Когда в нашем случае мастер создал приложение SDI (или MDI ), то мы видим, что сгенерированная им строка состояния содержит 4 панели (части). Первая панель предназначена для подсказок меню (или кнопок панели инструментов) и она самая длинная. Далее следуют еще три панели <Caps Lock>, <Num Lock> и <Scroll Lock> . Соответствующий код поддержки находится в трех местах, рассмотрим его

    Переменная-экземпляр класса CStatusBar - ее объявление находится в классе фреймового окна CMainFrame файла MainFrm.h

    Объявление переменной-экземпляра класса CStatusBar
    class CMainFrame : public CFrameWnd
    {
    ......................................................
    protected:  // control bar embedded members
      CStatusBar  m_wndStatusBar;
      CToolBar    m_wndToolBar;
    ......................................................
    };

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

    Массив индикаторов в файле MainFrm.cpp
    static UINT indicators[] =
    {
      ID_SEPARATOR,           // status line indicator
      ID_INDICATOR_CAPS,
      ID_INDICATOR_NUM,
      ID_INDICATOR_SCRL,
    };

    Порядок расположения идентификаторов в массиве соответствует позиции индикаторов в панели состояния. Первым в этом массиве всегда должен стоять ID_SEPARATOR, который имеет стандартное имя и сообщает среде выполнения, что с данной конкретной панелью строки состояния не связан ни один строковый ресурс. Фактически это означает, что первая панель строки состояния может принимать информацию от любых ресурсов, а в свободном состоянии отображать слово "Ready" (Готов).

    Три последних идентификатора массива индикаторов можно найти в строковом ресурсе файла ресурсов DrawSDI.rc при открытии внешним редактором

    Файл ресурсов DrawSDI.rc
    /////////////////////////////////////////////////////////////////////////////
    //
    // String Table
    //
      
    STRINGTABLE 
    BEGIN
        IDR_MAINFRAME           "DrawSDI\n\nDrawSDI\nDrawSDI Files (*.dhc)\n.dhc\nDrawSDI.Document\nDrawSDI.Document"
    END
      
    STRINGTABLE 
    BEGIN
        AFX_IDS_APP_TITLE       "DrawSDI"
        AFX_IDS_IDLEMESSAGE     "Ready"
    END
      
    STRINGTABLE 
    BEGIN
        ID_INDICATOR_EXT        "EXT"
        ID_INDICATOR_CAPS       "CAP"
        ID_INDICATOR_NUM        "NUM"
        ID_INDICATOR_SCRL       "SCRL"
        ID_INDICATOR_OVR        "OVR"
        ID_INDICATOR_REC        "REC"
    END

    Обратите внимание на текст, выделенный синим цветом. Кстати, если Вы этот текст в стороннем редакторе переместите в часть файла ресурсов String Table, поддерживающего русскую кодировку 1251, то вялое слово "Ready" можете заменить на свое, которое будет выдаваться как дежурное сообщение в первой панели строки состояния, когда эта панель простаивает.

    Функция OnCreate() фреймового окна-рамки - содержит код создания строки состояния и подсоединения к ней индикаторов. Вот соответствующий раздел функции в файле MainFrm.cpp

    Файл ресурсов DrawSDI.rc
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    ...................................................
      if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
      {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
      }
    ...................................................
    }

    Строка состояния создается функцией Create() с единственным обязательным параметром, являющемся указателем на родительское окно ( pParentWnd ). Родительским окном для строки состояния является фреймовое окно, поэтому достаточно указать this - указатель на экземпляр класса CMainFrame фреймового окна-рамки. Вторая функция SetIndicators() присваивает массив indicators[], в котором содержаться значения идентификаторов ресурсов, состояние которых нужно отобразить.

    Фактически функция Create() имеет три параметра, два из которых (последние) установлены по умолчанию.

  • CWnd* pParentWnd - указатель на родительское окно строки состояния
  • DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | CBRS_BOTTOM - комбинация флагов, определяющих тип, видимость, форму границ, месторасположение
  • UINT nID = AFX_IDW_STATUS_BAR - стандартный идентификатор самой строки состояния
  • Добавление индикатора цвета в строку состояния

    Для добавления в строку состояния нового индикатора прежде нужно создать его идентификатор в String Table файла ресурсов приложения. Идентификатор будет представлять в приложении закрепленный за ним индикатор. Чтобы в индикатор поместился любой закрепленный за ним текст, необходимо при создании соответствующего идентификатора ввести самый длинный текст из предполагаемых для отображения.

  • Вызовите на редактирование ресурс String Table и добавьте новую строку
  • В поле ID введите значение ID_INDICATOR_COLOR, поле Value оставьте по умолчанию, а в поле Caption введите строку Бирюзовый, как самую длинную из набора слов "Черный, Синий, Зеленый, Бирюзовый, Красный, Сиреневый, Желтый, Белый", подлежащих отображению
  • В массив индикаторов файла MainFrm.cpp добавьте новый идентификатор так
    Массив индикаторов в файле MainFrm.cpp
    static UINT indicators[] =
    {
      ID_SEPARATOR,           // status line indicator
      ID_INDICATOR_COLOR,
      ID_INDICATOR_CAPS,
      ID_INDICATOR_NUM,
      ID_INDICATOR_SCRL,
    };
  • Постройте приложение и убедитесь, что новый индикатор создан и в нем находится статический текст "Бирюзовый"
  • Связывание индикатора с обработчиком

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

  • Добавьте в класс CDrawSDIDoc новую функцию-обработчик, заполнив мастер так

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

  • Введите вручную в карту сообщений класса CDrawSDIDoc регистрацию обработчика за нашим новым индикатором с идентификатором ID_INDICATOR_COLOR
    Карта сообщений в файле DrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI)
      ON_UPDATE_COMMAND_UI(ID_INDICATOR_COLOR, OnUpdateIndicatorColor)
    END_MESSAGE_MAP()
  • Наполните обработчик кодом
    Код обработчика в файле DrawSDIDoc.cpp
    // Обработчик обновления индикатора
    void CDrawSDIDoc::OnUpdateIndicatorColor(CCmdUI* pCmdUI)
    {
      char* str[] = {"Черный", 
              "Синий",
              "Зеленый",
              "Бирюзовый",
              "Красный",
              "Сиреневый",
              "Желтый",
              "Белый"  
              };
      pCmdUI->Enable(); // По умолчанию TRUE - доступный
      pCmdUI->SetText((LPCTSTR)str[m_iColor]);
    }
  • Постройте приложение и убедитесь, что все работает
  • Нарисуйте свой автопортрет, например, так
  • А он лучше оригинала...

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

    Цель работы

  • Ознакомиться с архитектурой "Документ/Вид", используемой для создания однодокументных и многодокументных приложений
  • Научиться создавать каркасы однодокументных приложений
  • Научиться отделять данные от их представления
  • Научиться инкапсулировать данные в собственные классы C++
  • Научиться организовывать взаимодействие данных и меню
  • Посадить все опции меню одной и той же категории на один комбинированный обработчик с распознаванием нажатой опции
  • Создать собственную панель инструментов.
  • Научиться присоединять панель инструментов к рамке приложения
  • Отображать и скрывать панель инструментов с помощью элемента меню
  • Помещать поле со списком на панель инструментов
  • Отображать описание элементов панели инструментов в строке состояния
  • Добавлять собственные элементы строки состояния
  • Применение концепции Документ/Вид

    В основе приложений MFC лежит концепция document object/view window (Документ/Вид). Обычно документ представляет собой отдельный файл, который приложение способно открыть. В нем хранятся данные, управляемые приложением. Отображение данных и интерфейс работы с ними обеспечивает представление. Представление способно отображать данные документа частично и в разной форме, выполняя заданный срез документа.

    Отношение между документом и представлением строится по принципу "один ко многим". То есть один документ (одно содержание) может иметь несколько представлений (несколько форм), но каждое из этих представлений отображает одни и те же данные.

    Программно такая концепция реализуется механизмом SDI (Single Document Interface) - однодокументный интерфейс. Управление документом осуществляется объектом на основе производного класса от базового класса CDocument, а управление интерфейсами - на основе базового класса CView.

    Основой для реализации представления является фреймовое окно, определяющее границы приложения и называемое окном-рамкой. Оно порождается классом CFrameWnd. Фреймовое окно является главным окном приложения и играет роль родительского по отношению к содержащимся в нем окнам. Неотъемлемой частью фреймового окна является его заголовок и меню. Клиентскую область фреймового окна занимают всевозможные дочерние окна, в том числе окно панели инструментов, окно строки состояния, окно представления и др.

    В приложениях SDI, созданных мастером MFC Application Wizard, исходный код реализации фреймового окна (реализация класса CMainFrame ) размещается в файлах проекта MainFrm.h и MainFrm.cpp. Класс CMainFrame наследует практически все свои функциональные возможности от базового класса CFrameWnd.

    В свою очередь, фреймовым окном управляет каркас приложения, порожденный классом CWinApp. Каркас приложения не имеет визуального представления, его задача - обеспечить взаимодействие окна-рамки и дочерних фреймовых окон. Это взаимодействие осуществляется с помощью сообщений. Модель отношений между базовыми классами приложения SDI приведена на рисунке

    Класс CDocument обеспечивает стандартное поведение объектов документа MFC -приложения. Независимо от того, создается однодокументное приложение или многодокументное, мастер AppWizard создает только один класс документа, производный от базового класса CDocument.

    Классы массивов MFC

    В библиотеке 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() копируют в массив указатели, а не сами объекты. В результате, изменение значения объекта в одном из массивов приведет к изменению значения соответствующего объекта в другом массиве. Кроме того, при удалении элемента в одном из массивов указатель в другом массиве станет недействительным.

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

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

  • Войдите в свой каталог выполнения лабораторных работ
  • Создайте новый проект MFC Application и назовите его DrawSDI
  • Настройте мастер приложений Таблица настройки мастера создания заготовки приложения Свойство проекта Значение Вкладка Application Type Application Type: Single document Document/View architecture support Включено Resource language: Английский (США) Project style: MFC standard Use of MFC: Use MFC in a static library Вкладка Compound Document Support Compound document support: None Вкладка Document Template Strings File extension: dhc Вкладка Database Support Database support: None Вкладка User Interface Features Main frame styles: Minimize box Maximize box System menu Initial status bar Toolbars: Standard docking Вкладка Advanced Features Все оставить по умолчанию Вкладка Generated Classes Base class: CView Все остальное Оставить по умолчанию
  • После настройки вкладки мастера должны иметь такой вид

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

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

  • В панели Class View найдите верхний узел с названием DrawSDI и для него выполните опцию Add Class контекстного меню
  • В диалоговом окне
  • Появившееся окно мастера добавления класса заполните так
  • Щелкните по кнопке Finish
  • Мастер создаст заготовки для объявления и определения класса и разместит их в отдельных файлах MyLine.h и MyLine.cpp
  • Отредактируйте эти заготовки так, чтобы класс мог хранить координаты начальной и конечной точек линии. Для этого воспользуйтесь мастером добавления переменных, предварительно выделяя в панели Class View класс MyLine. Переменные определите с модификатором доступа private
  • Для того, чтобы класс мог нарисовать линию между двумя точками, добавьте в него с помощью мастера
  • Не забудьте нажать кнопку Add перед нажатием кнопки Finish

    В итоге должно получиться следующее

    Файл MyLine.h
    #pragma once
    #include "afx.h"
    #include "atltypes.h"
    
    class MyLine :
      public CObject
    {
    public:
      MyLine(CPoint, CPoint);
      ~MyLine(void);
    private:
      // Начальная точка линии
      CPoint m_pointBegin;
      // Конечная точка линии
      CPoint m_pointEnd;
    public:
      // Функция-член класса MyLine для рисования линий
      void Draw(CDC* pDC);
    };
    Файл MyLine.cpp
    #include "StdAfx.h"
    #include ".\myline.h"
      
    MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
    {
      m_pointBegin = pointFrom;
      m_pointEnd = pointTo;
    }
      
    MyLine::~MyLine(void)
    {
    }
      
    // Функция-член класса MyLine для рисования линий
    void MyLine::Draw(CDC* pDC)
    {
      // Нарисовать линию
      pDC->MoveTo(m_pointBegin);
      pDC->LineTo(m_pointEnd);
    }

    Конструирование класса документа

    Экземпляры объекта MyLine можно сохранять в объекте документа в простом динамическом массиве, реализуемом в MFC -библиотеке классом CObArray. Объект этого класса массива изменяет свой размер динамически в зависимости от количества находящихся в нем элементов. Этот класс-массив может содержать в себе любой объект, являющийся наследником класса CObject, размер массива ограничивается только количеством свободной памяти в компьютере.

    Создадим в классе CDrawSDIDoc приложения переменную-экземпляр класса CObArray для хранения линий-экземпляров созданного класса MyLine. Назовем ее m_Lines. Для этого:

  • В панели Class View выделите класс CDrawSDIDoc, через опцию Add Variable вызовите мастер и заполните его так

  • Таким же образом добавьте в класс CDrawSDIDoc функцию-член AddLine() для добавления объектов линий в массив, идентифицируемый только что созданной переменной. Мастер заполните так

    Мастер внесет следующие добавления в класс CDrawSDIDoc

    Файл DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ..............................................
    
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Добавить линию
      MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
    };
    Файл DrawSDIDoc.cpp
    // Добавить линию
    MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
    {
      return NULL;
    }
  • Отредактируйте заготовку функции AddLine() так

    Файл DrawSDIDoc.cpp
    // Добавить линию
    MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
    {
      MyLine* pLine = NULL; // Создали указатель на стеке
      
      try // Попытка
      {
        // Создать экземпляр объекта-линии на куче
        pLine = new MyLine(pointBegin, pointEnd); // Конструктор
        // Добавить указатель на новую линию в массив объектов
        m_Lines.Add(pLine);
        // Отметить документ как измененный
        SetModifiedFlag();
      }
      catch(CMemoryException* pErr) // Ловушка на недостаток памяти
      {
        // Сообщили пользователю
        AfxMessageBox("Недостаточно памяти", MB_ICONSTOP | MB_OK);
        // Уничтожим объект-линию, на котором сгенерировалось исключение
        if(pLine){
          delete pLine;
          pLine = NULL;
        }
      
        // Удалить объект с исключением
        pErr->Delete();
      }
      
      return pLine; // Вернуть указатель на объект-линию
    }
  • Подключите объявление класса MyLine к классу CDrawSDIDoc вначале файла DrawSDIDoc.h так

    Файл DrawSDIDoc.h
    // DrawSDIDoc.h : interface of the CDrawSDIDoc class
    //
      
      
    #pragma once
    #include "afxcoll.h"
      
    #include "MyLine.h"
      
    class CDrawSDIDoc : public CDocument
    {
    ...........................................
    };
  • Добавим в класс CDrawSDIDoc еще две публичные функции-члена, играющих роль функций доступа к закрытой переменной m_Lines:

  • GetLineCount() - возвращает количество объектов-линий, хранящихся в массиве m_Lines класса CObArray
  • GetLine() - возвращает указатель на линию, хранящийся в массиве m_Lines, по индексу массива
  • Обе функции доступа будут нужны в классе представления CDrawSDIView для отображения линий, хранящихся как закрытые в классе документа CDrawSDIDoc.

    Заполните мастера добавления функций так

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

    Добавления, выполненные мастерами, должны быть такими

    Добавления в файле DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ..........................................
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Добавить линию
      MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
      // Количество нарисованных линий
      int GetLineCount(void);
      // Возвращает указатель на объект-линию по индексу
      MyLine* GetLine(int index);
    };
    Добавления в файле DrawSDIDoc.cpp
    // Количество нарисованных линий
    int CDrawSDIDoc::GetLineCount(void)
    {
      return 0;
    }
      
    // Возвращает указатель на объект-линию по индексу
    MyLine* CDrawSDIDoc::GetLine(int index)
    {
      return NULL;
    }
  • Заполните код функций так

    Код функций доступа в файле DrawSDIDoc.cpp
    // Количество нарисованных линий
    int CDrawSDIDoc::GetLineCount(void)
    {
      // Возвратить количество элементов в массиве
      return (int)m_Lines.GetCount();
    }
      
    // Возвращает указатель на объект-линию по индексу
    MyLine* CDrawSDIDoc::GetLine(int index)
    {
      // Возвратить указатель на объект-линию, хранящийся 
      // в указанной позиции в массиве CObArray m_Lines
      return (MyLine*)m_Lines[index];
    }
  • Постройте приложение, чтобы своевременно выловить возможные ошибки
  • Конструирование класса представления

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

  • Введите частную переменную m_pointPrevPos в класс CDrawSDIView для хранения предыдущей точки рисования

  • В панели Class View выберите класс CDrawSDIView
  • Используя вкладку Properties в режиме Messages, добавьте к классу CDrawSDIView функции для обработки сообщений операционной системы

  • WM_LBUTTONDOWN - для начала рисования и захвата мыши
  • WM_LBUTTONUP - для завершения рисования и освобождения мыши
  • WM_MOUSEMOVE - для выполнения рисования и запоминания координат линий в классе документа
  • Добавление переменной и обработчиков в класс CDrawSDIView
    class CDrawSDIView : public CView
    {
    ...................................................
    private:
      // Предыдущая точка рисования
      CPoint m_pointPrevPos;
    public:
      afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
      afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
      afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    };
    Регистрация обработчиков в карте сообщений DrawSDIView.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIView, CView)
      // Standard printing commands
      ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
      ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
      ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
      ON_WM_LBUTTONDOWN()
      ON_WM_LBUTTONUP()
      ON_WM_MOUSEMOVE()
    END_MESSAGE_MAP()
  • Отредактируйте функции-обработчики, как показано ниже

    Код функций-обработчиков в файле DrawSDIView.cpp
    void CDrawSDIView::OnLButtonDown(UINT nFlags, CPoint point)
    {
      // Захватим мышь, чтобы другое приложение не могло
      // перезахватить мышь в момент, когда она
      // покинет пределы окна приложения
      SetCapture(); // мышь только для нас!!!
      
      // Сохраним точку начала рисования
      m_pointPrevPos = point;
      
      CView::OnLButtonDown(nFlags, point);
    }
      
    void CDrawSDIView::OnLButtonUp(UINT nFlags, CPoint point)
    {
      // Если мышь была ранее захвачена нашим приложением,
      // то отдадим ее, чтобы другие приложения
      // тоже могли использовать мышь
      if(GetCapture() == this)
        ReleaseCapture();
      
      CView::OnLButtonUp(nFlags, point);
    }
      
    void CDrawSDIView::OnMouseMove(UINT nFlags, CPoint point)
    {
      // Если нажата левая кнопка мыши, то рисовать
      if((nFlags  MK_LBUTTON) == MK_LBUTTON){
        // Если мышь захвачена нами, то рисовать
        if(GetCapture() == this){
          // Получить контекст устройства
          // клиентской области окна приложения
          CClientDC dc(this);
          // Добавить линию к документу и вернуть на нее 
          // указатель как на объект, чтобы воспользоваться
          // ее функцией-членом для рисования
          MyLine* pLine = 
            GetDocument()->AddLine(m_pointPrevPos, point);
      
          // Нарисовать текущую линию
          pLine->Draw(dc);
      
          // Сохранить текущую точку в качестве предыдущей
          m_pointPrevPos = point;
        } // end of if
      } // end of if
      
      CView::OnMouseMove(nFlags, point);
    }
  • Постройте приложение, порисуйте и поперемещайте клиента заграницы экрана

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

  • Добавьте в функцию OnDraw() код прорисовки всех сохраненных линий (не забудьте раскомментировать аргумент указателя CDC* в заголовке функции), как показано в листинге

    Код функции OnDraw() в файле DrawSDIView.cpp
    void CDrawSDIView::OnDraw(CDC* pDC)
    {
      CDrawSDIDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      if (!pDoc)
        return;
      
      // TODO: add draw code for native data here
      
      // Получить количество линий в документе
      int count = pDoc->GetLineCount();
      
      // Если есть линии, то нарисовать их все
      if(count){
        for(int i = 0; i < count; i++){
          MyLine* pLine = pDoc->GetLine(i);
          pLine->Draw(pDC);
        }
      } 
    }
  • Постройте приложение и убедитесь, что теперь все восстанавливается после разрушения окна приложения
  • Сохранение и загрузка рисунков

    Когда в классе документа CDrawSDIDoc срабатывает функция OnNewDocument(), то данные в массиве m_Lines необходимо уничтожить и освободить занимаемую ими память. Это ни в коем случае нельзя делать в функции OnNewDocument(), (хотя принципиально это и можно сделать), потому, что по логике своего предназначения эта функция должна инициализировать данные нового документа.

    Удаление текущего рисунка

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

  • В панели Class View выберите класс CDrawSDIDoc
  • Во вкладке Properties включите режим Overrides (Переопределение)
  • Найдите виртуальную функцию DeleteContents класса CDocument и создайте переопределение этой функции

  • Заполните переопределенную функцию DeleteContents() так

    Код переопределения виртуальной функции DeleteContents() в файле DrawSDIDoc.cpp
    void CDrawSDIDoc::DeleteContents()
    {
      // Получить количество линий в массиве объектов
      int count = (int)m_Lines.GetCount();
      
      // Попытка выполнить удаление объектов
      try 
      {
        if(count){ // есть что удалять
      
          // Освободить память на куче по каждому объекту
          for(int i = 0; i < count; i++)
            delete (MyLine*)m_Lines.GetAt(i);
      
          // Установить массив в исходное состояние
          m_Lines.RemoveAll();
        }
      }
      // Ловушка исключения о нарушении памяти
      catch(CMemoryException* pErr) 
      {
        // Отобразить сообщение о причине исключения
        pErr->ReportError();
      
        // Удалить объект, вызвавший исключение
        pErr->Delete();
      }
      
      CDocument::DeleteContents();
    }
  • Сохранение документа в файл и загрузка из файла

    Добавим возможность сохранения документа. Воспользуемся функцией сериализации Serialize() класса CDrawSDIDoc, которую перепишем так, чтобы она только передавала сообщение о преобразовании в последовательную форму объекту-массиву m_Lines. В самом же объекте-массиве m_Lines предусмотрим свою функцию сериализации, в которой и осуществим сохранение документа с использованием функциональных возможностей потока ввода-вывода C++.

  • В классе CDrawSDIDoc найдите функцию Serialize() преобразования в последовательную форму
  • Удалите все содержимое функции и перепишите ее так

    Функция сериализации класса CDrawSDIDoc
    void CDrawSDIDoc::Serialize(CArchive ar)
    {
      // Передать объекту-массиву сообщение
      // о преобразовании в последовательную форму
      m_Lines.Serialize(ar);
    }
  • Добавьте новую функцию-член к классу MyLine, выделив класс MyLine в панели Class View и заполнив мастер

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

  • Отредактируйте функцию так

    Функция сериализации класса MyLine
    // Преобразование в последовательную форму объекта-массива
    void MyLine::Serialize(CArchive  ar)
    {
      CObject::Serialize(ar);
      
      if(ar.IsStoring())
        ar << m_pointBegin << m_pointEnd; // Сохранить
      else
        ar >> m_pointBegin >> m_pointEnd; // Восстановить
    }
  • Постройте приложение и попробуйте сохранить документ на диск. Система выдаст сообщение, что невозможно сохранить документ

  • Исправим это...

    Зарегистрируем класс MyLine, как способный самостоятельно выполнять сериализацию. Для этого:

  • В начало объявления класса MyLine в файле MyLine.h и перед конструктором класса в файле MyLine.cpp добавьте по строке регистрации и конструктор по умолчанию

    Конструктор по умолчанию теперь нужно определить самим, поскольку определен общий конструктор, который делает невозможным вызов автоматического конструктора по умолчанию.

    Добавление в объявление класса MyLine в файле MyLine.h
    class MyLine : public CObject
    {
    public:
      DECLARE_SERIAL(MyLine);
      MyLine(){}; // Констуктор по умолчанию
      MyLine(CPoint, CPoint);
      ~MyLine(void);
    private:
      // Начальная точка линии
      CPoint m_pointBegin;
      // Конечная точка линии
      CPoint m_pointEnd;
    public:
      // Функция-член класса MyLine для рисования линий
      void Draw(CDC* pDC);
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Преобразование в последовательную форму объекта-массива
      void Serialize(CArchive  ar);
    };
    Добавление вначало файла MyLine.cpp
    #include "StdAfx.h"
    #include ".\myline.h"
      
    IMPLEMENT_SERIAL(MyLine, CObject, 1)
      
    MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
    {
      m_pointBegin = pointFrom;
      m_pointEnd = pointTo;
    }
      
    MyLine::~MyLine(void)
    {
    }
    ....................................................
  • Постройте приложение и убедитесь, что документы сохраняются на диске и загружаются с диска. Вот один из восстановленных шедевров

  • Реконструкция приложения для добавления цвета и толщины линий

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

    Изменения в классе MyLine

  • Добавьте переменные в класс MyLine и функцию доступа

    Добавления в файл MyLine.h
    class MyLine : public CObject
    {
    public:
      DECLARE_SERIAL(MyLine);
      MyLine(){}
      MyLine(CPoint, CPoint);
      ~MyLine(void);
    private:
      // Начальная точка линии
      CPoint m_pointBegin;
      // Конечная точка линии
      CPoint m_pointEnd;
    public:
      // Функция-член класса MyLine для рисования линий
      void Draw(CDC* pDC);
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Преобразование в последовательную форму объекта-массива
      void Serialize(CArchive  ar);
    private:
      // Для хранения цвета
      COLORREF color;
      // Для хранения толщины линий
      int widthLine;
    public:
      // Функция доступа
      void Set(COLORREF c, int w)
      {
        color = c;
        widthLine = w;
      }
    };
  • Инициализируйте переменные в общем конструкторе
    Добавления в файл MyLine.cpp
    MyLine::MyLine(CPoint pointFrom, CPoint pointTo)
    {
      m_pointBegin = pointFrom;
      m_pointEnd = pointTo;
      
      color = RGB(0, 0, 0);
      widthLine = 1;
    }
  • Дополните функцию рисования Draw класса MyLine

    Добавления в файл MyLine.cpp
    // Функция-член класса MyLine для рисования линий
    void MyLine::Draw(CDC* pDC)
    {
      // Создать перо
      CPen currPen(PS_SOLID, widthLine, color);// Конструктор пера
      // Установить новое перо в качестве рисующего объекта
      CPen* pOldPen = pDC->SelectObject(currPen);
      
      // Нарисовать линию
      pDC->MoveTo(m_pointBegin);
      pDC->LineTo(m_pointEnd);
      
      // Восстановить прежнее перо
      pDC->SelectObject(pOldPen);
    }
  • Измените функцию MyLine::Serialize()

    Добавления в файл MyLine.cpp
    // Преобразование в последовательную форму объекта-массива
    void MyLine::Serialize(CArchive  ar)
    {
      CObject::Serialize(ar);
      
      if(ar.IsStoring()) // Сохранить
        ar << m_pointBegin << m_pointEnd 
           << (DWORD)color << widthLine; 
      else // Восстановить
        ar >> m_pointBegin >> m_pointEnd 
           >> (DWORD)color >> widthLine;
    }
  • Постройте приложение и проверьте - пока ничего измениться не должно
  • Изменения в классе CDrawSDIDoc

  • Добавьте в класс документа CDrawSDIDoc массив таблицы цветов, индекс массива таблицы цветов и толщину линии. Код приведен ниже

    Добавления в файл DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ................................................
    private:
      // Массив линий
      CObArray m_Lines;
    public:
      // Добавить линию
      MyLine* AddLine(CPoint pointBegin, CPoint pointEnd);
      // Количество нарисованных линий
      int GetLineCount(void);
      // Возвращает указатель на объект-линию по индексу
      MyLine* GetLine(int index);
      virtual void DeleteContents();
    private:
      int m_iColor; // индекс массива таблицы цветов
      int m_iWidth;  // толщина линии
    public:
      // Объявление таблицы цветов
      static const COLORREF m_Colors[8];
    };
  • Добавьте инициализацию переменных m_iColor и m_iWidth в функции OnNewDocument()

    Добавление в функцию OnNewDocument() файла DrawSDIDoc.cpp
    BOOL CDrawSDIDoc::OnNewDocument()
    {
      if (!CDocument::OnNewDocument())
        return FALSE;
      
      // TODO: add reinitialization code here
      // (SDI documents will reuse this document)
      // Инициализировать начальный цвет черным
      // Толщину линии 1
      m_iColor = 0;
      m_iWidth = 1;
      
      return TRUE;
    }
  • Добавьте в файл DrawSDIDoc.cpp таблицу цветов сразу после таблицы сообщений

    Добавления таблицы цветов в файл DrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
    END_MESSAGE_MAP()
      
    const COLORREF CDrawSDIDoc::m_Colors[] = {
      RGB(0, 0, 0),    // Black - черный
      RGB(0, 0, 255),    // Blue - синий
      RGB(0, 255, 0),    // Green - зеленый
      RGB(0, 255, 255),  // Cyan - голубой
      RGB(255, 0, 0),    // Red - красный
      RGB(255, 0, 255),  // Magenta - сиреневый
      RGB(255, 255, 0),  // Yellow - желтый
      RGB(255, 255, 255)  // White - белый
    };
  • Измените функцию AddLine() класса CDrawSDIDoc так, чтобы перед сохранением объекта MyLine в него добавлялась информация о цвете и толщине линии

    Добавления в функцию AddLine() файла DrawSDIDoc.cpp
    // Добавить линию
    MyLine* CDrawSDIDoc::AddLine(CPoint pointBegin, CPoint pointEnd)
    {
      MyLine* pLine = NULL; // Создали указатель на стеке
      
      try // Попытка
      {
        // Создать экземпляр объекта-линии на куче
        pLine = new MyLine(pointBegin, pointEnd); // Конструктор
        // Добавить информацию о текущем цвете и толщине линии
        pLine->Set(m_Colors[m_iColor], m_iWidth);
        // Добавить указатель на новую линию в массив объектов
        m_Lines.Add(pLine);
        // Отметить документ как измененный
        SetModifiedFlag();
      }
      catch(CMemoryException* pErr) // Ловушка на недостаток памяти
      {
        // Сообщили пользователю
        AfxMessageBox("Недостаточно памяти", MB_ICONSTOP | MB_OK);
        // Уничтожим объект-линию, на котором сгенерировалось исключение
        if(pLine){
          delete pLine;
          pLine = NULL;
        }
      
        // Удалить объект с исключением
        pErr->Delete();
      }
      
      return pLine; // Вернуть указатель на объект-линию
    }
  • Изменение меню

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

  • Добавить категорию для выбора цвета
  • Добавить категорию для выбора толщины линии
  • Добавить обработчики для каждого нового пункта меню
  • Добавить обработчики, которые бы проверяли, какая позиция меню представляет текущий цвет и текущую толщину линии
  • Изменение ресурса меню

  • Через вкладку Resource View вызовите ресурс меню на редактирование
  • Отредактируйте ресурс меню в соответствии с рисунками, которые получены для режима Edit IDs оболочки
  • Мастер создал макроопределения. Чтобы их увидеть, в панели Solution Explorer необходимо файл приложения Resource.h. Файл должен содержать, примерно, следующий код

    Файл приложения Resource.h
    //{{NO_DEPENDENCIES}}
    // Microsoft Visual C++ generated include file.
    // Used by DrawSDI.rc
    //
    #define IDD_ABOUTBOX                    100
    #define IDP_OLE_INIT_FAILED             100
    #define IDR_MAINFRAME                   128
    #define IDR_DrawSDITYPE                 129
    #define ID_COLOR_BLACK                  32771
    #define ID_COLOR_BLUE                   32772
    #define ID_COLOR_GREEN                  32773
    #define ID_COLOR_CYAN                   32774
    #define ID_COLOR_RED                    32775
    #define ID_COLOR_MAGENTA                32776
    #define ID_COLOR_YELLOW                 32777
    #define ID_COLOR_WHITE                  32778
    #define ID_WIDTH_WIDTH                  32779
    #define ID_WIDTH_1                      32780
    #define ID_WIDTH_2                      32781
    #define ID_WIDTH_3                      32782
    #define ID_WIDTH_4                      32783
    #define ID_WIDTH_5                      32784
    #define ID_WIDTH_6                      32785
    #define ID_WIDTH_7                      32786
    #define ID_WIDTH_8                      32787
    ...............................................

    Добавление обработчиков меню

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

    Первым способом является вызов в редакторе меню через команду Add Event Handler контекстного меню соответствующего пункта

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

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

    Первый тип сообщения ( COMMAND ) Windows генерирует каждый раз, когда пользователь выбирает соответствующий пункт меню. При этом сообщение передается со значением ID идентификатора пункта в качестве параметра сообщения. Сообщение типа UPDATE_COMMAND_UI не является чистым сообщением Windows, это запрос MFC на обновление внешнего вида отдельных пунктов меню перед их показом пользователю. Мы создадим обработчики для обеих сообщений каждого пункта, добавленного нами в меню.

  • Откройте вкладку Class View и выберите класс CDrawSDIDoc
  • В панели Properties установите режим Events и найдите идентификатор ID_COLOR_BLACK
  • Раскройте узел событий для пункта меню как объекта и для каждого из сообщений COMMAND и UPDATE_COMMAND_UI создайте обработчики, которые заполните так

    Обработчики меню для выбора цвета Black
    void CDrawSDIDoc::OnColorBlack()
    {
      m_iColor = 0;
    }
      
    void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      // Отобразить пункт меню (если TRUE) с флажком
      pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
    }
  • Аналогичным образом создайте обработчики для выбора всех других цветов

    Обработчики меню для выбора остальных цветов
    void CDrawSDIDoc::OnColorBlue()
    {
      m_iColor = 1;
    }
      
    void CDrawSDIDoc::OnUpdateColorBlue(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iColor == 1 ? TRUE : FALSE);
    }
      
    .........................................................
      
    void CDrawSDIDoc::OnColorWhite()
    {
      m_iColor = 7;
    }
      
    void CDrawSDIDoc::OnUpdateColorWhite(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iColor == 7 ? TRUE : FALSE);
    }
  • Подобным образом создайте обработчики для выбора через меню толщины линий рисования

    Обработчики меню для выбора толщины линий
    void CDrawSDIDoc::OnWidth1()
    {
      m_iWidth = 1;
    }
      
    void CDrawSDIDoc::OnUpdateWidth1(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iWidth == 1 ? TRUE : FALSE);
    }
      
    .....................................................
      
    void CDrawSDIDoc::OnWidth8()
    {
      m_iWidth = 8;
    }
      
    void CDrawSDIDoc::OnUpdateWidth8(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iWidth == 8 ? TRUE : FALSE);
    }
  • Построение комбинированных обработчиков

    Мы видим, что количество обработчиков пунктов меню очень большое. Имеется возможность скомбинировать обработчики меню каждой категории, существенно уменьшив количество обработчиков и сделав код более изящным. Для этого применяют групповые сообщения Windows с параметрами.

    Карта сообщений для индивидуальных обработчиков

    Если посмотреть карту сообщений в файле DrawSDIDoc.cpp, то можно увидеть, что обработчики и идентификаторы пунктов меню связаны так

    Карта сообщений в файле CDrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND(ID_COLOR_BLACK, OnColorBlack)
      ON_UPDATE_COMMAND_UI(ID_COLOR_BLACK, OnUpdateColorBlack)
      ON_COMMAND(ID_COLOR_BLUE, OnColorBlue)
      ON_UPDATE_COMMAND_UI(ID_COLOR_BLUE, OnUpdateColorBlue)
      ON_COMMAND(ID_COLOR_GREEN, OnColorGreen)
      ON_UPDATE_COMMAND_UI(ID_COLOR_GREEN, OnUpdateColorGreen)
      ON_COMMAND(ID_COLOR_CYAN, OnColorCyan)
      ON_UPDATE_COMMAND_UI(ID_COLOR_CYAN, OnUpdateColorCyan)
      ON_COMMAND(ID_COLOR_RED, OnColorRed)
      ON_UPDATE_COMMAND_UI(ID_COLOR_RED, OnUpdateColorRed)
      ON_COMMAND(ID_COLOR_MAGENTA, OnColorMagenta)
      ON_UPDATE_COMMAND_UI(ID_COLOR_MAGENTA, OnUpdateColorMagenta)
      ON_COMMAND(ID_COLOR_YELLOW, OnColorYellow)
      ON_UPDATE_COMMAND_UI(ID_COLOR_YELLOW, OnUpdateColorYellow)
      ON_COMMAND(ID_COLOR_WHITE, OnColorWhite)
      ON_UPDATE_COMMAND_UI(ID_COLOR_WHITE, OnUpdateColorWhite)
      ON_COMMAND(ID_WIDTH_1, OnWidth1)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_1, OnUpdateWidth1)
      ON_COMMAND(ID_WIDTH_2, OnWidth2)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_2, OnUpdateWidth2)
      ON_COMMAND(ID_WIDTH_3, OnWidth3)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_3, OnUpdateWidth3)
      ON_COMMAND(ID_WIDTH_4, OnWidth4)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_4, OnUpdateWidth4)
      ON_COMMAND(ID_WIDTH_5, OnWidth5)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_5, OnUpdateWidth5)
      ON_COMMAND(ID_WIDTH_6, OnWidth6)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_6, OnUpdateWidth6)
      ON_COMMAND(ID_WIDTH_7, OnWidth7)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_7, OnUpdateWidth7)
      ON_COMMAND(ID_WIDTH_8, OnWidth8)
      ON_UPDATE_COMMAND_UI(ID_WIDTH_8, OnUpdateWidth8)
    END_MESSAGE_MAP()

    Обратите внимание, что каждое макроопределение карты сообщений, сгенерированное мастером Add Event Handler, имеет в заголовке только два параметра. Но существуют макроопределения, которые мастером не генерируются и которые нужно вводить вручную, но зато они имеют три параметра и способны регистрировать на один обработчик диапазон идентификаторов. Применительно к нашей задаче они могут выглядеть так

    Возможная таблица сообщений с диапазоном в файле CDrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnUpdateWidthUI)
    END_MESSAGE_MAP()

    Обработчикам передаются значения ID пунктов меню

    Написанные нами ранее в приложении обработчики, на примере выбора одного цвета, имеют вид

    Обработчики сообщений файла CDrawSDIDoc.cpp
    void CDrawSDIDoc::OnColorBlack()
    {
      m_iColor = 0;
    }
      
    void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      // Отобразить пункт меню (если TRUE) с флажком
      pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
    }
    ....................................................

    Мастер при создании первого обработчика типа OnColorBlack() не предусмотрел в нем передачу аргументов, однако этот обработчик может принимать аргумент в виде значения идентификатора нажатого пункта меню. Во второй обработчик типа OnUpdateColorBlack(CCmdUI *pCmdUI) в качестве аргумента передается указатель на объект, в свойстве pCmdUI->m_nID которого содержится значение идентификатора соответствующего пункта меню. Учитывая это, мы можем создать комбинированные обработчики для каждой категории меню.

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

    Дополнительные пункты меню категории Width
    Название пункта Толщина пера
    Medium (Средняя) 16
    Thick (Толстое) 24
    Very Thick (Очень толстое) 32
  • Через панель Resource View вызовите редактор меню
  • Добавьте в конец категории Width три новых пункта так, чтобы вначале названия пунктов были введены цифрами 16, 24, 32. Это нужно для того, чтобы редактор автоматически сформировал имена идентификаторов похожими на предыдущие пункты
  • Измените названия уже сгенерированных пунктов на слова, приведенные в таблице выше
  • Должно получиться что-то подобное в режиме Edit IDs редактора меню

    Новые комбинированные обработчики

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

  • Прототипы расположены объявлении класса CDrawSDIDoc в файле DrawSDIDoc.h
  • Определения размещены в конце файла DrawSDIDoc.cpp
  • Связь идентификаторов пунктов меню и имен соответствующих обработчиков прописана в карте сообщений класса CDrawSDIDoc в начале файла DrawSDIDoc.cpp
  • Для того, чтобы нам не организовывать в комбинированном обработчике условия проверки значений передаваемых идентификаторов, необходимо учесть, чтобы значения идентификаторов в каждой из категорий шли в порядке возрастания с шагом единица. Для этого:

  • Откройте через панель Solution Explorer файл Resource.h

    Он должен иметь примерно такой вид

    Идетификаторы пунктов меню в файле Resource.h
    ...............................................  
    #define ID_COLOR_BLACK                  32771
    #define ID_COLOR_BLUE                   32772
    #define ID_COLOR_GREEN                  32773
    #define ID_COLOR_CYAN                   32774
    #define ID_COLOR_RED                    32775
    #define ID_COLOR_MAGENTA                32776
    #define ID_COLOR_YELLOW                 32777
    #define ID_COLOR_WHITE                  32778
    #define ID_WIDTH_WIDTH                  32779
    #define ID_WIDTH_1                      32780
    #define ID_WIDTH_2                      32781
    #define ID_WIDTH_3                      32782
    #define ID_WIDTH_4                      32783
    #define ID_WIDTH_5                      32784
    #define ID_WIDTH_6                      32785
    #define ID_WIDTH_7                      32786
    #define ID_WIDTH_8                      32787
    #define ID_WIDTH_16                     32788
    #define ID_WIDTH_24                     32789
    #define ID_WIDTH_32                     32790
    ...............................................
  • В файле DrawSDIDoc.h уберите прототипы всех созданных ранее обработчиков для категорий меню Color и Width и введите прототипы комбинированных обработчиков отдельно для каждой категории в конец класса

    Код должен выглядеть так

    Прототипы комбинированных обработчиков в файле DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ................................................
    public:
      // Объявление таблицы цветов
      static const COLORREF m_Colors[8];
      
      // Комбинированные обработчики Color и Width
      afx_msg void OnColorCommand(UINT nID);
      afx_msg void OnUpdateColorUI(CCmdUI *pCmdUI);
      afx_msg void OnWidthCommand(UINT nID);
      afx_msg void OnUpdateWidthUI(CCmdUI *pCmdUI);
    };
  • В файле DrawSDIDoc.cpp карта сообщений после редактирования должна выглядеть так

    Карта сообщений в файле DrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI)
    END_MESSAGE_MAP()
  • Уберите в файле DrawSDIDoc.cpp код всех прежних обработчиков для категорий Color и Width и введите новый код обобщенных обработчиков так

    Новый код обработчиков сообщений в файле DrawSDIDoc.cpp
    
    // Срабатывает при нажатии на любой пункт меню Color
    void CDrawSDIDoc::OnColorCommand(UINT nID)
    {
      m_iColor = nID - ID_COLOR_BLACK;
    }
      
    // Срабатывает для каждого пункта перед раскрытием меню Color
    void CDrawSDIDoc::OnUpdateColorUI(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      bool check = pCmdUI->m_nID == m_iColor + ID_COLOR_BLACK;
      // Какой цвет установлен, такой пункт
      // меню и отобразить с флажком
      pCmdUI->SetCheck(check ? TRUE : FALSE);
    }
      
    // Срабатывает при нажатии на любой пункт меню Width
    void CDrawSDIDoc::OnWidthCommand(UINT nID)
    {
      switch(nID){
        case ID_WIDTH_16: m_iWidth = 16; break;
        case ID_WIDTH_24: m_iWidth = 24; break;
        case ID_WIDTH_32: m_iWidth = 32; break;
        default: m_iWidth = nID - ID_WIDTH_1 + 1;
      }
    }
      
    // Срабатывает для каждого пункта перед раскрытием меню Width
    void CDrawSDIDoc::OnUpdateWidthUI(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      
      bool check;
      int pos = 0;
      if(m_iWidth <= 8)
        pos = m_iWidth - 1;  //0-7
      else if(m_iWidth == 16)  
        pos = m_iWidth - 8;  //8
      else if(m_iWidth == 24)
        pos = m_iWidth - 15;//9
      else if(m_iWidth == 32)
        pos = m_iWidth - 22;//10
      // Какая толщина установлена, такой пункт
      // меню и отобразить с флажком
      check = pCmdUI->m_nID == pos + ID_WIDTH_1;
      pCmdUI->SetCheck(check ? TRUE : FALSE);
    }
  • Постройте приложение и проверьте его работоспособность
  • Оформите только About, но ни в коем случае не оформляйте строку заголовка окна и не русифицируйте ресурс String Table. Может смениться кодировка и придется тексты интерфейсов переколачивать заново вручную
  • Панели инструментов и строка состояния MFC

    В этой части мы наделим наше приложение еще большими возможностями, добавив панель инструментов и строку состояния.

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

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

    Добавление в приложение нового ресурса панели инструментов IDR_TBCOLOR

  • Откройте вкладку Resource View и через контекстное меню на узле Toolbar добавьте новый ресурс Toolbar, нажав кнопку New

    Появится окно графического редактора с заготовкой панели инструментов

    Одновременно в узле Toolbar вкладки Resource View приложения появится новый ресурс с автоматически сгенерированным идентификатором

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

    Чтобы не работать с карандашом, который включен по умолчанию, можно переключить инструмент рисования на кисть, предварительно вызвав через контекстное меню панели инструментов оболочки панель Image Editor

  • Убедитесь, что ресурс новой панели русифицирован, чтобы можно было заполнять в него тексты на русском
  • Выделите по очереди каждую кнопку новой панели и через вкладку оболочки Properties задайте свойства кнопок в соответствии с таблицей

    Свойство Prompt состоит из длинной (отображаемой в строке состояния) и короткой (всплывающей, когда пользователь задерживает курсор над кнопкой на время около 2 секунд) подсказок, разделенных управляющим символом "новая строка".

    ID Prompt
    ID_COLOR_BLACK Цвет рисования черный \n Черный
    ID_COLOR_BLUE Цвет рисования синий \n Синий
    ID_COLOR_GREEN Цвет рисования зеленый \n Зеленый
    ID_COLOR_CYAN Цвет рисования бирюзовый \n Бирюзовый
    ID_COLOR_RED Цвет рисования красный \n Красный
    ID_COLOR_MAGENTA Цвет рисования сиреневый \n Сиреневый
    ID_COLOR_YELLOW Цвет рисования желтый \n Желтый
    ID_COLOR_WHITE Цвет рисования белый \n Белый
  • Убедитесь, и это очень важно, что мы только что присвоили кнопкам панели инструментов IDR_TBCOLOR точно такие же идентификаторы, что и соответствующим пунктам меню категории Color
  • Они и должны совпадать, поскольку предназначены для одного и того же. А это значит, что обработчики команд специально для панели инструментов нам создавать не нужно - для одинаковых идентификаторов будут использованы обработчики команд меню.

    По умолчанию размер кнопок принят 16x15 пикселов. Использование изображений, размеры которых отличаются от принятых по умолчанию, вполне возможно, но об этом необходимо уведомить объект панели инструментов при помощи функции CToolBar::SetSizes().

  • Закройте окно графического редактора, чтобы случайно не испортить подготовленную панель инструментов
  • Любая панель инструментов хранится в виде растрового рисунка формата bmp в каталоге res проекта. Ее всегда можно открыть и отредактировать другим внешним редактором.

  • Выделите во вкладке
  • В нашем случае файл имеет имя toolbar1.bmp. Открытый во внешнем редакторе он имеет вид

    Присоединение панели инструментов IDR_TBCOLOR к рамке приложения

    Пока мы только заготовили ресурс панели инструментов. Теперь подсоединим его к приложению. Поскольку панели инструментов присоединяются к рамке, необходимо начать добавление и изменение кода именно в модуле рамки. Рамкой нашего приложения управляет класс CMainFrame, являющийся производным от базового класса CFrameWnd и расположенный в файлах MainFrm.h и MainFrm.cpp.

    У нас уже есть одна панель инструментов, автоматически сгенерированная мастером при создании заготовки приложения в самом начале выполнения лабораторной работы. В файле MainFrm.cpp можно увидеть, что она создается и прикрепляется к рамке окна приложения в функции OnCreate() - члене класса CMainFrame

    Функция OnCreate() файла MainFrm.cpp
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
      if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
      
      if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }
      
      if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
      {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
      }
      // TODO: Delete these three lines if you don't want the toolbar to be dockable
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      EnableDocking(CBRS_ALIGN_ANY);
      DockControlBar(m_wndToolBar);
      
      return 0;
    }

    В этой функции мы, подобным образом, создадим и прикрепим нашу новую панель. Но вначале для этого добавим в класс CMainFrame новую переменную типа CToolBar, которая и будет содержать новую панель. Эта переменная должна быть защищенной и назовем ее m_wndColorBar.

  • Щелкните правой кнопкой мыши на классе CMainFrame во вкладке Class View, вызовите мастер Add Variable, который заполните так

    Мастер добавит в объявление класса следующий код

    Объявление переменной-панели в классе CMainFrame файла MainFrm.h
    class CMainFrame : public CFrameWnd
    {
    ........................................................
    protected:
      afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
      DECLARE_MESSAGE_MAP()
      // Панель инструментов для цвета
      CToolBar m_wndColorBar;
    };
  • Добавьте к функции OnCreate() класса CMainFrame следующий код

    Добавления в функцию CMainFrame::OnCreate() файла MainFrm.cpp
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
      if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
      
      if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }
      
      // Создать инструментальную панель 
      // для выбора цвета и настроить ее
      if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }
      // Найти кнопку ID_COLOR_BLACK на инструментальной панели
        int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
      if(iTBCtrlID >= 0){
        // Установить все кнопки так, чтобы
        // они действовали как радиокнопки в группе
        for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
          m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
      }
      
      if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
      {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
      }
      
      // TODO: Delete these three lines if you don't want the toolbar to be dockable
      // Что делать: Удалите эти три строки, если инструментальную панель не нужно прикреплять
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
      EnableDocking(CBRS_ALIGN_ANY);
      
      DockControlBar(m_wndToolBar);
      DockControlBar(m_wndColorBar);// Наша!!!
      
      return 0;
    }
  • Постройте приложение и убедитесь, что панель ведет себя как нужно и переключает цвета точно также, как это делается через меню Color
  • Это происходит потому, что идентификаторы ресурсов кнопок и пунктов меню одни и те же.

    Анализ кода, добавленного в функцию CMainFrame::OnCreate() для IDR_TBCOLOR

    Класс панели инструментов CToolBar является лишь одним из нескольких классов, которые создают окна, пригодные для ввода команды и вывода информации. Базовым классом для всей этой группы элементов управления CToolBar, CStatusBar, CControlBar, CReBar, COleResizerBar, известных под общим названием панели управления, является класс CControlBar, производный от класса CWnd. Это очень важно, поскольку означает, что все панели инструментов как наследники класса CWnd, имеют все необходимые API -функции для работы с окнами.

    Класс CToolBar

    Класс CToolBar является наследником класса CControlBar, который в свою очередь является наследником класса CWnd. Соответственно класс CToolBar имеет все необходимые функциональные возможности для успешного управления программным способом панелями инструментов. Рассмотрим некоторые методы, в том числе и унаследованные, класса CToolBar.

    Некоторые методы класса CToolBar
    Метод Пояснения
    CreateEx() Создает панель инструментов
    LoadToolBar() Загружает ресурс панели инструментов
    CommandToIndex() Возражает индекс кнопки панели инструментов
    GetButtonStyle() Возвращает целочисленное значение стиля кнопки по ее индексу
    SetButtonStyle() Устанавливает стиль кнопки по ее индексу
    GetItemID() Возвращает идентификатор ресурса кнопки по ее индексу
    GetItenRect() Возвращает координаты размещения и размер кнопки
    GetButtonText() Возвращает содержимое текстовой подсказки кнопки по ее индексу в формате CString
    SetButtonText() Устанавливает для кнопки с индексом ее текстовую подсказку
    GetBarStyle() Возвращает стиль (переменную типа DWORD ), установленный ранее для панели инструментов
    SetBarStyle() Устанавливает стиль для панели инструментов
    GetCount() Возвращает количество элементов, расположенных на панели инструментов
    IsFloating() Возвращает булеву величину, указывающую, является ли в данный момент панель инструментов плавающей
    IsVisible() Возвращает булеву величину, указывающую, является ли в данный момент панель инструментов видимой
    GetDockingFrame() Возвращает указатель на фреймовое окно, которое является родительским для панели инструментов
    EnableDocking() Посылает уведомление панели инструментов и фреймовому окну о том, что пользователям разрешено самостоятельно перемещать панель инструментов и располагать ее в других частях фреймового окна.
    ShowWindow() Как член класса CWnd управляет видимостью панели инструментов как окном. При этом фон исчезнувшей панели инструментов не удается скрыть
    ShowControlBar() Управляет видимостью панели инструментов более умело с сокрытием и фона. Применяется совместно с функцией RecalcLayout()
    RecalcLayout() Заново создает макет в пределах рамки окна, т. е. перерасполагает заново все панели инструментов, строки состояния и все остальное в пределах рамки окна. Кроме того, данная функция передвигает созданную по умолчанию панель инструментов вверх и вниз при переключении состояния панели скрыть/отобразить
    GetButtonInfo() Возвращает информацию о кнопке
    SetButtonInfo() Устанавливает идентификатор, стиль и индекс пиктограммы для кнопки панели инструментов
    GetToolBarCtrl() Возвращает ссылку на объект класса CToolBarCtrl
    LoadBitmap() Загружает пиктограмму для панели инструментов
    SetBitmap() Устанавливает писктограмму для новой панели инструментов
    SetButtons() Устанавливает идентификаторы для кнопок панели инструментов
    SetHeight() Устанавливает высоту панели инструментов
    SetSizes() Устанавливает размеры кнопок панели инструментов

    Код создания панели инструментов IDR_TBCOLOR

    Добавленный нами ранее в функцию CMainFrame::OnCreate() фрагмент кода

    // Создать инструментальную панель 
      // для выбора цвета и настроить ее
      if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }

    содержит две функции-члена класса CToolBar

  • CreateEx() - создает панель инструментов с нужными свойствами в окне-рамке класса CMainFrame для экземпляра CToolBar m_wndColorBar
  • Параметры функции CreateEx() Параметр Пояснения первый параметр Первый параметр функции является указателем на родительское окно-рамку, к которому прикрепляется создаваемая панель инструментов. Он является обязательным, остальные же параметры могут быть опущены, так как они имеют значения по умолчанию второй параметр (необязательный) Второй параметр - это стиль расположения кнопок и других элементов управления на панели инструментов. третий параметр (необязательный) Третий параметр - это непосредственно стили самой панели инструментов. Этот параметр является комбинацией стилей окна и стилей элементов управления панели инструментов. Обычно используются только два или три стиля окна, а остальные являются стилями панели инструментов. Некоторые стили самой панели инструментов приведены ниже в таблице. четвертый параметр (необязательный) Четвертый параметр (отсутствует в нашем коде) - размер границы панели инструментов. Этот параметр задается как экземпляр класса стандартного прямоугольника CRect, задающего желаемую ширину и высоту панели инструментов. По умолчанию для всех измерений прямоугольника задается нуль, а это означает, что фактические размеры панели инструментов жестко не устанавливаются пятый параметр (необязательный) Пятый параметр (отсутствует в нашем коде) - идентификатор ( ID ) дочернего окна панели инструментов. По умолчанию он равен константе AFX_IDW_TOOLBAR, но при необходимости можно указать любой необходимый идентификатор
    Стили размещения кнопок относительно окна (второй параметр)
    Стиль Описание
    TBSTYLE_ALTDRAG Позволяет перемещать панель инструментов (перетаскивать ее при нажатии кнопки Alt )
    TBSTYLE_CHECK Задает режим поведения кнопок панели как флажков. Получаются кнопки с фиксацией, которые залипает при щелчке на них. Для возврата в исходное состояние необходимо щелкнуть повторно
    TBSTYLE_GROUP Кнопки панели рассматриваются как группа
    TBSTYLE_CUSTOMERASE Генерирует сообщение NM_CUSTOMDROW при стирании фона панели инструментов и кнопок, позволяя выбирать, когда и каким образом управлять процессом стирания фона
    TBSTYLE_FLAT Создает плоскую панель инструментов. Текст кнопки располагается под растровым изображением
    TBSTYLE_LIST Текст кнопки располагается справа от растрового изображения
    TBSTYLE_REGISTERDROP Используется при перетаскивании и опускании объектов на кнопки панели инструментов
    TBSTYLE_TOOLTIPS Создает всплывающую подсказку (элемент управления ToolTip ), которая может использоваться для отображения текста описания кнопок
    TBSTYLE_TRANSPARENT Создает прозрачную панель инструментов
    TBSTYLE_WRAPABLE Создает панель инструментов, которая может иметь несколько рядов кнопок
    Стили самой панели инструментов как окна (третий параметр)
    Стиль Описание
    WS_CHILD Панель инструментов создается как дочернее окно
    WS_VISIBLE Создается видимая сразу панель инструментов
    CBRS_ALIGN_TOP Позволяет прикреплять панель инструментов к верхней стороне рабочей области обрамляющего окна
    CBRS_ALIGN_BOTTOM Позволяет прикреплять панель инструментов к нижней стороне рабочей области обрамляющего окна
    CBRS_ALIGN_LEFT Позволяет прикреплять панель инструментов к левой стороне рабочей области обрамляющего окна
    CBRS_ALIGN_RIGHT Позволяет прикреплять панель инструментов к правой стороне рабочей области обрамляющего окна
    CBRS_ALIGN_ANY Позволяет прикреплять панель инструментов к любой стороне рабочей области обрамляющего окна
    CBRS_BORDER_TOP Если панель инструментов не прикреплена, помещает границу на верхний край панели инструментов
    CBRS_BORDER_BOTTOM Если панель инструментов не прикреплена, помещает границу на нижний край панели инструментов
    CBRS_BORDER_LEFT Если панель инструментов не прикреплена, помещает границу на левый край панели инструментов
    CBRS_BORDER_RIGHT Если панель инструментов не прикреплена, помещает границу на правый край панели инструментов
    CBRS_FLOAT_MULTI Позволяет помещать в одно плавающее мини-окно несколько панелей инструментов
    CBRS_TOOLTIPS Отображает всплывающие подсказки ( ToolTips ) для кнопок панелей инструментов
    CBRS_FLYBY Одновременно обновляет текст сообщения строки состояния и текст всплывающей подсказки ToolTip
    CBRS_GRIPPER Рисует область захвата на панели инструментов при ее перемещении
    CBRS_SIZE_DYNAMIC Позволяет изменять размеры окна панели инструментов
  • Код установки стилей кнопки

    Рассмотрим еще один фрагмент кода, добавленного нами ранее в функцию CMainFrame::OnCreate() файла MainFrm.cpp

    // Найти кнопку ID_COLOR_BLACK на инструментальной панели
        int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
       if(iTBCtrlID >= 0)
        {
        // Установить все кнопки так, чтобы
        // они действовали как радиокнопки в группе
        for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
          m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
       }

    Вначале применяется функция-член CommandToIndex(), которая возвращает индекс элемента управления, под которым он числится в панели инструментов как экземпляре класса CToolBar. Учитывая, что кнопка с таким идентификатором имеет наименьшее значение ID при графической разработки панели инструментов, в экземпляре класса она имеет наименьшее значение индекса (значение 0).

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

    Список возможных индивидуальных стилей кнопок панели инструментов
    Стиль Описание
    TBBS_AUTOSIZE Вычисляет ширину кнопки в зависимости от текста кнопки
    TBBS_BUTTON Создает стандартную кнопку (без фиксации)
    TBBS_CHECKBOX Создает кнопку, которая действует подобно флажку. Такая кнопка переключается из состояния "выбрана" в состояние "отменить выбор" и наоборот. Иными словами, это кнопка с фиксацией, которая залипает при щелчке на ней. Для возврата в исходное состояние необходимо щелкнуть на ней еще раз
    TBBS_CHECKGROUP Создает кнопку, которая действует подобно радиокнопкам (селективным кнопкам) - остается выбранной до выбора другой кнопки в группе. Фактически это комбинация стилей TBSTYLE_CHECK и TBSTYLE_GROUP
    TBBS_DROPDOWN Создается кнопку, представляющую собой раскрывающийся список
    TBBS_GROUP Создает кнопку, которая остается выбранной, пока не будет выбрана другая кнопка из группы
    TBBS_NOPREFIX Не связывает префикс акселератора (командной клавиши) с текстом кнопки
    TBBS_SEPARATOR Создает разделитель (небольшой интервал) между кнопками с обеих сторон

    Код закрепления панели инструментов IDR_TBCOLOR

    Рассмотрим следующий фрагмент кода, добавленного нами ранее в функцию CMainFrame::OnCreate() файла MainFrm.cpp

    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
      EnableDocking(CBRS_ALIGN_ANY);
      
      DockControlBar(m_wndToolBar);
      DockControlBar(m_wndColorBar);// Наша!!!

    По умолчанию положение панели инструментов можно изменить только программно. Но можно разрешить пользователям самостоятельно перемещать панели инструментов и располагать их в других частях фреймового окна. О том, что такая возможность разрешена, необходимо уведомить как саму панель инструментов, так и фреймовое окно. Для этого и вызываются функции CToolBar::EnableDocking() и CFrameWnd::EnableDocking() (разрешить прикрепление).

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

    Последняя функция DockControlBar(m_wndColorBar) кода - это функция фреймового окна, дающая команду изначально прикрепить панель инструментов, адрес на которую ей передается, к фрейму перед первым появлением окна на экране.

    Параметры, передаваемые функциями EnableDocking()
    Стиль Описание
    CBRS_ALIGN_TOP Прикрепляет панель инструментов к верхней стороне рабочей области обрамляющего окна
    CBRS_ALIGN_BOTTOM Прикрепляет панель инструментов к нижней стороне рабочей области обрамляющего окна
    CBRS_ALIGN_LEFT Прикрепляет панель инструментов к левой стороне рабочей области обрамляющего окна
    CBRS_ALIGN_RIGHT Прикрепляет панель инструментов к правой стороне рабочей области обрамляющего окна
    CBRS_ALIGN_ANY Позволяет прикреплять панель инструментов к любой стороне рабочей области обрамляющего окна
    CBRS_FLOAT_MULTI Позволяет помещать в одно плавающее мини-окно несколько панелей инструментов
    0 Не позволяет прикреплять панель инструментов к рамке

    Управление видимостью панели инструментов IDR_TBCOLOR

    Расширим меню View нашего приложения и добавим в него пункт включения/выключения панели инструментов IDR_TBCOLOR. Затем нужно создать код отображения и сокрытия панели инструментов. Здесь можно применить два варианта, первый из которых использует методы базового класса CWnd панели инструментов, второй - методы класса CToolBar.

    Вариант 1

    Прежде, чем отображать или скрывать панель инструментов, нужно выяснить ее текущее состояние (отображена/скрыта). Информацию о видимости панели инструментов содержит флаг WS_VISIBLE стиля ее окна. Для этого с помощью функции CWnd::GetStyle() необходимо получить сначала значение стиля окна панели инструментов, а затем сравнить его по маске со значением флага WS_VISIBLE.

    // Выяснить текущее состояние видимости
    bool bVisible = (m_wndColorBar.GetStyle()  WS_VISIBLE);

    На основании информации о том, скрыта панель инструментов или отображена, можно применить соответствующее действие

    // Показать или скрыть панель инструментов
    int nShow = (bVisible) ? SW_HIDE : SW_SHOWNORMAL;
    m_wndColorBar.ShowWindow(nShow);

    После этого обо всех программных изменениях панели инструментов необходимо уведомить фреймовое окно, которое повторно вычислит позиции всех панелей и перестроит их. Для этого достаточно вызова функции CFrameWnd::RecalcLayout()

    // Переконфигурировать элементы всех панелей инструментов,
    // строки состояния и всего остального в пределах фрейма
    RecalcLayout();

    Вариант 2

    Прежде, чем отображать или скрывать панель инструментов, нужно выяснить ее текущее состояние (отображена/скрыта). Информацию о видимости панели инструментов можно получить с помощью функции CControlBar::IsVisible(). Затем для отображения или скрытия панели инструментов можно применить функцию фреймового окна CFrameWnd::ShowControlBar(). Прототип этой функции

    void ShowControlBar(CControlBar* pBar, BOOL bShow, BOOL bDelay);
  • pBar - адрес переменной управляемой панели инструментов
  • bShow - булева переменная, которая определяет, показывать или скрывать панель инструментов ( TRUE - показать, FALSE - скрыть)
  • bDelay - булева переменная, которая определяет, показать/скрыть панель инструментов с задержкой ( TRUE ) или немедленно ( FALSE )
  • После этого обо всех программных изменениях панели инструментов необходимо уведомить фреймовое окно, которое повторно вычислит позиции всех панелей и перестроит их. Для этого достаточно вызова функции CFrameWnd::RecalcLayout()

    // Переконфигурировать элементы всех панелей инструментов,
    // строки состояния и всего остального в пределах фрейма
    RecalcLayout();

    Реализация управления видимостью

    Для добавления возможности управления видимостью нашей панели инструментов выполните следующие пошаговые инструкции:

  • Откройте ресурс меню на редактирование, русифицируйте его и добавьте в категорию Параметры для категории View ресурса меню Свойство Значение ID (идентификатор) ID_VIEW_COLORBAR Caption (надпись) Color Bar Prompt (подсказка) Показать или скрыть панель выбора цвета \n Показать/ Скрыть
  • Во вкладке Class View выделите класс CMainFrame
  • Во вкладке Properties установите режим Events
  • Найдите узел событий ID_VIEW_COLORBAR, раскройте его и добавьте обработчики для событий COMMAND и UPDATE_COMMAND_UI
  • Заполните обработчики так
    Обработчики меню View пункта Color Bar
    void CMainFrame::OnViewColorbar()
    {
    /***Студент Жук! Чтобы раскомментировать блок, добавьте спереди один слэш***
      // Вариант 1
      // Выяснить текущее состояние видимости
      bool bVisible = (m_wndColorBar.GetStyle()  WS_VISIBLE);
      // Показать или скрыть панель инструментов
      int nShow = (bVisible) ? SW_HIDE : SW_SHOWNORMAL;
      m_wndColorBar.ShowWindow(nShow);
    //*****************************************************/
      
    //***Студент Жук! Чтобы закомментировать блок, уберите спереди один слэш***
      // Вариант 2
      // Показать или скрыть панель инструментов
      ShowControlBar(m_wndColorBar
                      ,!m_wndColorBar.IsVisible()
                      ,FALSE);
    //*****************************************************/
      // Переконфигурировать элементы всех панелей инструментов,
      // строки состояния и всего остального в пределах фрейма
      RecalcLayout();// Можно и закомментировать, я разницу не нашел!
    }
      
    void CMainFrame::OnUpdateViewColorbar(CCmdUI *pCmdUI)
    {
      // В зависимости от видимости панели отобразить
      // пункт меню с отметкой флажком или без флажка
      pCmdUI->SetCheck(m_wndColorBar.IsVisible());
    }
  • Проследите за тем, чтобы обработчики были членами класса CMainFrame и попали в файл реализации MainFrm.cpp.
  • В первом обработчике реализованы два варианта управления отображением панели, рассмотренные выше. Один из вариантов закомментирован. Проверьте оба варианта, попеременно раскомментируя один и закомментируя другой. Обратите внимание на способ быстрого комментирования нужного блока, авось пригодится где-нибудь в "Силиконовой долине", где время - деньги.

    Добавление комбинированного списка к панели инстументов, отвечающего за установку толщины линии Width

    Добавим к панели инструментов комбинированный список, отвечающий за установку толщины пера рисования.

    Редактирование файла ресурсов проекта вручную

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

  • Перейдите на вкладку
  • Из контекстного меню этого ресурса откройте окно
  • Выберите текстовый редактор Source Code (Text) Editor, как показано на рисунке, и нажмите кнопку Open. Если появится окно сообщения с вопросом, хотим ли мы закрыть текущий редактор ресурсов (вкладку Resource View ),

    щелкните по кнопке Да ( Yes )

    А теперь будьте очень осторожными и случайно не испортите файл ресурсов, иначе это приведет к выводу из строя всего проекта приложения!!! (лучше вначале создать его копию)

  • Найдите раздел панели инструментов IDR_TBCOLOR и добавьте две строки SEPARATOR (разделитель) как показано ниже
    Прямое редактирование файла ресурсов DrawSDI.rc
    ....................................................  
    ///////////////////////////////////////////////////////////
    //
    // Toolbar
    //
      
    IDR_TBCOLOR TOOLBAR  16, 15
    BEGIN
        BUTTON      ID_COLOR_BLACK
        BUTTON      ID_COLOR_BLUE
        BUTTON      ID_COLOR_GREEN
        BUTTON      ID_COLOR_CYAN
        BUTTON      ID_COLOR_RED
        BUTTON      ID_COLOR_MAGENTA
        BUTTON      ID_COLOR_YELLOW
        BUTTON      ID_COLOR_WHITE
        SEPARATOR
        SEPARATOR
    END
    ...................................................
  • Закройте окно редактора с сохранением изменений
  • Теперь, если открыть вкладку Resource View, то можно увидеть предупреждение, что файл ресурсов редактировался напрямую через прочие редакторы

  • Сверните узел дерева с изображением папки и разверните вновь - предупреждение исчезнет
  • Прямое редактирование файла ресурсов было выполнено по трем причинам:

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

    Теперь нужно добавить текстовые строки как ресурс, которые будут загружены в комбинированный список при его создании. Следует помнить, что каждая строка комбинированного списка является отдельным ресурсом и должна иметь уникальный идентификатор с уникальным значением. Текстовые строки добавим через таблицу строк String Table ресурса приложения.

  • Через вкладку Resource View откройте узел String Table
  • Если вы не русифицировали этот ресурс и одновременно для кнопок панели инструментов вводили русские сообщения, то в файле ресурсов оболочка создала два раздела, относящиеся к таблице строк. Один из разделов с последними изменениями имеет вид

    Дополнительный раздел таблицы строк в DrawSDI.rc
    /////////////////////////////////////////////////////////////////////////////
    //
    // String Table
    //
      
    STRINGTABLE 
    BEGIN
        ID_COLOR_BLACK          "Цвет рисования черный\nЧерный"
        ID_COLOR_BLUE           "Цвет рисования синий\nСиний"
        ID_COLOR_GREEN          "Цвет рисования зеленый\nЗеленый"
        ID_COLOR_CYAN           "Цвет рисования бирюзовый\nБирюзовый"
        ID_COLOR_RED            "Цвет рисования красный\nКрасный"
        ID_COLOR_MAGENTA        "Цвет рисования сиреневый\nСиреневый"
        ID_COLOR_YELLOW         "Цвет рисования желтый\nЖелтый"
        ID_COLOR_WHITE          "Цвет рисования белый\nБелый"
    END
      
    STRINGTABLE 
    BEGIN
        ID_VIEW_COLORBAR        "Показать или скрыть панель выбора цвета\nПоказать/Скрыть"
    END
  • Двойным щелчком мыши на русифицированном ресурсе вызовите редактор таблицы строк. Добавляете новые строки в соответствии с таблицей, приведенной ниже. При этом следите за тем, чтобы значения поля Параметры для категории View ресурса меню ID Caption IDS_WIDTH_1 1 IDS_WIDTH_2 2 IDS_WIDTH_3 3 IDS_WIDTH_4 4 IDS_WIDTH_5 5 IDS_WIDTH_6 6 IDS_WIDTH_7 7 IDS_WIDTH_8 8 IDS_WIDTH_16 Medium IDS_WIDTH_24 Thick IDS_WIDTH_32 Very Thick
  • Должно получиться нечто подобное

    Теперь в файле ресурсов соответствующий раздел выглядит так

    Раздел таблицы строк в DrawSDI.rc после добавления
    //////////////////////////////////////////////////////////////
    //
    // String Table
    //
      
    STRINGTABLE 
    BEGIN
        ID_COLOR_BLACK          "Цвет рисования черный\nЧерный"
        ID_COLOR_BLUE           "Цвет рисования синий\nСиний"
        ID_COLOR_GREEN          "Цвет рисования зеленый\nЗеленый"
        ID_COLOR_CYAN           "Цвет рисования бирюзовый\nБирюзовый"
        ID_COLOR_RED            "Цвет рисования красный\nКрасный"
        ID_COLOR_MAGENTA        "Цвет рисования сиреневый\nСиреневый"
        ID_COLOR_YELLOW         "Цвет рисования желтый\nЖелтый"
        ID_COLOR_WHITE          "Цвет рисования белый\nБелый"
    END
      
    STRINGTABLE 
    BEGIN
        ID_VIEW_COLORBAR        "Показать или скрыть панель выбора цвета\nПоказать/Скрыть"
        IDS_WIDTH_1             "1"
        IDS_WIDTH_2             "2"
        IDS_WIDTH_3             "3"
        IDS_WIDTH_4             "4"
        IDS_WIDTH_5             "5"
        IDS_WIDTH_6             "6"
        IDS_WIDTH_7             "7"
        IDS_WIDTH_8             "8"
        IDS_WIDTH_16            "Medium"
        IDS_WIDTH_24            "Thick"
        IDS_WIDTH_32            "Very Thick"
    END

    Создание комбинированного списка на панели инструментов

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

  • Выберите вкладку
  • Нажмите кнопку
  • Для программной поддержки комбинированного списка создадим переменную m_ctrlWidth типа CComboBox как член класса CMainFrame. Для этого:

  • Во вкладке
  • Убедитесь в том, что мастер добавил переменную именно в класс CMainFrame файла MainFrm.h
  • Далее нам нужно будет выполнить такие действия:

  • Установить ширину и идентификатор ID заглушки-заполнителя комбинированного списка на панели инструментов равными соответственно ширине и идентификатору ID самого комбинированного списка
  • Определить размер и позицию комбинированного списка, получив для этого параметры заглушки-заполнителя на панели инструментов
  • Создать комбинированный список как программный объект, указав при этом панель инструментов в качестве его родительского окна
  • Загрузить текстовые строки в комбинированный список и установить индекс видимой начальной строки
  • Чтобы упорядочить этот процесс и не захламлять функцию OnCreate() класса фреймового окна CMainFrame не свойственным ей кодом, создадим отдельную функцию-член фрейма, а в функции OnCreate() разместим вызов этой вспомогательной упаковки кода. Вспомогательную функцию назовем CreateColorBar().

  • Выделите во вкладке Class View класс CMainFrame
  • Вызовите мастер
  • Перенесите (это значит полностью уберите со старого места!!!) соответствующий код из функции OnCreate() файла MainFrm.cpp в эту новую функцию (файл тот же!), а также добавьте новый код для создания комбинированного списка
  • Ниже приведен листинг функции, где красным выделен перенесенный код, а синим - добавленный новый код

    Вспомогательная функция в файле MainFrm.cpp
    // Вспомогательная упаковка кода создания панели инструментов
    bool CMainFrame::CreateColorBar(void)
    {
      // Создать инструментальную панель 
      // для выбора цвета и настроить ее
      if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
      {
        TRACE0("Failed to create toolbar\n");
        return FALSE;      // fail to create
      }
      // Найти кнопку ID_COLOR_BLACK на инструментальной панели
        int iTBCtrlID = m_wndColorBar.CommandToIndex(ID_COLOR_BLACK);
      if(iTBCtrlID >= 0){
        // Установить все кнопки так, чтобы
        // они действовали как радиокнопки в группе
        for(int i = iTBCtrlID; i < (iTBCtrlID + 8); i++)
          m_wndColorBar.SetButtonStyle(i, TBBS_CHECKGROUP);
      }
      
      // Добавить комбинированный список
      int nWidth = 100;
      int nHeight = 125;
      
      // Выбрать конфигурацию заглушки-заполнителя комбинированного списка
      m_wndColorBar.SetButtonInfo(9, IDC_CBWIDTH, TBBS_SEPARATOR, nWidth);
      
      // Получить высоту панели инструментов выбора цвета
      CRect rect;
      m_wndColorBar.GetItemRect(9, rect);
      rect.bottom = rect.top + nHeight;
      
      // Создать комбинированный список
      m_ctrlWidth.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST
                          ,rect, m_wndColorBar, IDC_CBWIDTH);
      
      // Заполнить комбинированный список текстами из ресурса String Table
      CString str;
      UINT id = IDS_WIDTH_1; // Важно, что значения идентификаторов
                               // расположены в порядке возрастания с шагом 1,
                             // поэтому можно организовать цикл
      for(; id - IDS_WIDTH_1 < 11; id++) // Перебираем 11 элементов
        if(str.LoadString(id))
          m_ctrlWidth.AddString((LPCTSTR)str);
      
      // Устанавливаем первый элемент видимым
      m_ctrlWidth.SetCurSel(0);
      
      return TRUE;
    }

    В приведенном коде используется функция

    // Выбрать конфигурацию заглушки-заполнителя комбинированного списка
      m_wndColorBar.SetButtonInfo(9, IDC_CBWIDTH, TBBS_SEPARATOR, nWidth);

    Первый параметр (9) - означает индекс размещаемого в панели элемента ( начинается с нуля ). Этим модифицируемым элементом-заглушкой будет ранее введенный нами последний сепаратор в файле ресурсов. Фрагмент файла ресурсов таков

    Идентификаторы элементов строки состояния в файле ресурсов
    /////////////////////////////////////////////////////////////////////////////
    //
    // Toolbar
    //
    
    IDR_TBCOLOR TOOLBAR  16, 15
    BEGIN
        BUTTON      ID_COLOR_BLACK
        BUTTON      ID_COLOR_BLUE
        BUTTON      ID_COLOR_GREEN
        BUTTON      ID_COLOR_CYAN
        BUTTON      ID_COLOR_RED
        BUTTON      ID_COLOR_MAGENTA
        BUTTON      ID_COLOR_YELLOW
        BUTTON      ID_COLOR_WHITE
        SEPARATOR
        SEPARATOR  // заглушка-заполнитель как элемент с индексом 9
    END

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

    Третий параметр - стандартная константа TBBS_SEPARATOR библиотеки MFC, представляющая разделитель панели инструментов.

    Четвертый аргумент - ширина заглушки-заполнителя для резервируемого места под комбинированный список.

  • Вновь дополните функцию CMainFrame::OnCreate() кодом вызова только что созданной упаковки CMainFrame::CreateColorBar(), после чего функция должна выглядеть так
    Вызов вспомогательной функции из OnCreate()
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
      if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
      
      if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
        !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
      {
        TRACE0("Failed to create toolbar\n");
        return -1;      // fail to create
      }
      
      // Добавить инструментальную панель выбора цвета
      // вместе с комбинированным списком выбора толщины пера
      if(!CreateColorBar()){
        TRACE0("Failed to create color toolbar\n");
        return -1;
      }
      
      if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
      {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
      }
      
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      m_wndColorBar.EnableDocking(CBRS_ALIGN_ANY);// Наша!!!
      EnableDocking(CBRS_ALIGN_ANY);
      
      DockControlBar(m_wndToolBar);
      DockControlBar(m_wndColorBar);// Наша!!!
      
      return 0;
    }
  • Постройте приложение и убедитесь, что список раскрывается, но ничего пока не делает
  • Мы создали комбинированный список как немодальное окно с помощью функции Create, которая выглядит так

    Функция Create в файле MainFrm.cpp
    // Вспомогательная упаковка кода создания панели инструментов
    bool CMainFrame::CreateColorBar(void)
    {
    ...............................................................
      // Создать комбинированный список
      m_ctrlWidth.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST
                          ,rect, m_wndColorBar, IDC_CBWIDTH);
    .......................................................................
      return TRUE;
    }

    Первым аргументом этой функции является сочетание стилей комбинированного списка, определяющих его внешние свойства и поведение. Часть флажков относятся к оконным стилям WS_ (window style) , поскольку комбинированный список наследует класс CWnd, а часть флажков специфицирует стиль только списка ( CBS_ - ComboBox Style ). Перечислим значения флажков стилей списка

    Стили комбинированного списка
    Стиль Описание
    WS_CHILD Указывает, что данное окно является дочерним (обязательный стиль)
    WS_VISIBLE Делает комбинированный список изначально видимым
    WS_DISABLED Отключает комбинированный список
    WS_VSCROLL Добавляет вертикальную прокрутку к раскрывающемуся списку
    WS_HSCROLL Добавляет горизонтальную прокрутку к раскрывающемуся списку
    WS_GROUP Группирует элементы управления
    WS_TABSTOP Включает комбинированный список в последовательность перехода по клавише Tab
    CBS_AUTOHSCROLL Обеспечивает прокрутку текста в поле редактирования при его заполнении
    CBS_DROPDOWN Подобен стилю CBS_SIMPLE, но не отображает список до тех пор, пока пользователь не выберет пиктограмму, следующую за полем редактирования
    CBS_DROPDOWNLIST Подобен стилю CBS_DROPDOWN. Однако заменяет поле редактирования статическим текстом, который отображает текущее выбранное значение элемента списка
    CBS_HASSTRINGS Указывает, что за перерисовку комбинированного списка отвечает владелец. Элементы в окне списка состоят из строк
    CBS_OEMCONVERT Текст, выводимый для отображения на экране в поле редактирования, конвертируется из набора символов ASCII в набор OEM, а затем, при передаче данных из комбинированного списка в программу - опять из набора OEM в набор символов ASCII. Набор отображаемых символов ASCII используется в MS-DOS, а OEM - в Windows. Символы с кодами 32-127 обычно совпадают в обеих системах. Другие же символы, используемые национальными кодировками, требуют конвертации. Для правильного отображения текста на экране в консольном режиме для этого используется функция CharToOem(char* strASCII, char* strOEM)
    CBS_OWNERDRAWFIXED Указывает, что владелец списка отвечает за перерисовку содержимого. Содержимое списка имеет фиксированную высоту
    CBS_OWNERDRAWVARIABLE Указывает, что владелец списка отвечает за перерисовку содержимого. Высота содержимого списка может изменяться
    CBS_SIMPLE Все время отображает список на экране
    CBS_SORT Автоматически сортирует строки в списке
    CBS_DISABLENOSCROLL Если список не содержит достаточно элементов для прокрутки, все равно отображается полоса прокрутки, хоть и в отключенном ( Disabled ) виде
    CBS_NOINTEGRALHEIGHT Указывает, что комбинированный список имеет точно указанный размер

    Обработка событий комбинированного списка, расположенного на панели инструментов

    При нажатии на кнопки панели инструментов нам не нужно передавать из класса CMainFrame, которому принадлежит панель инструментов, значение выбранного цвета пера в закрытую переменную m_iColor класса CDrawSDIDoc. Это делается через обработчики событий меню, которые перехватываются классом CDrawSDIDoc, поскольку кнопки панели и пункты меню Color имеют одинаковые идентификаторы.

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

  • Выделите класс CDrawSDIDoc во вкладке Class View и через контекстное меню вызовите мастер Add Function, который заполните так

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

  • Тело функции заполните так
    Функция доступа SetWidth() класса CDrawSDIDoc
    // Установить толщину пера
    void CDrawSDIDoc::SetWidth(int nWidth)
    {
      m_iWidth = nWidth;
    }
  • Теперь, имея указатель на текущий документ, мы сможем с помощью этой функции передать в документ значение толщины пера, установленное в комбинированном списке панели инструментов.

    Но как получить значение от комбинированного списка. Можно перхватывать события, которые генерирует комбинированный список при выборе пользователем нового элемента списка. Это событие имеет имя ON_CBN_SELCHANGE. Поскольку это сообщение генерируется в классе CMainFrame, перехватим его здесь. Для этого:

  • Введите вручную регистрацию события ON_CBN_SELCHANGE в карту сообщений класса, чтобы она выглядела так
    Карта сообщений класса CMainFrame в файле MainFrm.cpp
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
      ON_WM_CREATE()
      ON_COMMAND(ID_VIEW_COLORBAR, OnViewColorbar)
      ON_UPDATE_COMMAND_UI(ID_VIEW_COLORBAR, OnUpdateViewColorbar)
      ON_CBN_SELCHANGE(IDC_CBWIDTH, OnSelChangeWidth)
    END_MESSAGE_MAP()
  • Через мастер добавьте в класс
  • Заполните, обработчик кодом, как показано ниже
    Обработчик OnSelChangeWidth() в файле MainFrm.cpp
    // Функция-обработчик выбора элемента комбинированного списка
    void CMainFrame::OnSelChangeWidth(void)
    {
      int iIndex = m_ctrlWidth.GetCurSel();
      if(iIndex == CB_ERR)// Если получили индекс
        return;
      
      int iWidth = iIndex + 1;
      switch(iIndex + 1){ // Скорректировать толщину
        case  9: iWidth = 16; break;
        case 10: iWidth = 24; break;
        case 11: iWidth = 32;
      }
      
      // Получить активный документ
      CDrawSDIDoc* pDoc = (CDrawSDIDoc*)GetActiveDocument();
      if(pDoc) // Если получили указатель
        pDoc->SetWidth(iWidth); // Передать толщину в документ
    }
  • Поскольку класс документа CDrawSDIDoc в файле MainFrm.cpp не известен, включите в начало этого файла инструкцию #include. Следует иметь ввиду, что она должна располагаться в конце других инструкций #include, иначе могут появиться ошибки компиляции, поскольку предыдущие инструкции тоже могут содержать подобный код
    Инструкция включения файла DrawSDIDoc.h в файл MainFrm.cpp
    .........................................
    .........................................
    #include "DrawSDIDoc.h"
    // CMainFrame
    
    IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
    
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
      ON_WM_CREATE()
      ON_COMMAND(ID_VIEW_COLORBAR, OnViewColorbar)
      ON_UPDATE_COMMAND_UI(ID_VIEW_COLORBAR, OnUpdateViewColorbar)
      ON_CBN_SELCHANGE(IDC_CBWIDTH, OnSelChangeWidth)
    END_MESSAGE_MAP()
    .........................................
  • Постройте приложение и убедитесь, что список управляет толщиной линии, но при изменении толщины через меню значение списка синхронно не изменяется
  • Исправим это...

    Обновление комбинированного списка при вводе толщины пера через меню

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

    Функция-обработчик в файле DrawSDIDoc.cpp
    // Срабатывает при нажатии на любой пункт меню Width
    void CDrawSDIDoc::OnWidthCommand(UINT nID)
    {
      switch(nID){
        case ID_WIDTH_16: m_iWidth = 16; break;
        case ID_WIDTH_24: m_iWidth = 24; break;
        case ID_WIDTH_32: m_iWidth = 32; break;
        default: m_iWidth = nID - ID_WIDTH_1 + 1;
      }
    }

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

    Здесь необходимы следующие шаги:

  • Через обработчик меню вызвать функцию обновления комбинированного списка
  • В функции обновления, как члене документа, вначале получить указатель на активное окно представления View, поскольку сам документ не является оконным объектом и не происходит от базового класса CWnd
  • Затем через указатель окна представления получить указатель на обрамляющее это представление фреймовое окно-рамку, которому и принадлежит панель инструментов с комбинированным списком
  • И наконец, через полученный указатель на фреймовое окно обратиться к комбинированному списку и установить его в соответствие с выбранной через меню документа толщиной пера
  • Подключить к файлу документа заголовочный файл фреймового окна, поскольку будем добывать в документе указатель на фреймовое окно, а его порождающий класс CMainFrame в документе не виден
  • С учетом вышеизложенного выполните следующее:

  • Введите в документ функцию обновления комбинированного списка, назовите ее CDrawSDIDoc::UpdateComboBox(). Для этого для класса CDrawSDIDoc заполните мастер Add Function так

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

  • Мастер сгенерирует заготовку, которую заполните следующим кодом
    Функция UpdateComboBox() в файле DrawSDIDoc.cpp
    // Функция обновления комбинированного списка
    void CDrawSDIDoc::UpdateComboBox(int iIndex)
    {
      // Получить позицию активного вида в Z-последовательности видов
      POSITION pos = GetFirstViewPosition();
      if(!pos) // Позиция не получена, она нулевая
        return;
    
      // Получить указатель на верхнее представление (активный вид)
      CView* pView = GetNextView(pos);
      if(!pView) // Указатель не получен, он нулевой
        return;
    
      // Получить указатель на фреймовое окно с помощью вида
      CMainFrame* pFrame = (CMainFrame*)pView->GetTopLevelFrame();
      if(!pFrame) // Указатель не получен, он нулевой
        return;
    
      // Обновить комбинированный список на панели инструментов
      pFrame->m_ctrlWidth.SetCurSel(iIndex);
    }
  • Добавьте в начала файла DrawSDIDoc.cpp инструкцию препроцессору для подключения класса фреймового окна, объявление которого находится в файле приложения MainFrm.h. Имейте ввиду, что она должна располагаться в конце других инструкций #include, иначе могут появиться ошибки компиляции, поскольку предыдущие инструкции тоже могут содержать подобный код
    Инструкция препроцессору в файле DrawSDIDoc.cpp
    ...............................................................
    #include "MainFrm.h"
    // CDrawSDIDoc
      
    IMPLEMENT_DYNCREATE(CDrawSDIDoc, CDocument)
      
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI)
    END_MESSAGE_MAP()
    ...............................................................
  • Поместите вызов функции UpdateComboBox() в обработчике выбора пункта меню Width
    Вызов функции в обработчике меню файла DrawSDIDoc.cpp
    // Срабатывает при нажатии на любой пункт меню Width
    void CDrawSDIDoc::OnWidthCommand(UINT nID)
    {
      switch(nID){
        case ID_WIDTH_16: m_iWidth = 16; break;
        case ID_WIDTH_24: m_iWidth = 24; break;
        case ID_WIDTH_32: m_iWidth = 32; break;
        default: m_iWidth = nID - ID_WIDTH_1 + 1;
      }
      UpdateComboBox(nID - ID_WIDTH_1);
    }
  • Постройте приложение и убедитесь, что все работает правильно!!!..., кроме одного - при создании нового документа комбинированный список не устанавливается в начало
  • Чтобы это исправить, разместите вызов UpdateComboBox(0) еще и в функции OnNewDocument() класса CDrawSDIDoc (сделайте это самостоятельно!!!)
  • Есть еще одна неувязочка, которая может не понравиться ПОЛЬЗОВАТЕЛЮ. После прикрепления к левому или правому боку фреймового окна панель скрывает комбинированный список. На данном этапе исправим это, установив свойство панели прикрепляться только к верхнему краю, что не очень ограничит права пользователя. Для этого:

  • Измените в функции CMainFrame::OnCreate() соответствующий фрагмент кода
    Изменение свойства прикрепления панели в файле CMainFrm.cpp
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    ................................................................
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      EnableDocking(CBRS_ALIGN_ANY);
      m_wndColorBar.EnableDocking(CBRS_ALIGN_TOP);// Наша!!!
      EnableDocking(CBRS_ALIGN_TOP);
      
      DockControlBar(m_wndToolBar);
      DockControlBar(m_wndColorBar);// Наша!!!
      
      return 0;
    }
  • И последнее. Если потянуть панель в плавающем состоянии за нижний край, то список опять исчезает. Разозлимся и отключим у пользователя свет, газ и воду, а у панели - возможность изменять размер.

  • Просто закомментируйте код, как показано в листинге
    Отключение свойства панели изменять размер
    // Вспомогательная упаковка кода создания панели инструментов
    bool CMainFrame::CreateColorBar(void)
    {
      // Создать инструментальную панель 
      // для выбора цвета и настроить ее
      if (!m_wndColorBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
        | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY /* | CBRS_SIZE_DYNAMIC */) ||
        !m_wndColorBar.LoadToolBar(IDR_TBCOLOR))
      {
        TRACE0("Failed to create toolbar\n");
        return FALSE;      // fail to create
      }
    ..................................................
      // Устанавливаем первый элемент видимым
      m_ctrlWidth.SetCurSel(0);
      
      return TRUE;
    }
  • Строка состояния

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

    Анализ кода создания строки состояния, сгенерированного мастером

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

    Строка состояния MFC является окном немодального типа, наследуемым от класса CWnd, и поддерживается двумя классами:

  • CStatusBar - в основном используется в SDI и MDI приложениях
  • Некоторые методы класса CStatusBar Метод Описание CommandToIndex() Возвращает индекс индикатора по его идентификатору Create() Создает строку состояния как немодальное окно GetItemID() Возвращает идентификатор индикатора по его индексу GetItemRect() Возвращает параметры прямоугольника элемента по его индексу GetPaneInfo() Возвращает информацию об индикаторе GetPaneStyle() Возвращает стиль индикатора GetPaneText() Возвращает текст индикатора GetPaneBarCtrl() Возвращает ссылку на объект класса CStatusBarCtrl SetIndicators() Задает идентификатор индикатора SetPaneInfo() Задает для индикатора идентификатор, ширину и стиль SetPaneStyle() Задает стиль индикатора SetPaneText() Задает текст индикатора GetTextExtent() Возвращает размер индикатора (нужна только ширина)
  • Когда в нашем случае мастер создал приложение SDI (или MDI ), то мы видим, что сгенерированная им строка состояния содержит 4 панели (части). Первая панель предназначена для подсказок меню (или кнопок панели инструментов) и она самая длинная. Далее следуют еще три панели <Caps Lock>, <Num Lock> и <Scroll Lock> . Соответствующий код поддержки находится в трех местах, рассмотрим его

    Переменная-экземпляр класса CStatusBar - ее объявление находится в классе фреймового окна CMainFrame файла MainFrm.h

    Объявление переменной-экземпляра класса CStatusBar
    class CMainFrame : public CFrameWnd
    {
    ......................................................
    protected:  // control bar embedded members
      CStatusBar  m_wndStatusBar;
      CToolBar    m_wndToolBar;
    ......................................................
    };

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

    Массив индикаторов в файле MainFrm.cpp
    static UINT indicators[] =
    {
      ID_SEPARATOR,           // status line indicator
      ID_INDICATOR_CAPS,
      ID_INDICATOR_NUM,
      ID_INDICATOR_SCRL,
    };

    Порядок расположения идентификаторов в массиве соответствует позиции индикаторов в панели состояния. Первым в этом массиве всегда должен стоять ID_SEPARATOR, который имеет стандартное имя и сообщает среде выполнения, что с данной конкретной панелью строки состояния не связан ни один строковый ресурс. Фактически это означает, что первая панель строки состояния может принимать информацию от любых ресурсов, а в свободном состоянии отображать слово "Ready" (Готов).

    Три последних идентификатора массива индикаторов можно найти в строковом ресурсе файла ресурсов DrawSDI.rc при открытии внешним редактором

    Файл ресурсов DrawSDI.rc
    /////////////////////////////////////////////////////////////////////////////
    //
    // String Table
    //
      
    STRINGTABLE 
    BEGIN
        IDR_MAINFRAME           "DrawSDI\n\nDrawSDI\nDrawSDI Files (*.dhc)\n.dhc\nDrawSDI.Document\nDrawSDI.Document"
    END
      
    STRINGTABLE 
    BEGIN
        AFX_IDS_APP_TITLE       "DrawSDI"
        AFX_IDS_IDLEMESSAGE     "Ready"
    END
      
    STRINGTABLE 
    BEGIN
        ID_INDICATOR_EXT        "EXT"
        ID_INDICATOR_CAPS       "CAP"
        ID_INDICATOR_NUM        "NUM"
        ID_INDICATOR_SCRL       "SCRL"
        ID_INDICATOR_OVR        "OVR"
        ID_INDICATOR_REC        "REC"
    END

    Обратите внимание на текст, выделенный синим цветом. Кстати, если Вы этот текст в стороннем редакторе переместите в часть файла ресурсов String Table, поддерживающего русскую кодировку 1251, то вялое слово "Ready" можете заменить на свое, которое будет выдаваться как дежурное сообщение в первой панели строки состояния, когда эта панель простаивает.

    Функция OnCreate() фреймового окна-рамки - содержит код создания строки состояния и подсоединения к ней индикаторов. Вот соответствующий раздел функции в файле MainFrm.cpp

    Файл ресурсов DrawSDI.rc
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    ...................................................
      if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
      {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
      }
    ...................................................
    }

    Строка состояния создается функцией Create() с единственным обязательным параметром, являющемся указателем на родительское окно ( pParentWnd ). Родительским окном для строки состояния является фреймовое окно, поэтому достаточно указать this - указатель на экземпляр класса CMainFrame фреймового окна-рамки. Вторая функция SetIndicators() присваивает массив indicators[], в котором содержаться значения идентификаторов ресурсов, состояние которых нужно отобразить.

    Фактически функция Create() имеет три параметра, два из которых (последние) установлены по умолчанию.

  • CWnd* pParentWnd - указатель на родительское окно строки состояния
  • DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | CBRS_BOTTOM - комбинация флагов, определяющих тип, видимость, форму границ, месторасположение
  • UINT nID = AFX_IDW_STATUS_BAR - стандартный идентификатор самой строки состояния
  • Добавление индикатора цвета в строку состояния

    Для добавления в строку состояния нового индикатора прежде нужно создать его идентификатор в String Table файла ресурсов приложения. Идентификатор будет представлять в приложении закрепленный за ним индикатор. Чтобы в индикатор поместился любой закрепленный за ним текст, необходимо при создании соответствующего идентификатора ввести самый длинный текст из предполагаемых для отображения.

  • Вызовите на редактирование ресурс String Table и добавьте новую строку
  • В поле ID введите значение ID_INDICATOR_COLOR, поле Value оставьте по умолчанию, а в поле Caption введите строку Бирюзовый, как самую длинную из набора слов "Черный, Синий, Зеленый, Бирюзовый, Красный, Сиреневый, Желтый, Белый", подлежащих отображению
  • В массив индикаторов файла MainFrm.cpp добавьте новый идентификатор так
    Массив индикаторов в файле MainFrm.cpp
    static UINT indicators[] =
    {
      ID_SEPARATOR,           // status line indicator
      ID_INDICATOR_COLOR,
      ID_INDICATOR_CAPS,
      ID_INDICATOR_NUM,
      ID_INDICATOR_SCRL,
    };
  • Постройте приложение и убедитесь, что новый индикатор создан и в нем находится статический текст "Бирюзовый"
  • Связывание индикатора с обработчиком

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

  • Добавьте в класс CDrawSDIDoc новую функцию-обработчик, заполнив мастер так

    Не забудьте нажать кнопку Add перед нажатием кнопки Finish

  • Введите вручную в карту сообщений класса CDrawSDIDoc регистрацию обработчика за нашим новым индикатором с идентификатором ID_INDICATOR_COLOR
    Карта сообщений в файле DrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI)
      ON_UPDATE_COMMAND_UI(ID_INDICATOR_COLOR, OnUpdateIndicatorColor)
    END_MESSAGE_MAP()
  • Наполните обработчик кодом
    Код обработчика в файле DrawSDIDoc.cpp
    // Обработчик обновления индикатора
    void CDrawSDIDoc::OnUpdateIndicatorColor(CCmdUI* pCmdUI)
    {
      char* str[] = {"Черный", 
              "Синий",
              "Зеленый",
              "Бирюзовый",
              "Красный",
              "Сиреневый",
              "Желтый",
              "Белый"  
              };
      pCmdUI->Enable(); // По умолчанию TRUE - доступный
      pCmdUI->SetText((LPCTSTR)str[m_iColor]);
    }
  • Постройте приложение и убедитесь, что все работает
  • Нарисуйте свой автопортрет, например, так
  • А он лучше оригинала...

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