Глава 18.


Документ и его представления
   

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

 Примечание 

Точнее, архитектура позволяет это сделать, и мы рекомендуем к этому стремиться.

Любые изменения данных осуществляются с использованием специального класса документа, который обеспечивает необходимое пространство для их хранения в памяти, отвечает за запись и чтение документа с диска и предоставляет интерфейс для доступа и обновления данных. За изображение данных на экране, принтере или любом другом устройстве отвечает представление, которое использует для этого класс CView или производный от него. Можно сказать, что объект этого класса представляет собой окно, посредством которого, с одной стороны, осуществляется взаимодействие с пользователем, а с другой — организуется доступ к интерфейсной части документа для возможности обновления данных. В достаточно упрощенном виде сказанное можно проиллюстрировать с помощью рис. 18.1. Следует также повторить, что один и тот же документ одновременно может иметь несколько различных представлений.

Таким образом, действуя совместно, документ и его представления: 

Рассмотрим основные компоненты архитектуры "документ/представление" подробно. Начнем с документов.

Рис. 18.1. Взаимоотношения документа и представления

 

Документы

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

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

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

class CNoteDoc : public CDocuinent 

{

...

// Данные текстовых документов

public:

// Список указателей на объекты класса CParagraph,

// содержащего параметры форматирования абзаца текста

CTypedPtrList<CObList, CParagraph*> m_listPar;

...

}; 

Рис. 18.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 для реализации процесса сериализации — мы еще вернемся, а пока рассмотрим "цикл жизни" документа в рамках архитектуры "документ/представление":

Перечисленные шаги характерны для MDI-приложений. При работе с SDI-приложениями первый шаг выполняется единственный раз — при первом создании документа, а последний — когда приложение завершает свою работу.

 

Класс CDocument

Этот класс предоставляет базовые функциональные возможности для классов документов, определенных пользователем (рис. 18.3). Он поддерживает все стандартные операции, такие как создание документа, его загрузка и сохранение. Библиотека MFC работает с документами, используя интерфейс, определенный в CDocument.

Рис. 18.3. Место класса CDocument в иерархии библиотеки MFC

Объекты класса CDocument являются частью стандартного маршрута команд и поэтому получают команды от стандартных компонентов интерфейса пользователя. Документ получает команды раньше активного представления и если сам не обрабатывает некоторую команду, то передает ее на обработку своему шаблону.

Рассмотрим некоторые функции этого класса.

CDocument::CDocument ()

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

CDocTemplate* CDocument::GetDocTemplate ()

Возвращает указатель на объект-шаблон для документа этого типа. Если документ этого типа не поддерживается каким-либо шаблоном, то возвращается NULL.

void CDocument::AddView (CView *pView)

Присоединяет представление pViewK документу, добавляя его к общему списку представлений, ассоциированных с этим документом. Помимо этого, функция устанавливает указатель m_pDocument класса CView на этот документ. Вызывается библиотекой MFC при создании нового представления документа, обычно обработчиками команд ID_FILE_NEW, ID_FILE_OPEN, ID_WINDOW_NEW или при разбиении разделяемого окна.

void CDocument::RemoveView (CView *pView)

Отсоединяет представление pViewoj документа и удаляет его от общего списка, представлений, ассоциированных с этим документом. Указатель 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 для каждого представления (за исключением определяемого параметром pSendei) и передачи им информацию об изменениях в документе. Эта информация может быть представлена каким-либо кодом (параметр IHinf) и/или содержаться в объекте (параметр pHint).

void CNoteView::OnPageSetup()

{

...

// После того как были изменены параметры страницы, 

// необходимо обновить изображение данных на экране .

CNoteDoc* pDoc = (CNoteDoc*)GetDocument();

 // Сообщение об обновлении передается всем представлениям, 

// ассоциированным с документом, но реально предназначается 

// только объекту класса, на который указывает this

pDoc->UpdateAHViews (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);

 // Освобождаем память, занимаемую текущим абзацем.

 // Если этого не сделать, то для системы память 

// будет потеряна, хотя Windows 95/98 и Windows NT

 //в большинстве случаев "уберут" за вами 

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 для записи в файл данных документа и помечает его как немодифицированный.

Еще раз внимательно посмотрите на рис. 18.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 CGraphicsView : public CScrollView

 { 

protected: // вызывается только во время сериализации

CGraphicsView();

DECLARE_DYNCREATE(CGraphicsView)

...

public:

virtual void Serialize(CArchiveS ar);

...

private:

CTypedPtrList <CPtrList, CDrawObject *> m_List;

 } ;

CDrawObj ect::CDrawObj ect() 

 {

m_Selected = FALSE; 

}

CDrawObject::CDrawObject(BOOL Selected) 

{

m_Selected = Selected; 

}

CDrawObj ect::-CDrawObj ect() 

 }

