Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Методические указания. Вероятно, Вы обратили внимание, что практически в каждом сгенерированном ИС приложении есть знакомое меню File с командами New




Вероятно, Вы обратили внимание, что практически в каждом сгенерированном ИС приложении есть знакомое меню File с командами New, Open, Save и Save As. В этой работе Вы узнаете, как заставить приложение реагировать на них, т. е. считывать и записывать документы.

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

Приложение, которое Вам предстоит разработать в этой части работы – это SDI-приложение, основанное на приложении из предыдущей (второй) части работы. В нем создается документ со списком студентов и класс «вид», производный от CFormView. Теперь список студентов можно будет сохранять на диске и считывать с него, применяя так называемую сериализацию (serialization).

 

Понятие сериализации

Для Вас термин «сериализация» может быть, и новый, но он уже получил определенное распространение в мире программирования. Идея состоит в том, что существуют устойчивые (persistent) объекты, которые можно сохранять на диске при завершении программы и восстанавливать при следующем запуске. Этот процесс сохранения и восстановления объектов и называется сериализацией. Поддерживающие ее классы библиотеки MFC содержат функцию-член Serialize. Когда каркас приложений вызывает ее, например, для объекта класса CStudent, данные о студенте либо сохраняются на диске, либо считываются с него.

В библиотеке MFC сериализация не заменяет систему управления базами данных (БД) и вообще не имеет с ней ничего общего. Все объекты, связанные с конкретным документом, последовательно считываются или записываются в один дисковый файл. Доступ к отдельным объектам в файле по произвольным адресам невозможен. Если Вашей программе нужны средства управления БД, подумайте об использовании открытого интерфейса подключения к базам данных (Open DataBase Connectivity – ODBC) или объектов доступа к данным (Data Access Objects – DAO).

Примечание. На сегодняшний день есть новый способ хранения данных, занимающий промежуточное место между последовательными файлами и базами — структурированное хранилище (structured storage). Каркас MFC-приложений уже поддерживает его для программ-контейнеров, оперирующих внедренными (embedded) объектами.

Дисковые файлы и архивы

Как узнать, что должна делать функция Serialize: считывать данные или записывать их? Как она связывается с дисковым файлом? В MFC-библиотеке дисковые файлы обрабатываются объектами класса CFile. Объект CFile инкапсулирует описатель двоичного файла, возвращаемый Win32-функцией CreateFile. Это не указатель на структуру FILE буферизованного файла, возвращаемый функцией fopen стандартной С-библиотеки, а именно описатель двоичного файла. Каркас приложений использует его при вызовах Win32-функций ReadFile, WriteFile и SetFilePointer.

Если программа не выполняет прямые операции ввода/вывода на диск, а полагается на сериализацию, явного применения объектов CFile можно избежать. «Между» функцией Serialize и объектом CFile располагается объект-архив класса CArchive (рис. 1). Он буферизует данные для объекта CFile и поддерживает внутренний флажок, указывающий, записывается ли архив на диск или считывается с него. С каждым файлом единовременно может быть связан только один активный архив. За создание объектов CFile и CArchive, открытие дискового файла для объекта CFile и связывание объекта-архива с файлом отвечает каркас приложений. Все, что Вам остается сделать в своей функции Serialize — загрузить данные из объекта-архива или сохранить их в нем. Каркас приложений вызывает функцию Serialize класса «документ» при обработке команд Open и Save из меню File.

 
 

Рис. 1. Процесс сериализации

 

Создание сериализуемого класса

Для того чтобы сделать класс сериализуемым, он должен быть производным (прямо или косвенно) от CObject. Кроме того, в объявлении класса должен присутствовать макрос DECLARE_SERIAL, а в файле реализации — макрос IMPLEMENT_SERIAL. (Описание этих макросов см. в MSDN.) Мы внесем эти макросы в класс CStudent, который будем продолжать использовать в следующих примерах.

Создание функции Serialize

Ранее мы создали класс CStudent, производный от CObject, с такими переменными-членами:

public:

CString m_strName;

int m_nGrade;

 

Теперь напишем для класса CStudent функцию Serialize. Так как это виртуальная функция класса CObject, нужно, чтобы типы ее параметров и возвращаемого значения совпадали с объявленными в нем. Функция Serialize для класса CStudent должна выглядять так:

void CStudent::Serialize(CArchive& ar)

{

TRACE("Вход в CStudent::Serialize\n");

if (ar.IsStoring())

{

ar << m_strName << m_nGrade;

}

else

{

ar >> m_strName >> m_nGrade;

}

}

 

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

Обычно функции сериализации вызывают Serialize() соответствующего базового класса. Если бы да кабы CStudent был производным, скажем, от CMonkey, первая строка в этой функции выглядела бы так:

CMonkey::Serialize(ar);

 

Так как функции Serialize для классов CObject и CDocument не делают ничего полезного, вызывать их нет смысла.

Заметьте: ar – параметр, ссылающийся на объект «архив» приложения. Функция-член CArchive::IsStoring() информирует, для чего архив используется в настоящий момент — для записи или считывания. Благодаря тому, что у класса CArchive имеются переопределенные операторы вставки и извлечения для многих встроенных типов C++, мы и смогли написать функцию CStudent::Serialize в том простом виде, что приведен выше.

Операторы вставки принимают параметры по значению, операторы извлечения – по ссылке. Иногда приходится прибегать к преобразованию типов, чтобы избежать ошибок компиляции. Допустим, у нас есть переменная-член m_nType перечислимого типа. Вот какой код необходимо написать:

ar << (int) m_nType;

ar >> (int&) m_nType;

 

У таких MFC-классов, как CString и CRect, не являющихся производными от CObject, есть свои переопределенные операторы вставки и извлечения для CArchive.

 

Загрузка из архива: внедренные объекты и указатели

Допустим, в объект CStudent внедрены другие объекты, не являющиеся экземплярами классов стандартной библиотеки вроде CString, CSize или CRect. Например, добавим в класс CStudent новую переменную-член:

public:

CTranscript m_transcript;

 

Будем считать, что CTranscript — некий нестандартный класс, производный от CObject, у которого есть собственная функция Serialize(). Для CObject не предусмотрены переопределенные операторы << и >>, поэтому функция CStudent::Serialize() приобретает вид:

void CStudent::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

ar << m_strName << m_nGrade;

}

else

{

ar >> m_strName >> m_nGrade;

}

m_transcript.Serialize(ar);

}

 

Прежде чем вызывать функцию CStudent::Serialize для загрузки из архива записи о студенте, надо, очевидно, создать объект CStudent. Внедренный объект m_transcript класса CTranscript создается вместе с объектом CStudent перед вызовом функции CTranscript::Serialize. Последняя может загрузить из архива соответствующие данные во внедренный объект m_transcript. Запомните одно правило: для внедренных объектов классов, производных от CObject, функция Serialize вызывается явным образом.

Теперь допустим, что CStudent вместо внедренного объекта содержит указатель на CTranscript:

 

public:

CTranscript* m_pTranscript;

 

Можно было бы воспользоваться функцией Serialize, как показано ниже, но при этом пришлось бы самостоятельно создавать объект CTranscript:

void CStudent::Serialize(CArchive& ar)

{

if (ar.IsStoring())

ar << m_strName << m_nGrade;

else {

m_pTranscript = new CTranscript; // создание объекта

ar >> m_strName >> m_nGrade;

}

m_pTranscript->Serialize(ar);

}

 

Поскольку операторы вставки и извлечения в CArchive для указателей на CObject на самом деле переопределены, можно написать функцию Serialize и так:

void CStudent::Serialize(CArchive& ar)

{

if (ar.IsStoring())

ar << m_strName << m_nGrade << m_pTranscript;

else

ar >> m_strName >> m_nGrade >> m_pTranscript;

}

 

 

Как, однако, создается объект класса CTranscript, когда данные загружаются из архива? Для этой цели предназначены макросы DECLARE_SERIAL и IMPLEMENT_SERIAL, которые должны быть записаны в классе CTranscript. Когда объект CTranscript записывается в архив, эти макросы гарантируют занесение туда вместе с данными и имени класса. При загрузке архива считывается имя класса, и динамически создается объект нужного класса под управлением кода, сгенерированного макросами. После того как объект CTranscript сформирован, можно вызвать переопределенную для класса CTranscript функцию Serialize, чтобы считать данные из дискового файла.

