Глава 18.
Документ и его
представления
Как я уже неоднократно отмечал, одним из основных достоинств архитектуры
"документ/представление" является то, что в ней данные отделены от их
изображения.
Примечание
Точнее, архитектура позволяет это сделать, и мы
рекомендуем к этому стремиться.
Любые изменения данных осуществляются с использованием специального класса
документа, который обеспечивает необходимое пространство для их хранения в
памяти, отвечает за запись и чтение документа с диска и предоставляет интерфейс
для доступа и обновления данных. За изображение данных на экране, принтере или
любом другом устройстве отвечает представление, которое использует для этого
класс CView или производный от него. Можно сказать, что объект этого класса
представляет собой окно, посредством которого, с одной стороны, осуществляется
взаимодействие с пользователем, а с другой — организуется доступ к интерфейсной
части документа для возможности обновления данных. В достаточно упрощенном виде
сказанное можно проиллюстрировать с помощью рис. 18.1. Следует также повторить,
что один и тот же документ одновременно может иметь несколько различных
представлений.
Таким образом, действуя совместно, документ и его представления:
- содержат, изображают данные приложения и управляют ими;
- предоставляют интерфейс с данными документа;
- участвуют в чтении и записи файлов;
- предоставляют необходимые возможности для^ печати данных;
- обрабатывают большинство команд и сообщений приложения.
Рассмотрим основные компоненты архитектуры "документ/представление" подробно.
Начнем с документов.
Рис. 18.1. Взаимоотношения документа и представления
Документы
В самом общем случае можно сказать, что документы содержат и управляют
данными приложения. Или, другими словами, документ представляет собой некоторую
единицу данных, которую пользователь обычно открывает по команде ID_FILE_OPEN и сохраняет по команде ID_FILE_SAVE.
Для реализации документа в типичном приложении необходимо проделать следующую
последовательность действий (рис. 18.2):
- для каждого типа документа образовать класс на базе CDocumenf,
- добавить в него переменные для хранения всех данных документа;
- реализовать функции для чтения и модификации этих данных;
- переопределить функцию CObject::Serialize в новом классе документа для организации
чтения/записи данных документа с диска и на диск.
Данные документа определяются как переменные специального класса документа,
производного от 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 для реализации процесса сериализации — мы еще вернемся,
а пока рассмотрим "цикл жизни" документа в рамках архитектуры
"документ/представление":
- Во время динамического создания вызывается конструктор объекта —
"документ".
- Для каждого нового документа вызывается функция OnNewDocument или OnOpenDocument.
- Пользователь взаимодействует с документом посредством представлений,
ассоциированных с ним.
- Для удаления данных документа вызывается функция DeleteContents.
- Для удаления объекта "документ" вызывается его деструктор.
Перечисленные шаги характерны для 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, но при этом
ориентировано на работу только с определенным набором данных. С другой стороны,
в рамках одного фрейма документа можно создавать и использовать сколько угодно
представлений для работы с одним и тем же документом.
Рис. 18.4. Место классов представлений в иерархии библиотеки MFC
Достаточно часто возникает желание иметь различные представления одного
данного типа документа. Что имеется в виду? Например, при работе с текстовым
процессором желательно иметь одно представление для текста документа, а другое
(контурное) — для изображения области колонтитулов. Эти различные типы
представлений могут размещаться как в различных фреймах, так и в различных
областях одного и того же фрейма. В обоих случаях достаточно один раз
сопоставить представление с документом, но организовать различную обработку
данных документа (рис. 18.5).
Таким образом, представление отвечает за изображение и модификацию данных
документа, но не имеет никакого отношения к их хранению. (Точнее, не должно
иметь.) Необходимые подробности о различных параметрах этих данных хранятся
вместе с самим документом, откуда либо само представление, либо специальные
функции класса документа, обеспечивающие для того или иного представления доступ
к ним, берут только необходимое.
Несколько слов о взаимодействии с пользователем. Реализация выполнения любых
действий такого рода целиком ложится на соответствующий класс представления, а
значит, и на программиста. (Никто не может предугадать всех ваших желаний, даже
разработчики прекрасной библиотеки MFC.) Это может быть ввод с клавиатуры,
действия мышью, выбор элемента меню, нажатие кнопки панели инструментов или
работа с полосой прокрутки. Соответствующие команды представление получает от
своего фрейма (правда, не напрямую, а опосредованно). Обработку сообщений оно
осуществляет аналогично всем другим получателям команд — посредством карты
сообщений. Если представление само не обрабатывает команду, то передает ее
дальше "по маршруту", как описано в главе 5.
Последовательность действий по созданию представлений, выполняемая
библиотекой MFC, представлена на рис. 18.6.
Теперь, после того как сложилось некоторое общее представление о назначении и
возможностях этой группы классов, можно переходить к рассмотрению компонентов
группы и свойств ее составляющих.
Рис. 18.5. Использование нескольких представлений для отображения
данных одного документа
Рис. 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(или производного от него) имеет
следующие ограничения:
- Не поддерживает редактирование, по принципу WYSIWYG (What You See Is What You Get, "что видишь, то и
получаешь"), т. к. предназначен в основном для чтения с экрана.
- Может изображать текст только одним шрифтом и не поддерживает
специальное форматирование символов.
- Так же, как для элемента управления 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);
В карту сообщений класса необходимо добавить макрокоманды, связывающие
команды с соответствующими обработчиками:
Рис. 18.7. Стандартный блок диалога Find
Рис. 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 одновременно.
Полоса прокрутки в представлении появляется в трех случаях:
- Если для представления установлен стандартный стиль Windows,
включающий стили WS_HSCROLL и/или WS_VSCROLL.
- Если во фрейм, содержащий представление, добавлен элемент управления
"полоса прокрутки"; при этом библиотека посылает сообщения WM_HSCROLL и/или WM_VSCROLL из фрейма в текущее активное представление.
- Если объект CScrollView
расположен в CSplitterWnd с раздельными
полосами прокрутки; библиотека посылает сообщения WM_HSCROLL и/или WM_VSCROLL из CSplitterWnd в текущую активную разделенную область.
Рассмотрим основные члены класса 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 ()