void CDrawObject::Serialize(CArchiveS ar) 

{

if (ar. JsStoring() )

{

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 CGraphicsView::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 -:. . :...Х..К.....

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

 

Представления

Это специальная группа классов дочерних окон фрейма (рис. 18.4), которая отвечает за отображение данных документа и за взаимодействие с пользователем. В этом разделе мы представим возможности, предоставляемые для этих задач библиотекой MFC.

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

gl18-4.jpg

Рис. 18.4. Место классов представлений в иерархии библиотеки MFC

Достаточно часто возникает желание иметь различные представления одного данного типа документа. Что имеется в виду? Например, при работе с текстовым процессором желательно иметь одно представление для текста документа, а другое (контурное) — для изображения области колонтитулов. Эти различные типы представлений могут размещаться как в различных фреймах, так и в различных областях одного и того же фрейма. В обоих случаях достаточно один раз сопоставить представление с документом, но организовать различную обработку данных документа (рис. 18.5).

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

Несколько слов о взаимодействии с пользователем. Реализация выполнения любых действий такого рода целиком ложится на соответствующий класс представления, а значит, и на программиста. (Никто не может предугадать всех ваших желаний, даже разработчики прекрасной библиотеки MFC.) Это может быть ввод с клавиатуры, действия мышью, выбор элемента меню, нажатие кнопки панели инструментов или работа с полосой прокрутки. Соответствующие команды представление получает от своего фрейма (правда, не напрямую, а опосредованно). Обработку сообщений оно осуществляет аналогично всем другим получателям команд — посредством карты сообщений. Если представление само не обрабатывает команду, то передает ее дальше "по маршруту", как описано в главе 5.

Последовательность действий по созданию представлений, выполняемая библиотекой MFC, представлена на рис. 18.6.

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

gl18-5.jpg

Рис. 18.5. Использование нескольких представлений для отображения данных одного документа

gl18-6.jpg

Рис. 18.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: :OnInitialUpdat&()

// Пусть библиотека MFC выполнит:за нас всю

 // необходимую предварительную работу 

 CScrollView: :OnInitialUpdat.e();

// Создаем новый (пустой) абзац 

CNoteDoc* pDoc = (CNoteDoc*)G.etpQCtanent ();

 CParagraph *pPar = pDoc->OnNewPar(ND_LEFT);

 // Добавляем его в общий список абзацев 

pDoc->m_listPar.AddTail (рРаг); 

//Обновляем все изображения .данных документа

pDoc->UpdateAHViews (NULL, /0, this) ; 

 

virtual void CView: :OntJpdate (

CView *pSender, 

 LPARAM IHint, 

CObject *pHint)

Вызывается из CDocument::UpdateAIIViews после того, 'как документ был модифицирован. Кроме того, она вызывается из функции CView::OnlnitialUpdate. Реализация по умолчанию помечает всю рабочую область как недействительную для ее перерисовки при получении следующего сообщения WM_PAINT- Если обновления требует не вся рабочая область, то следует переопределить эту функцию, посылая информацию об изменениях через параметры IHint и pHint. Параметр IHint определяет специальное значение, обычно 6итоаук> маску или перечисляемый тип, несущее информацию о характере изменений в документе. Параметр pHint определяет указатель на объект, производный от CObject, который хранит информацию об изменениях в документе. При переопределении этой функции можно воспользоваться функцией CObject/teK/ndOf Для получения типа объекта во время выполнения программы. Ёсли оба параметра Hint и pHint нулевые, то документ послал общее извещение об обновлении, Представление, получившее такое извещение или не сумевшее декодировать параметры, обновляет всю свою рабочую область. Параметр pSender идентифицирует представление, которое должно быть обновлено в соответствии С изменениями в документе

 Примечание 

Обратите внимание! Параметр pSender функции CDocumenfcUpdateAIIViews, рассмотренной ранее, исключает представление, на которое указывает, из группы представлений, получающих сообщение об обновлении, а параметр 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, когда представление активизируется (<b/4cf/Vafe=TRUE) или деактивизируется. По умолчанию функция устанавливает фокус на активизируемое представление. В качестве параметров она получает также указатели на активизируемое (параметр 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();

// Мы могли нарисовать несколько объектов,

// и все их нужно вывести на экран, поэтому организуем цикл

for(int i = 0; i< pDoc->m_lineArray.GetSize(); i++)

{

// Получаем, координаты текущей линии 

 pLine = pDoc->m_lineArray[i];

// Выбираем в контекст устройства, которым может быть 

// как экран, так и принтер, текущий "карандаш"

 CPen *pOld = pDC->SelectObject (pDoc->GetPen ());

// Выводим текущую линию на экран

pDC->MoveTo(pLine->left, • pLine->top);

pDC->LineTo(pLine->right, pLineJ>bottom);

 // Восстанавливаем параметры старого "карандаша" 

pDC->SelectObject(pOld);

 } 

}

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

 

Класс CCMVtew

Это базовый класс для группы классов элементов управления, включающей в себя CEditView, CListtiew, CRichEtiitViewvi CTreeVlew, которые специально адаптированы к архитектуре "документ/Представление". Его назначение во многом напоминает назначение класса CControlBar для панелей элементов управления. Для самого класса CCtrlView приведем две общедоступные переменные и конструктор: 

CString CCtrlView: :m_strClass

Содержит имя класса окна Windows для класса представления.

DWORD CCtrlView::m_dwDefaultStyle

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

CCtrlView::CCtrlView (

LPCTSTR IpszClass,

DWORD dwStyle) 

Создает объект класса. Этот конртруктор вызывается библиотекой MFC при создании нового фрейма или разбиении окна на области. Для инициализации представления после его присоединения к соответствующему документу необходимо переопределить функцию CView::OnlnHialUpdate, а для создания объекта Windows следует использовать функцию CWnd::Create или CWnd::CreateEx. Передаваемые в конструктор параметры определяют имя класса Windows — IpszClass и используемый по умолчанию стиль — dwStyle и записывают эти значения в соответствующие переменные класса.

Помимо перечисленных членов класса, в нем еще переопределены функции OnDraw и PreCreateWindow, а также реализован обработчик OnPaint сообщения WM_PAINT.

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

 

Класс CEditView

Этот класс определяет представление, которое, подобно классу CEdit, инкапсулирует функциональные возможности элемента управления EDIT и может применяться для реализации простейшего текстового редактора. При этом, поскольку класс создан на базе CView, его объекты могут использоваться с документами и шаблонами документов, как и любое другое представление. Помимо тех возможностей, которые он унаследовал от своих "родительских" классов, в нем дополнительно реализована поддержка функций поиска и замены фрагментов текста, а также обработка стандартных команд ID_EDIT_SELECT_ALL, ID_EDIT_REPLACE, ID_EDIT_REPEAT, ID_FILE_PRINT и ID_EDIT_FIND.

Присущие элементу управления EDIT ограничения в полной мере относятся и к рассматриваемому классу. Объект класса CEditView(или производного от него) имеет следующие ограничения:

Теперь, после небольшого обзора возможностей класса, рассмотрим его основные члены.

static AFX_DATA const DWORD CEciitView::dwStyleDefault

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

CEditView::CEditView ()

Конструктор, создает объект класса. После создания объекта, но до использования, элемента управления EDIT необходимо вызвать функцию CWnd::Create для создания окна Windows. Если на базе этого класса определен производный, который добавлен в шаблон документа при помощи функции CWinApp:: AddDocTemplate, то библиотека сама вызывает и конструктор, и функцию Create.

Рассматриваемый класс до появления 32-разрядных версий Windows являлся одним из наиболее часто используемых и потому достаточно полно адаптирован для различных режимов работы. В нем реализована поддержка печати, предварительного просмотра, установка величины табуляции и многое другое.

Примечание 

Он же является ярким примером нарушения принципа разделения данных документа и их изображения.

CFont* CEditView::GetPrinterFont ()

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

void CEditView::SetPrinterFont (CFont* pFont)

Устанавливает для принтера шрифт, определяемый указателем pFont на объект класса CFont. Данную функцию следует вызывать из функции OnPreparePrinting для того, чтобы установка нового шрифта произошла до начала печати содержимого представления. Используется в случае, когда для принтера нужен особый шрифт. Если параметр pFont равен NULL, то шрифт, используемый для печати, основывается на экранном шрифте.

UINT CEditView::PrintlnsideRect (

 CDC *pDC, 

RECT &rectLayout, 

UINT nlndexStart, 

UINT nlndexStop)

Печатает текст в прямоугольнике, заданном в параметре rectLayout. Если у объекта класса не установлен стиль ES_AUTOHSCROLL, то текст переносится на следующую строку внутри прямоугольника. В противном случае текст отсекается по его правой границе. Поле rectLayout.bottom изменяется так, что размеры определяют ту часть начального прямоугольника, которую занимает текст. Параметры nlndexStart и nlndexStop показывают, соответственно, индексы первого и следующего за последним полученных символов относительно начала буфера. Функция возвращает индекс следующего символа, который будет печататься.

void CEditView::GetSelectedText (CString SstrResult)

Копирует выделенный текст или символы, предшествующие первому символу "возврат каретки", в объект класса CString, на который ссылается параметр strResult.

LPCTSTR CEditView::LockBuffer ()

Блокирует буфер элемента управления EDIT в определенном месте памяти, запрещая его модификацию и перемещение, и возвращает указатель на него.

void CEditView::UnlockBuffer ()

Разрешает модификацию и перемещение буфера, заблокированного функцией LockBuffer.

void CEditView::SerializeRaw (CArchive &ar)

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

Еще одна группа функций облегчает работу с блоком диалога Find/Replace (Найти и Заменить), выводимым на экран в ответ на стандартные команды ID_EDIT_FIND и ID_EDIT_REPLACE, и предоставляет программистам обработчики команд кнопок Find (Найти), Replace (Заменить) и Replace All (Заменить все).

BOOL CEditView::FindText (

LPCTSTR IpszFind,

BOOL bNext = TRUE,

BOOL bCase = TRUE)

Осуществляет поиск текста, заданного параметром IpszFind, в буфере элемента управления, начиная с текущей позиции и в направлении, определяемом параметром bNex: если он имеет значение TRUE, то поиск осуществляется от начала к концу буфера, в противном случае — наоборот. Параметр bCase определяет, следует ли различать строчные и прописные буквы при поиске (TRUE — учитывать регистр, FALSE — не учитывать). Если в результате проведенного поиска текст не найден, то функция возвращает FALSE. Обычно она вызывается из переопределенной функции OnFindText.

virtual void CEditView::OnFindText (

LPCTSTR IpszFind,

BOOL bNext = TRUE,

BOOL bCase = TRUE)

Аналогична предыдущей функции, однако вызывается в ответ на нажатие кнопки Find Next (Следующий) в стандартном блоке диалога Find (Поиск), выводимом на экран в ответ на выбор команды ID_EDIT_FIND. По умолчанию вызывает функцию FindText. Если текст не найден, то вызывается функция OnTextNotFound.

virtual void CEditView::OnReplaceAll

LPCTSTR IpszFind, 

LPCTSTR IpszReplace, 

BOOL bCase)

Вызывается, когда пользователь выбирает кнопку Replace All в стандартном блоке диалога Replace (Замена). Функция ищет в буфере текст, заданный параметром IpszFind, начиная с текущей позиции, и при нахождении заменяет его на другой, определенный в IpszReplace. Параметр bCase задает чувствительность к регистру. Поиск осуществляется через вызов функции FindText. В случае, если заданный текст не найден, вызывается функция OnTextNotFound. Для выполнения каких-либо специфических действий эту функцию следует переопределить.

virtual void CEditView::OnReplaceSel (

LPCTSTR IpszFind,

BOOL bNext,

BOOL bCase,

LPCTSTR IpszReplace)

Вызывается, когда пользователь выбирает кнопку Replace (Заменить) в стандартном блоке диалога Replace (Замена). После замены выделенного текста функция ищет следующий фрагмент, совпадающий с определяемым параметром IpszFind, в направлении, определяемом параметром bNext. Сам поиск осуществляется посредством функции FindText. В случае, если заданный текст не найден, вызывается функция OnTextNotFound. Для выполнения каких-либо специфических действий эту функцию следует переопределить.

virtual void CEditView::OnTextNotFound (LPCTSTR IpszFind)

Вызывается библиотекой MFC, если текст, определенный в параметре IpszFind, не найден. По умолчанию вызывает функцию Windows MessageBeep. Если нужны какие-либо другие действия, функцию следует переопределить.

Работа с представлением, реализованным на базе класса CEditView, практически аналогична работе с элементом управления EDIT. Поэтому не будем останавливаться на этом классе более подробно, а в качестве примера работы с классом рассмотрим фрагменты кода приложения NoteDraw, которые демонстрируют, каким образом осуществляется организация режимов поиска и замены в рассматриваемом представлении. На рис. 18.7 и 18.8 представлен внешний вид блоков диалога Find и Replace в режимах поиска и замены, соответственно.

Прежде всего необходимо зарегистрировать новое сообщение.

// Для работы с блоком диалога Find или Replace 

// при регистрации сообщения необходимо использовать

 // строковую константу FINDMSGSTRING

 static const UINT nNewMsg =

::RegisterWindowMessage(FINDMSGSTRING);

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

gl18-7.jpg

Рис. 18.7. Стандартный блок диалога Find

gl18-8.jpg

Рис. 18.8. Стандартный блок диалога Replace

BEGIN_MESSAGE_MAP(CTextView, CEditView)

//{{AFX_MSG_MAP(CTextView)

ON_COMMAND(ID_EDIT_FIND, OnEditFind)

 ON_COMMAND(ID_EDIT_REPLACE, OnEditReplace)

 //}}AFX_MSG_MAP 

 ON_REGISTERED_MESSAGE (nNewMsg, OnNewCmd)

  END_MESSAGE_MAP()

И, наконец, в реализацию класса необходимо добавить обработчики команд:

void CTextView::OnEditFind() 

{

// Создаем объект "блок диалога"

CFindReplaceDialog *pDlg = new CFindReplaceDialog;

bFindOnly = TRUE;

// Создаем, объект Windows для блока диалога Find

pDlg->Create(TRUE, NULL, NULL, FR_HIDEWHOLEWORD [ FR_DOWN, this);

}

void CTextView::OnEditReplace() 

// Создаем объект "блок диалога"

CFindReplaceDialog *pDlg = new CFindReplaceDialog;

bFindOnly = FALSE;

// Создаем объект Windows для блока диалога Replace

pDlg->Create(FALSE, NULL, NULL, FRJTOWHOLEWORD [ FR_DOWN, this); 

}

LRESULT CTextView::OnNewCmd(WPARAM, LPARAM IParam) 

{

// Получаем указатель на объект "стандартный блок диалога"

// Find/Replace

CFindReplaceDialog* pDialog =

CFindReplaceDialog::GetNotifier(IParam);

if (pDialog->IsTerminating())

{

// Если пользователь завершил работу по поиску/замене, то

// сообщаем библиотеке, что объект "блок диалога"

// больше не нужен

pDialog = NULL;

 }

else if (pDialog->FindNext()) 

{

// Получаем строку, которую требуется найти

CString strFind = pDialog->GetFindString();

// Осуществляем поиск.

// Параметры поиска получаем прямо из блока диалога

OnFindNext(strFind,

pDialog->SearchDown(), pDialog->MatchCase()); 

}

else if (pDialog->ReplaceCurrent()) 

{

// Сюда вход разрешен только для режима замены

ASSERT(IbFindOnly) ;

// Пользователь нажал кнопку Replace: требуется

// найти определенную строку и заменить ее на заданную.

// Параметры получаем прямо из блока диалога

OnReplaceSel(pDialog->GetFindString(),

pDialog->SearchDown(), pDialog->MatchCase(), pDialog->GetReplaceString() ) ; 

}

else if (pDialog->ReplaceAll())

 {

ASSERT(IbFindOnly);

// Пользователь нажал кнопку Replace All: 

// требуется найти все вхождения определенной строки 

//и заменить их на заданную. 

// Параметры получаем прямо из блока диалога

 OnReplaceAll(pDialog->GetFindString() ,

pDialog->GetReplaceString(), 

pDialog->MatchCase()) ; 

}

return 0; 

}

void CTextView::OnTextNotFound(LPCTSTR IpszFind)

 {

// Если искомая строка не найдена, то выводим 

// сообщение об этом AfxMessageBox("Текст не найден"); 

}

 

Класс CScrollView

Согласитесь, что достаточно редко нам "хватает" одного окна для отображения всей необходимой информации. Гораздо чаще мы вынуждены организовать либо прокрутку документа, либо изменение его масштаба, либо чередовать и то, и другое. Если базироваться на классе CView, то решение такой задачи целиком ложится на плечи программиста: необходимо самому обрабатывать сообщения от полос прокрутки, используя для этого функции CWnd::OnHScroll и CWnd::OnVScroll. Во избежание такой достаточно типичной ситуации в библиотеке MFC реализован специальный класс — CScrollView, который берет на себя всю работу по поддержке автоматической прокрутки и масштабирования. При таком подходе на вашу долю остается совсем немного: нужно рассчитать величину прокрутки, основываясь на размере всего документа, и из переопределенной функции CView::OnInitialUpdate или CView:: OnUpdate вызвать функцию SetScrollSizes. Ее вызов устанавливает режим отображения для представления, его полный размер и величины горизонтальной и вертикальной прокрутки. Все размеры даются в логических единицах, которые обычно рассчитываются исходя из данных, хранящихся в документе.

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

 Примечание 

Нельзя использовать функции SetScaleToFitSize и SetScrollSizes одновременно.

Полоса прокрутки в представлении появляется в трех случаях:

Рассмотрим основные члены класса CScrollView.

CScrollView::CScrollView ()

Создает объект класса. До использования этого объекта необходимо вызвать одну из функций — SetScrollSizes или SetScaleToFitSize.

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

void CScrollView::SetScrollSizes (

int nMapMode,

SIZE sizeTotal,

const SIZE SsizePage = sizeDefault,

const SIZE SsizeLine = sizeDefault)

Функция служит для регулировки характеристик прокрутки текущего представления. Параметр nMapMode задает текущий режим отображения и может принимать одно из следующих значений: ММ_ТЕХТ, MM_HIMETRIC, MM_TWIPS, MM_HIENGLISH, MM_LOMETRIC и MMJ.OENGLISH. Общие размеры представления определены в sizeTotal. Они могут быть как фиксированными, так и зависеть от размера ассоциированного документа. Величина вертикальной и горизонтальной прокрутки "на страницу" задается параметром sizePage, а "на строку" — параметром sizeLine. Обычно функция вызывается из функции OnUpdate или OnlnitialUpdate.

void CNoteView::OnUpdate(CView*, LPARAM, CObject *pHint) 

{

// Если определено представление, требующее перерисовки

 if(pHint != NULL)

//и оно является объектом класса CNoteView ...

 if(pHint->IsKindOf(RUNTIME_CLASS(CNoteView))) 

{

// ... изменяем размеры этого представления

CNDApp *pApp = (CNDApp*)AfxGetApp();

// Параметры размера бумаги хранятся у приложения

//и надо получить доступ к ним

CSize sizeTotal;

// Формируем размеры области вывода, учитывая

// как размер бумаги, так и поля

sizeTotal.ex = (pApp->m_sizePaper.cx.-

pApp->m_rtMargin.right -

pApp->m_rtMargin.left - 120); 

sizeTotal.су = (pApp->m_sizePaper.cy -

pApp->m_rtMargin.bottom -

pApp->m_rtMargin.top — 120);

// В соответствии с текущими размерами бумаги и полей 

// устанавливаем новые параметры прокрутки

  SetScrollSizes(MM_LOMETRIC, sizeTotal); 

}

// Обновляем все, хотя можно и ограничить размеры области 

Invalidate(TRUE); 

}

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

CSize CScrollView::GetTotalSize ()

Позволяет узнать полные текущие горизонтальный и вертикальный размеры (в логических единицах) прокручиваемого представления,

 и

void CScrollView::GetDeviceScrollSizes (

int snMapMode,

SIZE ssizeTotal,

SIZE SsizePage,

SIZE SsizeLine)

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

CClientDC dc(this); 

и преобразовать единицы

dc.DPtoLP(sizeTotal);

Кроме представленных, в классе реализованы знакомые по работе с элементом управления SCROLL BAR функции получения и установки позиции ползунка полосы прокрутки.

CPoint CScrollView::GetDeviceScrollPosition ()

И

CPoint CScrollView::GetScrollPosition ()

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

void CScrollView: :ScrollToPosition .(POINT pt)

Устанавливает прокрутку в заданную позицию представления. При этом поле pt.sx должно быть больше или равно нулю, но меньше полного размера представления. Это же справедливо для поля ptsy, если режим отображения установлен в ММ_ТЕХТ. В противном случае эта величина должна быть отрицательной. Если работа с представлением осуществляется в режиме масштабирования, то функцию вызывать нельзя.

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

void CScrollView::SetScaleToFitSize (SIZE sizeTotal)

Осуществляет автоматическое масштабирование области вывода представления относительно размера текущего окна. При использовании прокрутки только часть логического представления может быть видима в текущий момент времени. При использовании масштабирования представление не имеет полос прокрутки, и его логический размер изменяется так, чтобы точно совпасть со всей рабочей областью окна. Если окно изменяет размер, то промасштабированное представление отображает свои данные в новой области (рис. 18.9) . Это наиболее часто используется для реализации операции "Zoom to fit".

Обе функции SetScrollSites и SetScaleToFitSize рекомендуется вызывать в обработчике OnlnitialUpdate. Очевидно, что перед их использованием должны быть установлены размеры самого окна представления, например, следующим образом:

GetParentFrame()->RecalcLayout() ;

Для согласования размеров фрейма документа и представления применяется функция (рис. 18.10-18.12):

void CScrollView::ResizeParentToFit (BOOL bShrinkOnly = TRUE)

Приводит размеры родительского фрейма в соответствие с размерами представления. Характер изменения размера определяется параметром bShrinkOnly. Если его значение TRUE, то родительский фрейм сжимается до размеров представления; в противном случае представление подгоняется точно под размер фрейма, что может иметь неожиданные последствия в случае с фреймом MDI-приложения или с экраном.

Рис. 18.9. Разделенное окно представления в режиме масштабирования

Рис. 18.10. Фрейм документа до выполнения функции ResizeParentToFit

Рис. 18.11. Фрейм документа после выполнения функции ResizeParentToFit( FALSE)

Рис. 18.12. Фрейм документа после выполнения функции ResizeParentToFif(TRUE)

 

Класс CSplitterWnd

Прокрутка позволяет работать с документом, размер которого больше, чем можно отобразить в окне, при этом большая часть документа скрыта от пользователя. Вторым вариантом работы с большим документом, который мы также рассматривали, является масштабирование. Но если размеры документа достаточно велики, то такой способ хорош только для получения обшей информации об его структуре, т. к. при этом на экране трудно рассмотреть его содержимое. Для того чтобы пользователь мог одновременно просматривать различные части документа, в Windows используется так называемое "разделенное окно", которое представляет собой одно окно, разбитое на несколько областей. Такое окно содержит "блок разделителя" (splitter box) в верхней части вертикальной и левой части горизонтальной полос прокрутки, двойной щелчок на котором приводит к разделению области, соответственно, на вертикальные или горизонтальные внутренние области. Сами области отделены друг от друга специальной полосой разделения (splitterbar), которая служит также для перераспределения площади окна между соседними областями. Пересечение горизонтальной и вертикальной полос прокрутки (splitter intersection) позволяет одновременно регулировать и вертикальный, и горизонтальный размеры областей.

Для поддержки разделенных окон в библиотеке MFC реализован специальный класс CSplitterWnd. Каждая область представляет собой отдельное окно, которое управляется объектом CSplitterWnd и обычно является определенным объектом приложения, производным от CView (или его производного класса), но может быть и произвольным объектом CWnd, имеющим соответствующий идентификатор дочернего окна. При этом класс, на основе которого создается область, должен использовать макросы DECLARE_ DYNCREATE и IMPLEVENT_DYNCREATE для поддержки режима динамического создания. Очевидно, что для областей гораздо легче (и лучше) использовать классы представлений, чем "обычные" оконные классы. В зависимости от того, каким образом — по горизонтали или по вертикали, разбито окно, для соответствующих областей используются названия "строка" и "столбец". Исходное окно при этом идентифицируется как "нулевая строка" и "нулевой столбец".

Класс CSplitterWnd поддерживает два различных стиля разделенных окон: статические и динамические.

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

Рис. 18.13. Статическое разделенное окно с двумя горизонтальными областями

В динамических разделенных окнах (рис. 18.14) дополнительные области могут создаваться и разрушаться в произвольные моменты времени. Эти окна создаются с единственным представлением, и полосы разделителей предоставляют начальное разделение. Если представление разделено в одном направлении, то дополнительный объект представления динамически создается в представленной новой области. Если представление разделяется в двух направлениях, то три новых представления создаются в трех новых областях. Когда разделитель активен, блок разделителя изображается в виде разделительной полосы между областями. Дополнительные объекты представления разрушаются, когда пользователь удаляет разделитель, но начальное представление (нулевая строка, нулевой столбец) остается, пока не будет разрушено его окно.

Рис. 18.14. Динамическое разделенное окно с двумя горизонтальными и двумя вертикальными областями

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

В обоих случаях число областей, на которые можно разделять окно, ограничено: для статических разделяемых окон — 16 строк и 16 столбцов; для динамических — 2 строки и 2 столбца. Одна из многочисленных рекомендаций фирмы Microsoft касается и конфигураций разделенных окон:

Строка/Столбец

Рекомендации фирмы Microsoft

Для статических окон

1 строка х 1 столбец 

2 строки х 1 столбец 

2 строки х 2 столбца

 

Для областей, основанных на различных объектах

Для областей, основанных на одинаковых объектах

Для динамических окон

1 строка х 2 столбца 

2 строки х 1 столбец 

2 строки х 2 столбца

 

Для данных, расположенных в колонках 

Для текстовых или других данных 

Для сеточных номограмм и табличных данных

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

Как и многие другие окна Windows, окно класса CSplitterWnd может иметь полосы прокрутки, которые определяются либо указанием соответствующего стиля окна при его создании — WS_HSCROLL и/или WS_VSCROLL, либо могут представлять собой дочерний элемент управления. В этом плане они ничем не отличаются от других типов окон. Но наряду с этим класс CSplitterWnd поддерживает специальный тип полос прокрутки, которые могут использоваться совместно различными областями. В этом случае при перемещении пользователем ползунка полосы прокрутки соответствующее сообщение посылается во все области.

Полосы прокрутки формируются при создании разделяемого окна. Например, объект класса CSplitterWnd, имеющий одну строку, два столбца и стиль WS_VSCROLL, изображает вертикальную полосу прокрутки, которая используется совместно двумя областями, и сообщение WM_VSCROLL посылается в обе.

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

Обычно объект класса CSplitterWnd внедряется в свой родительский объект класса CFrameWnd для SDI- и CMDIChildWnd для MDI-приложений. В этом случае вызывается конструктор класса CSplitterWnd::CSplitterWnd ().

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

BOOL CSplitterWnd::Create ( 

CWnd *pParentWnd, 

int nMaxRows,

 int nMaxCols, 

SIZE sizeMin, 

CCreateContext *pContext, 

DWORD dwStyle = WS_CHILD [ WS_VISIBLE [ WS_VSCROLL [ WS_HSCROLL ]

SPLS_DYNAMIC_SPLIT, 

UINT nID = AFX_IDW_PANE_FIRST)

Создает динамическое разделяемое окно для создания и прокрутки определенного числа индивидуальных областей (или представлений) того же самого документа. Все области, используемые в таком окне, должны быть одного класса. При успешном создании окна Windows функция возвращает TRUE, в противном случае— FALSE. В качестве параметров она использует: pParentWnd — указатель на родительский фрейм; nMaxRows и nMaxCols — максимальное число строк и столбцов, которое может иметь разделенное окно (оба эти параметра не могут быть больше 2); sizeMin— минимальная высота строки или ширина столбца, при котором область будет изображаться на экране; pContext— указатель на структуру CCreateContext, который в большинстве случаев является соответствующим параметром родительского фрейма или может быть равен NULL; nID— идентификатор дочернего окна, может иметь значение, указанное по умолчанию, если это окно не используется внутри другого разделяемого окна; dwStyle — стиль окна, в дополнение к стандартным оконным стилям используется SPLS_DYNAMIC_SPLIT, что указывает на создание динамического разделяемого окна.

BOOL CDrawFrame::OnCreateClient(LPCREATESTRUCT Ipcs, 

 CCreateContext* pContext) 

{

// Создаем динамическое разделяемое окно с двумя строками 

//и двумя столбцами, устанавливая некоторые начальные 

// минимальные размеры, чтобы сначала представление занимало

 // все разделяемое окно 

return m_splitWnd.Create(this, 2, 2,

CSizeCLO, 10), pContext); 

}

BOOL CSplitterWnd::CreateStatic (

CWnd *pParentWnd,

int nRows,

int nCols,

DWORD dwStyle = WS_CHILD |WS_VISIBLE,

UINT nID = AFX_IDW_PANE_FIRST)

Создает статическое разделяемое окно, в котором пользователь может изменять только размер индивидуальных областей, но не их число или порядок. Количество областей задается параметрами nRows и nCols, значения которых не должны быть больше 16. Функция автоматически устанавливает минимальные высоту строки и ширину столбца в нулевое значение. Для добавления в это окно полос прокрутки необходимо дополнительно установить стили WS_VSCROLL и WS_HSCROLL к значению по умолчанию параметра dwStyle. При успешном создании окна Windows функция возвращает TRUE, в противном случае — FALSE.

В отличие от использования функции Create, при создании статического разделяемого окна необходимо самостоятельно позаботиться о создании всех его областей, которые могут принадлежать различным оконным классам. Обычно для этого используется функция CreateView, но допускается и создание любого оконного класса, не обязательно основанного на представлении. Реализовать данную процедуру следует до завершения функции OnCreateClient родительского фрейма, чтобы обеспечить корректное изображение окна.

BOOL CSplitterWnd::CreateView (

 int row,

 int col,

CRunTimeClass *pViewClass, 

SIZE sizelnit,

 CCreateContext *pContext)

Создает область статического разделяемого окна. Строка и стрлбец, в которых будет размещаться вновь создаваемая область, задаются в параметрах row и со/. Тип области передается в функцию через pViewClass— указатель на структуру CRunTimeClass, определяющую класс представления. Параметр sizelnit устанавливает начальный размер нового представления.

BOOL CNoteFrame::OnCreateClient(

LPCREATESTRUCT Ipcs,

CCreateContext* pContext) 

{

//' Создаем разделенное окно Windows 

// с двумя строками и одним столбцом 

if(!m_spltWnd.CreateStatic(this, 2, 1))

 {

TRACEO("Ошибка при создании разделенного окна\n")

 return FALSE; 

}

// Создаем представление в первой строке 

// (горизонтальной области)

if(!m_spltWnd.CreateView(0, 0, pContext->m_pNewViewClass,

CSize(lpcs->cx, lpcs->cy*2/3), 

pContext)) 

{

TRACEO("Ошибка при создании представления для вывода\n");

 return FALSE; 

}

// Создаем представление во второй строке

 // (горизонтальной области)

 if(!m_spltWnd.CreateView(l, 0, RUNTIME_CLASS(CTextView),

CSize (0, 0), pContext)) {

 

TRACEO("Ошибка при создании представления для ввода\n"); 

return FALSE;

 }

// Устанавливаем фокус на область (1, 0)

 SetActiveView((CView*)m_spltWnd.GetPane(I, 0));

 return TRUE; 

}

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

Рассмотрев процесс создания, перечислим функции, которые позволяют получать информацию и изменять параметры разделяемого окна:

GetRowCount, GetColumn Count, GetRowInfo, GetColumnlnfo, Set Row Info, SetColumnlnfo, GetPane, IsChildPane, IdFromEowCol, RecalcLayout, GetScrollStyle, SetScrollStyle.

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

OnDrawSplitter, OnlnVertTracker, DeleteView, SplitRow, SplitColumn, DeleteRow, DeleteColumn, GetActivePane, SetActivePane, CanActivateNext, ActivateNext, DoKeyboardSplit, DoScroll, DoScrollBy, CreateScrollBarCtrl.

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