И последнее. Указатель на CTranscript сохраняется в переменной-члене m_pTranscript. Чтобы избежать «утечки памяти» (memory leaks), убедитесь, что в m_pTranscript еще не занесен указатель на объект CTranscript. Если объект CStudent только что создан (а не загружен из архива), указатель на CTranscript будет нулевым.

 

Сериализация наборов

Так как все классы наборов производны от CObject и в их объявлениях присутствует вызов макроса DECLARE_SERIAL, для сериализации наборов достаточно просто вызвать функцию Serialize соответствующего класса набора. Например, если вызвать Serialize для набора CObList объектов CStudent, то последует вызов функции Serialize для каждого объекта CStudent. Но при этом не забывайте о следующих особенностях процесса загрузки наборов из архива:

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

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

· Когда из архива загружается набор указателей на CObject, над каждым объектом в наборе выполняются следующие операции:

1. Определяется класс объекта.

2. Объекту выделяется память из кучи.

3. Во вновь выделенную память загружаются данные объекта.

4. Указатель на новый объект сохраняется в наборе.

 

Сериализация внедренного набора объектов CStudent будет описана ниже – потерпите.

 

Функция Serialize и каркас приложений

Итак, теперь Вы знаете, как писать функции Serialize() и что вызовы функций могут быть вложенными. Но знаете ли Вы, когда вызывается первая функция Serialize для запуска процесса сериализации, а? В каркасе приложений все связано с документом (с объектом класса, производного от CDocument). При выборе команды Save или Open из меню File каркас приложений создает объект CArchive (и соответствующий объект CFile), после чего вызывает функцию Serialize из Вашего класса документа, передавая ссылку на объект CArchive. Затем функция Serialize производного класса документа выполняет сериализацию всех его (постоянных) переменных-членов (см. рис.1).

Примечание. Присмотревшись к какому-нибудь классу документа, сгенерированному мастером ИС, Вы заметите, что вместо макросов DECLARE_SERIAL и IMPLEMENT_SERIAL в нем применяются макросы DECLARE_DYNCREATE и IMPLEMENT_DYNCREATE. Макросы SERIAL не нужны, т. к. объекты-документы никогда не используются вместе с CArchive-оператором извлечения и не включаются в наборы; каркас приложений вызывает функцию Serialize документа напрямую. Макросы DECLARE_SERIAL и IMPLEMENT_SERIAL предназначены для остальных «сериализуемых» классов.

 

SDI-приложение

В предыдущей части работы Вы создавали свое SDI-приложение (или пытались приспособить чужое) с одним классом «документ» и одним классом «вид». В этой части работы мы по-прежнему будем иметь дело с единственным классом «вид», но постараемся исследовать взаимосвязи между объектом-приложением, основным окном-рамкой, документом, его представлением, объектом-шаблоном документа и связанными с ними ресурсами строк и меню.

 

Объект-приложение Windows

Для каждого из ранее рассмотренных приложений ИС автоматически генерировала класс, производный от CWinApp. Кроме того, она генерировала определение вида:

СМуАрр theApp;

 

То, что Вы видите, — это механизм запуска приложения, построенного на базе MFC. Класс СМуАрр происходит от CWinApp, a theApp — глобальный экземпляр данного класса, называемый объектом-приложением Windows.

 

Замечание. Естественно, что класс СМуАрр будет иметь такое имя в том случае, если проект назван Му. Если же Вы нарекли проект именем Lab9, то класс будет окрещен (по умолчанию) CLab9App.

 

Теперь рассмотрим последовательность операций, выполняемых при запуске Windows-приложения, написанного на базе MFC:

1. Windows загружает программу в память.

2. Создается глобальный объект CWinApp, для чего, естественно, вызывается его конструктор. (Все глобально объявленные объекты конструируются в момент загрузки программы.)

3. Windows вызывает глобальную функцию WinMain, которая является частью MFC-библиотеки. (WinMain эквивалентна функции main приложений текстового режима; обе они - главные точки входа в программу.)

4. WinMain отыскивает единственный экземпляр класса, производного от CWinApp.

5. WinMain вызывает функцию-член theApp.InitInstance, переопределенную в Вашем производном классе приложения (см. любой проект).

6. Ваша переопределенная функция InitInstance инициирует загрузку документа и создание основного окна-рамки приложения и окна представления.

