Программирование под Windows с использованием MFC Документ и его представления
- Документы.
- Класс CDocument
- Сериализация
- Представления
- Класс CView
- Класс CCtrlView
- Класс CEditView
- Класс CScrollView
- Класс CSplitterWnd
Любые изменения данных осуществляются с использованием специального класса документа, который обеспечивает необходимое пространство для их хранения в памяти, отвечает за запись и чтение документа с диска и предоставляет интерфейс для доступа и обновления данных. За изображение данных на экране, принтере или любом другом устройстве отвечает представление, которое использует для этого класс CView или производный от него. Можно сказать, что объект этого класса представляет собой окно, посредством которого, с одной стороны, осуществляется взаимодействие с пользователем, а с другой — организуется доступ к интерфейсной части документа для возможности обновления данных. В достаточно упрощенном виде сказанное можно проиллюстрировать с помощью рис. 1. Следует также повторить, что один и тот же документ одновременно может иметь несколько различных представлений.
Таким образом, действуя совместно, документ и его представления:
- содержат, изображают данные приложения и управляют ими;
- предоставляют интерфейс с данными документа;
- участвуют в чтении и записи файлов;
- предоставляют необходимые возможности для^ печати данных;
- обрабатывают большинство команд и сообщений приложения.
Рассмотрим основные компоненты архитектуры "документ/представление" подробно. Начнем с документов.
<>
Рис. 1. Взаимоотношения документа и представления
Документы
В самом общем случае можно сказать, что документы содержат и управляют данными приложения. Или, другими словами, документ представляет собой некоторую единицу данных, которую пользователь обычно открывает по команде ID_FILE_OPEN и сохраняет по команде ID_FILE_SAVE.
Для реализации документа в типичном приложении необходимо проделать следующую последовательность действий (рис. 2):
- для каждого типа документа образовать класс на базе CDocument,
- добавить в него переменные для хранения всех данных документа;
- реализовать функции для чтения и модификации этих данных;
- переопределить функцию CObject::Serialize в новом классе документа для организации чтения/записи данных документа с диска и на диск.
Данные документа определяются как переменные специального класса документа, производного от CDocument. Рассмотрим, как это реализовано в приложении NoteDraw, в котором используются документы двух типов. Для текстовых документов мы использовали класс библиотеки MFC CTypedPtrList, представляющий собой связанный список указателей на объекты произвольных типов:
class CNoteDoc : public CDocument
{
...
// Данные текстовых документов
public:
// Список указателей на объекты класса CParagraph,
// содержащего параметры форматирования абзаца текста
CTypedPtrList<CObList, CParagraph*> m_listPar;
...
};
<>
Рис. 2. Последовательность действий при создании документа
В данном случае список CTypedPtrList содержит указатели на объекты специального созданного класса CParagraph, отвечающего за хранение и использование таких параметров, как текст, его цвет, шрифт и стиль выравнивания для каждого абзаца текста.
Для графических документов используются другие типы данных:
class CDrawDoc : public CDocument
{
...
// Данные графических документов
protected:
// Параметры, используемые всеми нарисованными линиями
int m_nWidth;
CPen m_curPen;
COLORREF m_curColor;
public:
// Массив указателей на объекты класса CRect, содержащего
// координаты начальной и конечной точек рисуемой линии
CArray<CRect*, CRect*> m_lineArray;
...
//
CPen *GetPen() {return &m_curPen;}
...
};
Помимо самих данных, в классе документа зачастую определяются специальные функции для установки и извлечения элементов данных, а также для выполнения необходимых операций над ними (в приведенном фрагменте — функция GetPen).
К последнему действию, необходимому при создании какого-либо документа приложения — переопределению функции Serialize для реализации процесса сериализации — мы еще вернемся, а пока рассмотрим "цикл жизни" документа в рамках архитектуры "документ/представление":
- Во время динамического создания вызывается конструктор объекта — "документ".
- Для каждого нового документа вызывается функция OnNewDocument или OnOpenDocument.
- Пользователь взаимодействует с документом посредством представлений, ассоциированных с ним.
- Для удаления данных документа вызывается функция DeleteContents.
- Для удаления объекта "документ" вызывается его деструктор.
Перечисленные шаги характерны для MDI-приложений. При работе с SDI-приложениями первый шаг выполняется единственный раз — при первом создании документа, а последний — когда приложение завершает свою работу.
Класс CDocument
Этот класс предоставляет базовые функциональные возможности для классов документов, определенных пользователем (рис. 3). Он поддерживает все стандартные операции, такие как создание документа, его загрузка и сохранение. Библиотека MFC работает с документами, используя интерфейс, определенный в CDocument.
<>
Рис. 3. Место класса CDocument в иерархии библиотеки MFC
Объекты класса CDocument являются частью стандартного маршрута команд и поэтому получают команды от стандартных компонентов интерфейса пользователя. Документ получает команды раньше активного представления и если сам не обрабатывает некоторую команду, то передает ее на обработку своему шаблону.
Рассмотрим некоторые функции этого класса.
CDocument::CDocument ()
Создает объект класса. Непосредственно обработку создания документа выполняет библиотека MFC. Для выполнения каких-либо специфических инициализирующих действий необходимо переопределить функцию OnNewDocument, что особенно важно для однодокументных приложений.
CDocTemplate* CDocument::GetDocTemplate ()
Возвращает указатель на объект-шаблон для документа этого типа. Если документ этого типа не поддерживается каким-либо шаблоном, то возвращается NULL.
void CDocument::AddView (CView *pView)
Присоединяет представление pView к документу, добавляя его к общему списку представлений, ассоциированных с этим документом. Помимо этого, функция устанавливает указатель m_pDocument класса CView на этот документ. Вызывается библиотекой MFC при создании нового представления документа, обычно обработчиками команд ID_FILE_NEW, ID_FILE_OPEN, ID_WINDOW_NEW или при разбиении разделяемого окна.
void CDocument::RemoveView (CView *pView)
Отсоединяет представление pView oт документа и удаляет его от общего списка, представлений, ассоциированных с этим документом. Указатель m_pDocument класса CView устанавливается в NULL. Вызывается библиотекой MFC при закрытии фрейма или области разделенного окна.
virtual POSITION CDocument::GetFirstViewPosition ()
Позволяет получить позицию первого представления в общем списке представлений, ассоциированных с документом. Используется для возможности начала поиска требуемого представления.
virtual CView* CDocument::GetNextView (POSITION srPosition)
Возвращает указатель на объект класса CView (или производного от него), определяемый его позицией rPosition в списке представлений документа. После этого устанавливает rPosition в значение для следующего представления в списке.
Если полученное представление последнее в списке, то /Position устанавливается в NULL. Позиция представления в списке полностью определяется порядком его записи в него.
...
// Получаем указатель на документ, с которым
// ассоциировано текущее представление
CNoteDoc* pDoc = (CNoteDoc*)GetDocument();
// Получаем позицию первого представления, хранящегося
// в списке представлений, ассоциированных с документом
POSITION pos = pDoc->GetFirstViewPosition();
// Получаем указатель на первое представление в списке
pDoc->GetNextView (pos);
// Получаем указатель на второе представление в списке
CTextView *pText = (CTextView *)pDoc->GetNextView(pos);
...
// Читаем данные представления
pText->GetSelectedText(str);
...
void CDocument::UpdateAllViews (
CView *pSender,
LPARAM IHint = OL,
CObject *pHint = NULL)
Информирует каждое представление, присоединенное к документу, за исключением изменившего документ, на которое указывает pSender, о том, что документ был изменен. Это приводит к вызову функции CView::OnUpdate для каждого представления (за исключением определяемого параметром pSender) и передачи им информацию об изменениях в документе. Эта информация может быть представлена каким-либо кодом (параметр IHint) и/или содержаться в объекте (параметр pHint).
void CNoteView::OnPageSetup()
{
...
// После того как были изменены параметры страницы,
// необходимо обновить изображение данных на экране .
CNoteDoc* pDoc = (CNoteDoc*)GetDocument();
// Сообщение об обновлении передается всем представлениям,
// ассоциированным с документом, но реально предназначается
// только объекту класса, на который указывает this
pDoc->UpdateAllViews (NULL, 0, this);
...
}
void CNoteView::OnUpdate(CView*, LPARAM, CObject *pHint)
{
// Если определено представление, требующее перерисовки,
if(pHint != NULL)
//и оно является объектом класса CNoteView ...
if(pHint->IsKindOf(RUNTIME_CLASS(CNoteView)))
{
// ... изменяем размеры этого представления
...
}
...
}
virtual BOOL CDocument::CanCloseFrame (CFrameWnd *pFrame}
Вызывается библиотекой MFC до закрытия фрейма документа. Функция проверяет все фреймы, которые отображают документ, и если в нем были какие-либо изменения, выводит на экран окно запроса на сохранение. Параметр pFrame указывает на фрейм представления, присоединенного к документу.
virtual void CDocument::DeleteContents ()
Удаляет данные документа без разрушения самого объекта. Вызывается библиотекой MFC непосредственно перед разрушением документа, а также гарантирует, что документ пустой, если есть необходимость в его многократном использовании. Для выполнения "осмысленных" действий необходимо переопределить эту функцию, т. к. реализация по умолчанию не делает ничего.
void CNoteDoc::DeleteContents()
{
// Находим позицию первого абзаца в списке
POSITION роs = m_listPar.GetHeadPosition();
while(pos != NULL)
{
// Последовательно проходим весь список
CParagraph *pPar = m_listPar.GetNext(pos);
// Освобождаем память, занимаемую текущим абзацем.
delete pPar; }
// Очищаем список — делаем его "пустым"
m_listPar.RemoveAll();
// Вызываем функцию базового .класса для корректного завершения
CDocument::DeleteContents();
}
virtual BOOL CDocument::OnNewDocument 0
Вызывается библиотекой MFC при обработке команды ID_FILE_NEW. Реализация по умолчанию вызывает функцию DeleteContents, которая обеспечивает очистку документа, и соответствующим образом помечает его. При переопределении здесь рекомендуется инициализировать структуру данных нового документа. Для SDI-приложений эта функция вместо создания нового переинициализирует существующий объект документа. Для MDI-приложений библиотека MFC каждый раз создает новый объект документа, и в этой функций производится его инициализация.
BOOL CNoteDoc::OnNewDocument()
{
// Всю стандартную обработку по созданию документа
// возлагаем на библиотеку MFC — она делает это прекрасно!
if (!CDocumenr::OnNewDocument())
return FALSE;
// После успешного создания проводим "настройку"
// конкретного документа
initDoc();
return TRUE;
}
virtual BOOL CDocument::OnOpenDocument (LPCTSTR IpszPathName)
Вызывается библиотекой MFC при обработке команды ID_FILE_OPEN. Реализация по умолчанию открывает файл, имя которого определяется параметром IpszPathName, вызывает функцию DeleteContents, обеспечивающую очистку документа, функцию Serialize для чтения содержимого файла и помечает документ как немодифицированный. Переопределять ее следует, если используется какой-либо специальный механизм работы с архивами и/или файлами, например, распознавание формата документа.
virtual BOOL CDocument::OnSaveDocument (LPCTSTR IpszPathName)
Вызывается библиотекой MFC при обработке команд ID_FILE_SAVE или ID_FILE_SAVE_AS. Реализация по умолчанию открывает файл, имя которого определяется параметром IpszPathName, вызывает функцию Serialize для записи в файл данных документа и помечает его как немодифицированный.
Еще раз внимательно посмотрите на рис. 2. На нем наглядно показано, что документ может быть создан в двух случаях. В первом случае по команде ID_ FILE_NEW создается новый, пустой документ. В этом случае для его инициализации достаточно переопределить функцию класса документа OnNewDocument. Во втором по команде ID_FILE_OPEN создается новый документ, содержимое которого загружается из файла. При этом для его инициализации можно переопределить функцию класса документа OnOpenDocument. Этот случай представляет больший интерес, т. к. вплотную подводит нас к достаточно важному понятию не только в рамках архитектуры "документ/представление", но и во всей библиотеке MFC. Речь, конечно же, идет о сериализации или, другими словами, о преобразовании в последовательную форму. Основная идея сериализации заключается в том, чтобы обеспечить сохранение и восстановление текущего состояния объектов на устройстве постоянного хранения, например, в файле на диске. Поскольку речь идет о "преобразовании в последовательную форму", то очевидно, что состояние объекта сохраняется в бинарном формате.
Следующим важным моментом, на котором мы остановимся, является необходимость включить специальные макросы в объявление:
class CParagraph : public CObject
{
protected:
CParagraph();
// Обязательный макрос, обеспечивающий поддержку сериализации
DECLARE_SERIAL(CParagraph);
...
};
и реализацию класса:
// Обязательный макрос, обеспечивающий поддержку сериализации
IMPLEMENT_SERIAL(CParagraph, CObject, 2)
И, наконец, необходимо переопределить функцию Serialize, которая имеется у объекта любого класса, производного от CObject
void CNoteDoc::Serialize(CArchive& ar)
{
CNDApp *pApp = (CNDApp*)AfxGetApp();
if (ar.IsStoring() )
{
// Сохраняем текущий размер бумаги
ar « pApp->m_sizePaper;
} else
{
// Восстанавливаем текущий размер бумаги
ar » pApp->m_sizePaper;
}
// Для сохранения и восстановления собственно содержимого
// документа вызываем функцию Serialize класса CObList,
// который, в свою очередь, последовательно вызывает
// аналогичную функцию для каждого абзаца.
// Последнюю тоже необходимо было переопределить,
// чтобы сохранить данные конкретного документа
m_listPar.Serialize(ar);
}
void CParagraph::Serialize(CArchiveS ar)
{
LOGFONT If;
if (ar.IsStoring())
{
m_font.GetLogFont(&lf) ;
ar « m_align;
ar « m_color;
ar « If.IfCharSet;
ar « If.lfltalic;
ar « If.IfUnderline;
ar « If.IfStrikeOut;
ar « If.IfWeight;
ar « If.IfFaceName;
ar « (long)(If.lfHeight/2.3);
ar « m_words;
} else
{
CString tmp;
memset(Slf, 0, sizeof(LOGFONT));
ar » m_aiign;
ar » m_color;
ar »-If.IfCharSet;
ar » If.IfItalic;
ar » If.IfUnderline;
ar » If.IfStrikeOut;
ar » If.IfWeight;
ar » tmp;
strcpy(If.IfFaceName, tmp);
ar » If.IfHeight;
If.lfHeight = (long)(If.lfHeight*17.55);
ar » m_words;
m_font.CreatePointFontIndirect(&lf) ;
}
}
Последний представленный фрагмент приведен для иллюстрации следующего положения. При сохранении параметров выбранного шрифта, которые хранятся в объекте класса CFont, было бы логично ожидать, что при использовании операторов » и « архива произошло бы сохранение (и восстановление) объекта m_font, поскольку класс CFont является производным от CObject и, следовательно, поддерживает сериализацию. Однако этого не происходит, т. к. в самом этом классе не переопределена функция Serialize, а аналогичные функции базовых классов, естественно, корректно не работают. Поэтому, прежде чем использовать сериализацию классов, производных от CObject, проверьте, реализована ли в них эта удобная, а во многих случаях и необходимая функция.
Сериализация
Преобразование в последовательную форму и обратно, т. е. сериализация — метод, который позволяет сохранять и восстанавливать объекты классов, созданных на базе классов CObject. Этот метод можно разделить функционально на две составляющих.
С одной стороны, наличие определенной виртуальной функции (Serialize) позволяет унифицировать процесс сохранения/восстановления объектов. Впрочем, это обычная практика для программ, написанных с использованием практически любого объектно-ориентированного языка. Для реализации процесса следует только переопределить виртуальную функцию, вызов которой по указателю на объект одного из базовых классов приведет к вызову нужной функции. Библиотека MFC идет несколько дальше, обеспечивая открытие потока, прежде всего файла, самостоятельно. Связывая этот поток с классом CArchive, библиотека обеспечивает тем самым удобный способ определения типа операции (чтение/запись) и целый набор функций для всех простейших типов данных (например, чисел) и библиотечных классов.
С другой стороны, сериализация поддерживает механизм динамического создания объектов неизвестного заранее типа. Например, приложение должно сохранять и восстанавливать некоторое количество объектов различного типа. Естественно, что для восстановления объекта посредством вызова соответствующего конструктора необходимо точно знать тип создаваемого объекта. Механизм сериализации делает это за программиста, сохраняя необходимую информацию самостоятельно. Единственное, что требуется — все классы сохраняемых и восстанавливаемых объектов должны базироваться на классе CObject и обеспечить систему соответствующей информацией, используя макросы DECLARE_SERIAL и IMPLEMENT_SERIAL при объявлении и в реализации соответственно.
Теперь давайте рассмотрим все вышесказанное на примере. Класс CGmphicsView имеет список указателей на объекты класса CDrawObject и производных от него. Класс CDrawObject основан на CObject и является абстрактным, а классы CLine, CRectangle и CEllipse являются производными от CDrawObject и поддерживают динамическое создание на основе сохраненной информации. Класс CDrawObject не может поддерживать динамическое создание в полном объеме, т. к. является абстрактным.
Посмотрите объявление классов и реализацию их функций, обратив внимание на объявление конструкторов.
class CDrawObject : public CObject
{
protected: // вызывается только во время сериализации
CDrawObject();
public:
CDrawObject(BOOL Selected);
virtual -CDrawObject();
...
virtual void Draw(CDC *pDC) = 0;
virtual void Serialize(CArchiveS ar);
private:
BOOL m_Selected;
};
class CLine : public CDrawObject
{
protected: // вызывается только во время сериализации
CLine();
DECLARE_SERIAL(CLine)
public:
CLine(CPoint beg, CPoint end);
~CLine();
...
virtual void Draw(CDC *pDC);
virtual void Serialize(CArchiveS ar);
protected:
CPoint m_beg;
CPoint m_end;
}; class CRectangle : public CLine
{
protected: // вызывается только во время сериализации
CRectangle();
DECLARE_SERIAL{CRectangle)
public:
CRectangle(CPoint beg, CPoint end);
virtual -CRectangle();
...
virtual void Draw(CDC *pDC);
};
class CEllipse : public CLine
{
protected: // вызывается только во время сериализации -
CEllipse();
DECLARE_SERIAL(CEllipse) public:
CEllipse(CPoint beg, CPoint end);
virtual ~CEllipse();
...
virtual void DrawfCDC *pDC);
};
class CGraphicsDoc : public CDocument
{
protected: // вызывается только во время сериализации
CGraphicsDoc();
DECLARE_DYNCREATE(CGraphicsDoc)
...
public:
virtual void Serialize(CArchiveS ar);
...
private:
CTypedPtrList <CPtrList, CDrawObject *> m_List;
} ;
CDrawObject::CDrawObject()
{
m_Selected = FALSE;
}
CDrawObject::CDrawObject(BOOL Selected)
{
m_Selected = Selected;
}
CDrawObject::CDrawObject()
{
}
void CDrawObject::Serialize(CArchiveS ar)
{
if (ar.IsStoring() )
{
ar « m_Selected;
}
else
{
ar » m_Selected;
}
}
IMPLEMENT_SERIAL(CLine,CObject, 1)
CLine::CLine()
{
}
CLine::CLine(CPoint beg, CPoint end) : CDrawObject(FALSE)
{
m_beg = beg;
m_end = end;
}
CLine::-CLine()
{
}
void CLine::Serialize(CArchiveS ar)
{
CDrawObject::Serialize(ar);
if (ar.IsStoring() )
{
ar « m_beg; ar « m_end;
}
else
{
ar » m_beg;
ar » m_end;
}
}
void CLine::Draw (CDC* pDC)
{
...
}
IMPLEMENT_SERIAL (CRectaiigle, CLine, 1)
CRectangle::CRectangle ()
{
i
CRectangle::CRectangle(CPoint beg, CPoint end) : CLine(beg, end)
{
}
CRectangle::CRectangle()
{
}
void CRectangle::Draw(CDC* pDC)
{
}
IMPLEMENT_SERIAL(CEllipse,CLine,1)
CEllipse::CEllipse ()
{
}
CEllipse::CEllipse(CPoint beg, CPoint end) : CLine(beg, end)
{
}
CEllipse::-CEllipse()
{
}
void CEllipse::Draw(CDC* pDC)
{
..
}
void CGraphicsDoc::Serialize(CArchives ar)
{
if (ar.IsStoring())
{
ar « m_List.GetCount();
POSITION pos = m_List.GetHeadPosition();
while (pos != NULL)
ar « m_List.GetNext(pos);
}
else
{
int nCount;
ar » nCount;
while (nCount--)
{
CObject *pObject = NULL;
ar » pObject;
m_List.AddTail(pObject);
}
}
}
Итак, каждый класс имеет конструкторы двух типов: во-первых, это конструкторы без параметров (реализация конструкторов по умолчанию), которые объявлены как защищенные, а во-вторых, это общедоступные конструкторы, которые можно использовать для создания объектов по мере необходимости. Защищенные конструкторы без параметров как раз и вызываются при восстановлении объектов в момент выполнения оператора чтения из архива.
ar » pObject;
Собственно чтение в этот момент активизирует не только соответствующие конструкторы, но и функции Serialize уже созданных объектов. Например, вызов конструкторов и функций для восстановления объекта класса CRectangle происходит в следующем порядке:
1. CObject: :CObject ()
2. CDrawObject::CDrawObject()
3. CLine::CLine()
4. CRectangle::CRectangle()
5. CLine::Serialize()
Поскольку класс CRectangle не переопределяет функцию Serialize, используется реализация функции базового класса.
Ниже представлено содержимое файла, в котором располагаются три объекта-прямоугольника и два объекта-линии в следующем порядке: прямоугольник, линия, прямоугольник, прямоугольник, линия. Одинарной чертой подчеркнуто описание типа объекта, а двойной — ссылка на него в виде однобайтового числа и символа 'А'. Информация о типе объекта содержит только имя соответствующего класса, по которому и происходит вызов нужного конструктора во время исполнения программы, причем только один раз, а далее тип определяется номером, указанным в' ссылке. Не правда ли, удобно?
0000: 05 00 00 00 FF FF.01 00 ОА 00 43 52 65 63 74 61 _...__...CRecta
0010: 6Е 67 6С 65 26 00 00 00 19 00 00 00 58 00 00 00 ngleS..._...X...
0020: 4В 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 К..._......._...
0030: 00 00 00 00 00 00 00 00 26 00 00 00 19 00 00 00 ........&... ...
0040: 58 00 00 00 4В 00 00 00 FF FF 01 00 05 00 43 4С X...К...__._.CL
0050: 69 6Е 65 26 00 00 00 19 00 00 00 58 00 00 00 4В ine&.._...X...К
0060: 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 ..._......._....
0070: 00 00 00 00 00 00 00 26 00 00 00 19 00 00 00 58 .......&..._...X
0080: 00 00 00 4В 00 00 00 01 80 26 00 00 00 19 00 00 ...К .=А&..._..
0090: 00 58 00 00 00 4В 00 00 00 01 00 00 00 00 00 00 .X...К.._......
ООАО: 00 01 00 00 00 00 00 00 00 00 00 00 00 26 00 00 ._...........&..
ООВО: 00 19 00 00 00 58 00 00 00 4В 00 00 00 01 80 26 ._...X...К.._A&
ООСО: 00 00 00 19 00 00 00 58 00 00 00 4В 00 00 00 01 ..._...X...К..._
OODO: 00 00 00 00 00 00 00 01 00 00 :00 -00 00:00 00 00 ....._.......
ООЕО: 00 00 00 26 00 00 00 19 00 ОО'ОО 58 00 00 00 4В .:•&..._..-X..К
OOFO: 00 00 00 03 80 26 00 00 00 19 00 00: 00 58 .00 00 ...A4&.._ X..
0100: 00 4В 00 00 00 01 00 00 00 00:00 00 00 01 00 00 .К..._......._..
ОНО: 00 00 00 00 00 00 00 00 00 26 ,00 00 00 19 00 00 .........&..._..
0120: 00 58 00 00 00 4В 00 00 00 -:. . :...Х..К.....
Закончив знакомство с документами, пора переходить к тому, каким образом можно взаимодействовать с хранящимися в них данными. Осуществить это взаимодействие и визуализацию данных документа призваны объекты специальных классов — представления.
Представления
Это специальная группа классов дочерних окон фрейма (рис. 4), которая отвечает за отображение данных документа и за взаимодействие с пользователем. В этом разделе мы представим возможности, предоставляемые для этих задач библиотекой MFC.
Представление ассоциируется с некоторым документом и действует как посредник между ним и пользователем. Другими словами, представление переводит образ документа на экран, принтер или любое другое устройство графического вывода и интерпретирует действия пользователя как операции над документом. Основным, на что здесь следует обратить внимание, является то, что представление может быть ассоциировано (или присоединено) только с одним документом, т. е. хотя оно и является окном, как, впрочем, почти все видимые объекты Windows, но при этом ориентировано на работу только с определенным набором данных. С другой стороны, в рамках одного фрейма документа можно создавать и использовать сколько угодно представлений для работы с одним и тем же документом.
Рис. 4. Место классов представлений в иерархии библиотеки MFC
Достаточно часто возникает желание иметь различные представления одного данного типа документа. Что имеется в виду? Например, при работе с текстовым процессором желательно иметь одно представление для текста документа, а другое (контурное) — для изображения области колонтитулов. Эти различные типы представлений могут размещаться как в различных фреймах, так и в различных областях одного и того же фрейма. В обоих случаях достаточно один раз сопоставить представление с документом, но организовать различную обработку данных документа (рис. 5).
Таким образом, представление отвечает за изображение и модификацию данных документа, но не имеет никакого отношения к их хранению. (Точнее, не должно иметь.) Необходимые подробности о различных параметрах этих данных хранятся вместе с самим документом, откуда либо само представление, либо специальные функции класса документа, обеспечивающие для того или иного представления доступ к ним, берут только необходимое.
Несколько слов о взаимодействии с пользователем. Реализация выполнения любых действий такого рода целиком ложится на соответствующий класс представления, а значит, и на программиста. (Никто не может предугадать всех ваших желаний, даже разработчики прекрасной библиотеки MFC.) Это может быть ввод с клавиатуры, действия мышью, выбор элемента меню, нажатие кнопки панели инструментов или работа с полосой прокрутки. Соответствующие команды представление получает от своего фрейма (правда, не напрямую, а опосредованно). Обработку сообщений оно осуществляет аналогично всем другим получателям команд — посредством карты сообщений. Если представление само не обрабатывает команду, то передает ее дальше "по маршруту", как описано в главе 5.
Последовательность действий по созданию представлений, выполняемая библиотекой MFC, представлена на рис. 6.
Теперь, после того как сложилось некоторое общее представление о назначении и возможностях этой группы классов, можно переходить к рассмотрению компонентов группы и свойств ее составляющих.
Рис. 5. Использование нескольких представлений для отображения данных одного документа
Рис. 6.Последовательность создания представления
В полном соответствии с общей идеологией построения библиотеки MFC на вершине иерархии классов представлений стоит единственный базовый класс — CView, обеспечивающий поддержку печати, общее взаимодействие с документом и некоторые другие возможности, которые будут описаны ниже. От него образованы еще два класса — CCtrlView и CScrollView, каждый из которых унаследовал все свойства базового класса и добавил свои специфические. Класс CCtrlView был добавлен в библиотеку MFC, начиная с версии 4.0, для того, чтобы можно было использовать в архитектуре "документ/представление" деревья (класс CTree View), списки (CListView), а также простейшие (CEditView) и расширенные (CRichEditView) элементы управления для редактирования текстов. Класс CScrollView добавляет к свойствам базового класса поддержку автоматической прокрутки документов, если данные документа, которые необходимо отобразить, не помещаются в одном окне.
Примечание
Помимо приведенных здесь классов представлений в библиотеке MFC реализованы также некоторые другие. Производный от CScrollView класс CFormView обеспечивает дополнительную поддержку диалоговых ресурсов, таких как кнопки и поля. Наибольшее применение этот класс находит при работе с базами данных, о чем свидетельствуют его производные классы: CDaoRecordView, COIeDBRecordView и CRecordView. Кроме того, в рассматриваемой версии добавился класс CHtmlView. Более подробно эти классы будут рассмотрены при обсуждении соответствующих вопросов.
Всего в библиотеке MFC реализованы двенадцать классов представлений, некоторые из них мы рассмотрим подробнее. Кроме того, здесь же будет рассмотрен класс CSplitterWnd, который хотя и не входит в группу классов представлений, но достаточно тесно с ними связан.
Класс CView
Этот класс предоставляет базовые функциональные возможности для всех классов представлений, которые есть 18 библиотеке или определяются пользователем. Рассмотрим основные функции Этого класса. Первым, как всегда, идет конструктор;
CView:: CView ()
Создает объект класса; вызывается, когда создается новый фрейм или окно разделяется на области. Никакой инициализации представления здесь не производится.
CDocument* CView::GetDocument ()
Позволяет получить указатель на/объект "документ", присоединенный к этому представлению. Если с ним не ассоциирован никакой документ, то возвращается NULL. Использование данного указателя предоставляет доступ к функциям соответствующего класса документа. Для каждого производного класса создается своя специфичная функция.
virtual void CView::OnlnitialUpdate ()
Вызывается библиотекой MFC после того, как представление первый раз присоединено к документу, но до его первоначального отображения. Реализация по умолчанию вызывает функцию CView::OnUp>date без какой-либо информации (Hint = 0 и pHint = NULL). Для проведения специальных инициализирующих действий необходимо переопределить эту функцию. Например, так:
void CNoteView: :OnInitialUpdate()
{
// Пусть библиотека MFC выполнит:за нас всю
// необходимую предварительную работу
CScrollView: :OnInitialUpdate();
// Создаем новый (пустой) абзац
CNoteDoc* pDoc = (CNoteDoc*)GetDocument();
CParagraph *pPar = pDoc->OnNewPar(ND_LEFT);
// Добавляем его в общий список абзацев
pDoc->m_listPar.AddTail (рРаг);
//Обновляем все изображения .данных документа
pDoc->UpdateAllViews (NULL, 0, this) ;
}
virtual void CView: :OnUpdate (
CView *pSender,
LPARAM IHint,
CObject *pHint)
Вызывается из CDocument::UpdateAllViews после того, как документ был модифицирован. Кроме того, она вызывается из функции CView::OnlnitialUpdate. Реализация по умолчанию помечает всю рабочую область как недействительную для ее перерисовки при получении следующего сообщения WM_PAINT- Если обновления требует не вся рабочая область, то следует переопределить эту функцию, посылая информацию об изменениях через параметры IHint и pHint. Параметр IHint определяет специальное значение, обычно 6итоаук> маску или перечисляемый тип, несущее информацию о характере изменений в документе. Параметр pHint определяет указатель на объект, производный от CObject, который хранит информацию об изменениях в документе. При переопределении этой функции можно воспользоваться функцией CObject/teK/ndOf Для получения типа объекта во время выполнения программы. Ёсли оба параметра Hint и pHint нулевые, то документ послал общее извещение об обновлении, Представление, получившее такое извещение или не сумевшее декодировать параметры, обновляет всю свою рабочую область. Параметр pSender идентифицирует представление, которое должно быть обновлено в соответствии С изменениями в документе
Примечание
Обратите внимание! Параметр pSender функции CDocumentUpdateAllViews, рассмотренной ранее, исключает представление, на которое указывает, из группы представлений, получающих сообщение об обновлении, а параметр pSender функции GVtewiOnUpdate, наоборот, идентифицирует представление, получившее это сообщение.
void CNoteView::OnUpdate(CView*, LPARAM, CObject *pHint)
{
if(pHint != NULL)
{
// Нам ли предназначено сообщение?
if(pHint->IsKindOf(RUNTIME_CLASS(CNoteView)))
{
// Здесь можно проводить любые действия,
// необходимые для инициализации представления CNoteView
}
}
// Обновляем все, хотя можно и ограничить размеры области
Invalidate(TRUE) ;
}
В приведенной выше функции можно выполнять непосредственно и "перерисовку" данных документа. Однако для этих целей предназначены другие функции. Здесь лучше просто описать (в координатах устройства) прямоугольную область, требующую перерисовки, и передать ее в функцию InvalidateRect, чтобы осуществить рисование при получении следующего сообщения WM_PAINT.
virtual void CView::OnActivateView (
BOOL bActivate,
CView *pActivateView,
CView *pDeactiveView)
Вызывается библиотекой MFC, когда представление активизируется или деактивизируется. По умолчанию функция устанавливает фокус на активизируемое представление. В качестве параметров она получает также указатели на активизируемое (параметр pActivateView) и деактивизируемое (параметр pDeactiveView) представления. Эти параметры указывают на одно и то же представление, если активизируется главное окно приложения без изменения активного представления, что можно использовать для изменения свойств представления. Однако эти параметры отличаются при переключении между различными представлениями одного и того же приложения (или дочерними окнами MDI-приложения). Наиболее часто это связано с "разделяемыми" (splitter) окнами.
virtual void CView::OnActivateFrame (
UINT nState,
CFrameWnd *pFrame'Wnd)
Вызывается в случае, когда активизируется или деактивизируется фрейм (параметр pFrameWnd), ассоциированный с текущим представлением. Характер действия определяется параметром nState, который может принимать одно и: следующих значений:
WA_INACTIVE
Фрейм деактивизируется
WA_ACTIVE
Фрейм активизируется любым способом, как от нажатия в нем кнопки мыши, так и от клавиатуры
WA_CLICKACTIVE
Фрейм активизируется путем нажатия кнопки мыши в его границах
virtual BOOL CView::OnScroll (
UINT nScrollCode,
UINT nPos,
BOOL bDoScroll)
Вызывается библиотекой MFC для определения возможности прокрутки. Параметр bDoScroll определяет, нужно ли осуществлять прокрутку. Если bDoScroll = TRUE, то, когда представление получает сообщение о прокрутке, ее действительно нужно осуществить. Если же bDoScroll = FALSE, то при перенесении элемента OLE в область автопрокрутки осуществлять саму прокрутку представления не нужно. Параметр nScrollCode определяет код прокрутки и состоит из двух частей: младший байт задает код прокрутки по горизонтали, а старший — по вертикали. Он может принимать одно из следующих значений:
SB_BOTTOM
Прокрутка до самого низа
SB_TOP
Прокрутка до самого верха
SB_INEDOWN
Прокрутка вниз на одну линию (строку)
SB_INEUP
Прокрутка вверх на одну линию (строку)
SB_PAGEDOWN
Прокрутка вниз на одну страницу
SB_PAGEUP
Прокрутка вверх на одну страницу
SB_THUMBTRACK
Перенос ползунка полосы прокрутки в определенную позицию, указанную в параметре nPos
Функция возвращает TRUE, если прокрутка действительно осуществлена; в противном случае — FALSE.
virtual BOOL CView::OnScrollBy (
CSize sizeScroll,
BOOL bDoScroll)
Вызывается библиотекой MFC, когда курсор мыши находится вне области, изображаемой представлением документа, что происходит или при переносе элемента OLE к границе текущего представления, или при манипуляциях горизонтальной или вертикальной полосами прокрутки. Реализация по умолчанию не делает ничего. В производных классах функция проверяет, видимо ли представление, прокручиваемое в направлении, запрошенном пользователем, и, при необходимости, обновляет новый регион. Она автоматически вызывается функциями CWnd::OnHScroll и CWnd::OnVScroll для выполнения прокрутки. Параметр sizeScroll определяет число пикселов (по горизонтали и по вертикали), на которое осуществляется прокрутка. Функция возвращает TRUE, если представление может быть прокручено, и FALSE — в противном случае.
virtual void CView::OnDraw (CDC *pDC) = 0
Чисто виртуальная функция, которая используется для изображения образа документа. Следует обязательно переопределить ее для изображения представления документа, что и сделано в производных классах. Библиотека MFC использует эту функцию как для печати (и предварительного просмотра) документа, так и для отображения его на экране. Это основная и единственная функция для изображения видимого образа документа. От того, каким образом она реализована, зависит, что увидит пользователь на экране или после печати на принтере.
void CDrawView::OnDraw(CDC* pDC)
{
CRect *pLine;
// Поскольку отобразить нужно'Данные документа,
//то получаем указатель на него
CDrawDoc *pDoc = (CDrawDoc*)GetDocument();
// Мы могли нарисовать несколько объектов,
// и все их нужно в
<== предыдущая лекция | | | следующая лекция ==> |
Квантовый гармонический осциллятор | | | с биотканями. КЛАССИФИКАЦИЯ МЕДИЦИНСКИХ ПРИМЕНЕНИЙ ЛАЗЕРОВ |
Дата добавления: 2017-01-26; просмотров: 1696;