Опубликован: 17.08.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 9:

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

Сохранение и загрузка рисунков

Когда в классе документа 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
  1. Добавьте в класс документа 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];
    };
  2. Добавьте инициализацию переменных 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;
    }
  3. Добавьте в файл 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 - белый
    };
  4. Измените функцию 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; // Вернуть указатель на объект-линию
    }
Александр Даниленко
Александр Даниленко
Стоит Windows 8 Pro, Visual Studio 2010 Express Edition .
Эдуард Санин
Эдуард Санин
Украина, Харьков, ХАИ
Владимир Новосад
Владимир Новосад
Россия, Магадан