7. WinMain вызывает функцию-член theApp.Run, которая организует распределение оконных и командных сообщений (вспомните Application.Run в проектах Delphi).

 

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

Примечание. Windows допускает одновременное выполнение нескольких экземпляров программы. Функция InitInstance вызывается всякий раз, когда запускается новый экземпляр программы. В Win32 каждый такой экземпляр — отдельный процесс. И то, что на виртуальные адресные пространства каждого процесса проецируется один и тот же код, несущественно. Если Вы хотите найти другие выполняемые экземпляры своей программы, то либо воспользуйтесь Win32-функцией FindWindow, либо — для связи между процессами — определите общую секцию данных или файл, проецируемый в память.

 

Класс шаблона документа

Взгляните, пристально, в глаза функции InitInstance, сгенерированной для вашего производного класса приложения, в которой есть такой фрагмент кода:

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CStudentDoc),

RUNTIME_CLASS(CMainFrame), // основное окно-рамка SDI-программы

RUNTIME_CLASS(CStudentView));

AddDocTemplate(pDocTemplate);

 

Если Вы не собираетесь использовать разделяемые окна или множественные представления данных, то с объектом «шаблон документа» Вы встретитесь только в этом месте программы. В данном случае это объект класса CSingleDocTemplate, производного от CDocTemplate. Класс CSingleDocTemplate применим только для SDI-приложений, у которых не может быть более одного объекта «документ». Что касается AddDocTemplate, то это функция-член класса CWinApp.

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

Такое динамическое создание объектов — пример искусного использования языка C++. Благодаря тому, что в определении и реализации класса присутствуют макросы DECLARE_DYNCREATE и IMPLEMENT_DYNCREATE, библиотека MFC способна создавать объекты данного класса динамически. Без этого в программу пришлось бы жестко зашить намного больше взаимосвязей между классами приложения. Например, в вашем производном классе приложения понадобился бы код для создания документа, его представления и рамки как объектов конкретных производных классов. Это нарушило бы объектно-ориентированную природу программы.

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

На рис. 2 показаны взаимосвязи между различными классами, а на рис. 3 – между объектами классов. У SDI-приложения может быть только один шаблон (и ассоциированные с ним группы классов), а во время его работы — один объект документа и один объект основного окна-рамки.

 

 
 

 

Рис. 2. Взаимосвязи классов

 

 
 

Рис. 3. Взаимосвязи объектов

 

Примечание. Динамическое создание объектов существовало в MFC-библиотеке еще до того, как в языке C++ стала возможной идентификация типов в период выполнения (runtime type identification, RTTI). Однако средства MFC значительно превосходят возможности RTTI, и MFC-библиотека продолжает для динамического создания объектов использовать именно их. Описание средств динамического создания объектов в MFC см., например, в приложении Б [1]. Очень полезно!

Ресурс шаблона документа

Первый параметр функции AddDocTemplate – идентификатор строкового ресурса IDR_MAINFRAME. Вот что ИС генерирует в проекте с именем FRM:

IDR_MAINFRAME "FRM\n" // заголовок окна приложения "\n" /* основа для имени документа по умолчанию (если не задано, то "Untitled" или "Без имени" в зависимости от языка ресурса) */ "FRM\n" // имя типа документа "FRM Files (*.frm)\n" // описание типа документа и фильтр ".frm\n" // расширение документов этого типа "FRM.Document\n" // идентификатор типа файла в реестре "FRM Document" // описание типа файла в реестре

 

Примечание. Конкатенация (сцепление) строк компилятором ресурсов не поддерживается. В файле FRM.rc перечисленные выше отдельные «подстроки» просто собраны в одну длинную строку:

"FRM\n\nFRM\nFRM Files (*.frm)\n.frm\nFRM.Document\nFRM Document"

 

IDR_MAINFRAME определяет одну строку, разбитую на подстроки символами \n. Эти подстроки показываются на экране в тот или иной момент исполнения программы. Строка.frm – расширение файлов документов, которое может быть изменено программистом при генерации каркаса приложения.

Кроме строк, идентификатор IDR_MAINFRAME определяет значок приложения, ресурсы панелей инструментов и меню. Эти ресурсы генерируются ИС, а Вы работаете с ними через редактор ресурсов.

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

 

Множественное представление документа в SDI-программах

Поддержка нескольких представлений документа в SDI-программе несколько сложнее. Тут можно просто определить элемент меню, позволяющий выбрать конкретное представление данных или же создать несколько представлений в разделяемом окне. Оба способа рассмотрены в главе 20 [1].

 

Создание пустого документа: функция CWinApp::OnFileNew

 

Функция InitInstance вашего класса приложения, вызвав функцию-член AddDocTemplate, затем вызывает (неявно, через CWinApp::ProcesShellCommand) другую важную функцию-член класса CWinApp – OnFileNew. Последняя, обращаясь к CWinApp::OpenDocumentFile, распутывает «паутину» взаимосвязанных имен классов и выполняет следующие действия:

1. Создает объект-документ, не пытаясь читать данные с диска.

2. Создает объект «основное окно-рамка» (класса CMainFrame) и основное окно, не отображая его на экране. У основного окна-рамки есть меню IDR_MAINFRAME, панель инструментов и строка состояния.

3. Формирует объект «вид» и соответствующее окно, не отображая его на экране.

4. Устанавливает связи между объектами «документ», «основное окно» и «представление». Не путайте связи между объектами со связями между классами, установленными вызовом AddDocTemplate.

5. Вызывает для объекта «документ» виртуальную функцию-член CDocument::OnNewDocument, которая обращается к виртуальной функции DeleteContents.

6. Вызывает для объекта «вид» виртуальную функцию-член CView::OnInitialUpdate.

7. Вызывает для объекта-рамки виртуальную функцию-член CFrameWnd::ActivateFrame, чтобы вывести на экран основное окно-рамку вместе с меню, окном представления, панелью инструментов и строкой состояния.

 

Примечание. Некоторые из функций вызываются не функцией CWinApp::OpenDocumentFile, а неявно самим каркасом приложений.

 

В SDI-приложении объекты «вид», «документ» и «основное окно-рамка» создаются только один раз и существуют на протяжении всей жизни программы. Функция CWinApp::OnFileNew вызывается функцией InitInstance. Кроме того, она вызывается в ответ на выбор в меню команды New и в этом случае OnFileNew должна вести себя несколько иначе. Она не формирует объекты «вид», «документ» и «рамка», так как они уже созданы. Вместо этого она использует существующие объекты повторно и выполняет вышеперечисленные операции 5, 6 и 7. Заметьте: OnFileNew всегда вызывает (неявно) DeleteContents, чтобы очистить документ.

 

Функция OnNewDocument класса «документ»

Если бы SDI-приложение не использовало объект-документ повторно, функция OnNewDocument была бы не нужна, так как всю инициализацию документа можно было бы провести в конструкторе его класса. Но реально Вы должны переопределить функцию OnNewDocument, чтобы инициализировать объект-документ всякий раз, когда пользователь выбирает в меню File команды New или Open. ИС поможет Вам в этом, создав заготовку функции в сгенерированном производном классе документа.

Примечание. Неплохо бы свести к минимуму объем операций, выполняемых в конструкторах. Чем их меньше, тем ниже вероятность сбоя в конструкторе — а ошибки там могут иметь весьма тяжкие последствия. Функции, подобные CDocument::OnNewDocument и CView::OnInitialUpdate, – идеальное место для начальной очистки объекта. Если возникнут какие-то проблемы, Вы сможете вывести сообщения в информационном окне, а при вызове OnNewDocument – и возвратить FALSE. Обе функции можно вызывать для данного объекта неоднократно. Если какие-то действия надо выполнить однократно, объявите специальную переменную-член (флажок) — она послужит признаком «первого вызова».

 

Связывание Open с кодом сериализации: функция CWinApp::OnFileOpen

ИС, генерируя приложение, сопоставляет команде Open из меню File функцию-член CWinApp::OnFileOpen, которая выполняет следующие действия:

2. Предлагает пользователю выбрать файл с помощью стандартного диалога Windows по открытию файла.

3. Вызывает виртуальную функцию-член CDocument::OnOpenDocument для уже существующего объекта-документа. Та открывает файл, вызывает CDocument::DeleteContents, создает объект CArchive, подготовленный для загрузки, и затем вызывает функцию Serialize документа, которая загружает данные из архива.

4. Вызывает функцию OnInitialUpdate класса «вид».

 

Альтернатива команде Open меню File — список последних открывавшихся файлов (MRU – Most Recently Used file list). По умолчанию, каркас приложений запоминает последние четыре файла и показывает их имена в меню File. В промежутке между запусками программы эти имена хранятся в реестре Windows.

 

Примечание. Можно изменить число последних открывавшихся файлов, которое должно запоминаться, вызвав с соответствующим параметром функцию LoadStdProfileSetting в функции InitInstance класса приложения.

 

Функция DeleteContents класса «документ»

 

При загрузке данных из дискового файла в существующий объект-документ SDI надо как-то стереть текущее содержимое объекта. Лучший способ сделать это — переопределить виртуальную функцию CDocument::DeleteContents в вашем производном классе документа. Вы уже программировали текст этой функции во второй части работы: такая переопределенная функция делает все, что нужно для очистки переменных-членов класса документа. При выборе в меню File команд New и Open функции OnFileNew и OnFileOpen класса CWinApp обращаются к DeleteContents, а значит, она вызывается сразу после создания объекта-документа (и вновь вызывается при закрытии документа).

Чтобы ваши классы документов работали в SDI-приложениях, очищайте содержимое документа в функции DeleteContents, а не в деструкторе. Последний используйте только для очистки элементов, существующих на протяжении всей жизни объекта.

Связывание File Save и File Save As с кодом сериализации

ИС, генерируя приложение, сопоставляет команде Save меню File функцию-член OnFileSave класса CDocument. OnFileSave вызывает функцию OnSaveDocument класса CDocument, которая, в свою очередь, обращается к функции Serialize документа, передавая ей объект-архив, подготовленный для сохранения. Команда Save As из меню File обрабатывается аналогично – ей сопоставляется функция OnFileSaveAs класса CDocument, которая вызывает OnSaveDocument. Все операции с файлами, необходимые для сохранения документа на диске, осуществляет здесь каркас приложений.

Примечание. Очевидно, что командам File New и File Open сопоставляются функции-члены класса приложения, a File Save и File Save As связываются с функциями-членами класса документа. File New связана с OnFileNew. Версия InitInstance для SDI-приложения тоже вызывает OnFileNew (неявным образом). Объект-документ не существует в момент вызова InitInstance каркасом приложений, поэтому OnFileNew не может быть функцией-членом CDocument. Но при сохранении документа объект-документ, разумеется, существует.

Флажок «документ изменен»

Многие приложения Windows, ориентированные на документ, отслеживают изменения в нем. При его закрытии или выходе из программы появляется окно с запросом, следует ли сохранить текущий документ. Каркас MFC-приложений поддерживает такое поведение при помощи переменной-члена m_bModified класса CDocument. Эта логическая переменная равна TRUE, если документ изменен (становится «грязным» – «dirty»), и FALSE – если он не изменился.

Доступ к защищенному флажку m_bModified осуществляется через функции-члены SetModifiedFlag и IsModified класса CDocument. Когда документ создается, открывается или сохраняется на диске, флажок объекта-документа устанавливается в FALSE. При изменении его данных Вы должны устанавливать этот флажок в TRUE с помощью SetModifiedFlag. Виртуальная функция CDocument::SaveModified, которую каркас приложений вызывает, когда пользователь закрывает документ, выводит информационное окно, если флажок m_bModified установлен в TRUE. Если Вам нужно выполнить другие действия, переопределите эту функцию.

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

Примечание. SDI-программы, написанные на базе MFC, ведут себя несколько иначе, нежели другие SDI-приложения для Windows вроде Paint. Вот как выглядит типичная последовательность событий: 1. Пользователь создает документ и сохраняет его на диске как, скажем, test.dat. 2. Пользователь изменяет документ. 3. Пользователь выбирает команду Open из меню File и указывает файл test.dat.   При выборе команды File Open программа Paint спрашивает, нужно ли сохранить изменения в документе, сделанные на этапе 2. Если пользователь отвечает «нет», программа вновь считывает документ с диска. Приложение на MFC считает изменения постоянными, не перезагружая при этом файл.



Поделиться с друзьями:


Дата добавления: 2014-12-26; Просмотров: 416; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.107 сек.