    Создание и просмотр документов 

    Прочтя предыдущие 16 глав и тщательно проработав приведенные в них примеры 
программ, вы получили хорошие знания об MFC, и проблем с написанием 
MFC-приложений у вас не должно быть. Но осталась еще одна тема, которую 
необходимо изучить, чтобы стать настоящим специалистом в области MFC, — это 
архитектура создания и просмотра документов. Она представляет собой 
альтернативный способ организации MFC-программ. Хотя рассмотренный ранее 
способ создания программ был полностью корректен, использование данной 
архитектуры имеет ряд существенных преимуществ. Понимание механизма работы с 
документами особенно важно в том случае, когда для создания каркаса программы 
применяется мастер AppWizard из Visual C++, поскольку в нем используется 
именно такой подход к организации приложения. В этом случае просто необходимо 
понимать принципы создания и просмотра документов. К счастью, программы, 
построенные на основе данной архитектуры, лишь чуть-чуть сложнее программ, 
организованных обычным способом. 

    461 

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

    Архитектура создания и просмотра документов 

    Вначале немного истории. До появления библиотеки MFC версии 2.0 все 
MFC-программы создавались так, как это было описано в предыдущих главах. При 
таком подходе создаются два основных объекта: объект приложения и объект окна. 
Давайте для простоты назовем этот подход архитектурой создания приложений. 
Такие программы построены по принципу обычных программ для Windows, написанных 
без использования MFC. Различия между ними только в том, что MFC 
автоматизирует и скрывает многие детали, которые в противном случае 
приходилось программировать явно. Но с концептуальной точки зрения оба подхода 
практически идентичны. 
    Хотя организация программ на основе архитектуры создания приложений 
обеспечивает программисту значительные преимущества (это было подробно 
рассмотрено в предыдущих главах), такой подход, тем не менее, не позволяет в 
полной мере использовать все возможности реализуемого библиотекой MFC 
интерфейса. В частности, в таких программах сами данные и способ их 
представления тесно связаны между собой. Чаще всего, поля данных объявляются в 
пределах класса, предназначенного для создания окна. И там же определяется, 
каким образом данные должны отображаться. А это не всегда желательно, ведь во 
многих случаях требуется различное представление одних и тех же данных. 
Например, часто необходимо отображать данные на экране, а затем распечатывать 
их на принтере. Вся суть в том, что в программах для Windows данные 
концептуально отделены от способа их представления. Но в архитектуре создания 
приложений оба понятия смешиваются в одном классе. 
    В архитектуре создания документов все организуется иначе. Данные (т.е. 
документ) отделены от своего представления (т.е. вида документа, или области 
просмотра). Это достигается путем инкапсуляции данных в классе, предназначен 
ном для создания документа, и инкапсуляции механизма представления этих данных 
в классе, предназначенном для организации просмотра документа. Класс просмотра 
также позволяет управлять вводом данных от пользователя. В некотором смысле 
отделение документа от способа его представления аналогично разделению данных 
и кода в самой программе. В программах, организованных таким способом, объект 
документа порождается от класса CDocument, а объект области просмотра 
порождается от класса CView (или производных от него). Это, правда, не 
отменяет необходимость порождения классов, предназначенных для создания 
главного окна и приложения. Но их роль значительно уменьшается. Например, 
область просмотра, создаваемая классом CView, перекрывает главное окно, 
формируемое классом CFrameWnd. 
    Прежде всего, необходимо детальнее разобраться, что же означают термины 
"документ" и "область просмотра". 

    462                                    MFC: основы программирования 


    Что такое документ? 

    Как упоминалось в предыдущем параграфе, документ — это данные, связанные с 
программой. Необходимо запомнить, что термин "документ" относится к любым 
видам данных, а не ограничен, скажем, текстовыми файлами. Например, битовый 
поток данных, передаваемых с орбиты спутником-шпионом, также формирует 
документ. Говоря формально, документ представляет собой блок данных, с которым 
работает программа. 
    В архитектуре создания и просмотра документов приложения могут быть 
построены на основе интерфейса SDI (Single Document Interface — интерфейс 
единого документа) или MDI (Multiple Document Interface — интерфейс составного 
документа). Однако фирма Microsoft настоятельно рекомендует для новых 
приложений использовать интерфейс SDI, поэтому в данной главе рассматривается 
только этот тип документов. 

    Что такое область просмотра? 

    Как уже упоминалось, область просмотра (view) соответствует виду 
документа. Другими словами, она содержит физическое представление данных. 
Отображение документа в области просмотра неоднозначно. Если, например, 
документ содержит данные о времени, то имеется по крайней мере четыре 
различных формата отображения этих данных: аналоговый 12-часовой, аналоговый 
24-часовой, цифровой 12-часовой и цифровой 24-часовой. Следует иметь в виду, 
что область просмотра не ограничена одним лишь экранным представлением. При 
печати документа определяется совершенно другая область просмотра. Таким 
образом, документ содержит чистые данные, а область просмотра определяет 
конкретный способ их представления. 

    Управление хранением документов 

    Классы работы с документами, содержащиеся в MFC, позволяют 
автоматизировать хранение документов в файлах на диске. В архитектуре создания 
документов этот процесс называется идентификацией (serialization). В основе 
идентификации лежит концепция сохранности данных: текущее состояние объекта (в 
данном случае документа) может быть сохранено и впоследствии восстановлено. На 
практике это означает, что должна существовать возможность сохранить документ 
на диске и потом загрузить его оттуда. Механизм идентификации реализован в 
классе CDocument. 
    Идентификация является определяющей характеристикой архитектуры создания и 
просмотра документов. Даже когда в программе порождается класс работы с 
документом (от класса CDocument) и класс работы с областью просмотра (от 
класса CView), то эта программа все равно не может считаться построенной на 
основе архитектуры создания документов, если она не поддерживает сохранение объектов. 

    Динамическое создание объектов 

    В программах работы с документами все объекты являются динамическими. Это 
означает, что объекты главного окна, документа и области просмотра создаются 

    17. Создание и просмотр документов                                 463 

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

    DECLARE_DYNCREATE(Имя_класса) IMPLEMENTJ3YNCREATE{Имя_класса, Имя_родительского_класса) 

    Здесь параметр Имя_к.ласса обозначает имя класса, для которого разрешается 
динамическое создание объектов, а параметр Имя_родительского_класса содержит 
имя базового класса из MFC. 
    Макрос DECLAREJDYNCREATEQ используется в объявлении класса для указания 
того, что объекты класса могут создаваться динамически. Макрос 
IMPLEMENT_DYNCREATE() вызывается в программном файле. После вызова обеих 
макрокоманд имя класса может быть указано в качестве параметра макроса RUNTIME_CLASS(): 

    RUNTIME_CLASS(Имя_класса) 

    Здесь параметр Имя_класса содержит имя динамического класса, которое ранее 
было указано в макросе DECLARE_DYNCREATE(). Макрос RUNTIME_CLASS() возвращает 
указатель на структуру типа CRuntimeClass, связанную с заданным классом. Этот 
указатель впоследствии используется при создании шаблона документа. 

    Создание каркаса приложения 

    Для построения приложения на основе архитектуры создания и просмотра 
документов необходимо выполнить следующие действия: 
    1. Породить классы, предназначенные для создания приложения, главного 
окна, документа и области просмотра. 
    2. Разрешить динамическое создание объектов для классов главного окна, 
документа и области просмотра. 
    3. Создать шаблон документа, объединяющий вместе классы главного окна, 
документа и области просмотра. 
    4. Реализовать обработку командной строки приложения. 
    5. Переопределить необходимые функции, например CObject::Serialize() или CView::OnDraw(). 
    Давайте подробнее рассмотрим каждое действие. 

    Порождение классов приложения и главного окна 

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

    464                                    MFC: основы программирования 

    Класс главного окна порождается от класса CFrameWnd. Для него должно быть 
разрешено динамическое создание объектов, т.е. он должен включать макрос 
DECLARE_DYNCREATE(). В данном классе обычно инкапсулируются все объекты 
элементов управления, связанных с главным окном, таких как панель инструментов 
и строка состояния. Ниже приведен базовый фрагмент класса, порождаемого от 
CFrameWnd и содержащего макрос DECLARE_DYNCREATE(). 

    class CMainWin : public CFrameWnd 
    ( 
    DECLARE_DYNCREATE(CMainWin) 
    // закрытая часть класса 
    public: 
    CMainWin() ; 
    // открытая часть класса 
    DECLARE_MESSAGE_MAP() 
    }; 

    Здесь макрос DECLARE_DYNCREATE() объявлен в закрытой части класса. 
Впрочем, он может находиться в защищенной и даже открытой части, если это 
необходимо в программе. 

    Порождение класса документа 

    Класс, предназначенный для работы с документом, порождается от класса 
CDocument. Для него должно быть разрешено динамическое создание объектов с 
помощью макроса DECLARE_DYNCREATE(). Обычно данный класс обрабатывает все 
команды, связанные с изменением состояния документа, и содержит очередь 
сообщений для этих команд. Теоретически, класс документа должен отвечать за 
обработку данных, содержащихся вдокументе. Поэтому он обрабатывает те команды, 
которые вызывают изменение данных. 
    Класс CDocument содержит множество функций, которые можно использовать в 
программе и, в некоторых случаях, переопределять. К последним относится 
функция OnNewDocument(), которая вызывается при создании нового документа. Ее 
прототип таков: 

    virtual BOOL CDocument::OnNewDocument(); 

    В переопределенной функции необходимо сначала вызывать базовую функцию из 
класса CDocument. Функция должна возвращать ненулевое значение при успешном 
завершении и ноль — в противном случае. 
    Другой функцией, которая часто переопределяется в классе документа, 
является функция SerializeO. Она является членом класса CObject и будет 
описана немного позднее. 
    Ниже приведен образец класса, предназначенного для работы с документом: 


    // Это класс документа 
    class CSampleDoc : public CDocument 
    ( 
    DECLAREJ3YNCREATE(CSampleDoc) 


    17. Создание и просмотр документов                                 465 


    // закрытая часть класса 
    public: 
    CSampleDoc О ; 
    BOOL OnNewDocument(); // инициализация каждого нового документа 
    void Serialize(CArchive barch); // запись или получение данных 
    
    // открытая часть класса 
    DECLARE_MESSAGE_MAP() 


    Порождение класса области просмотра 

    Класс области просмотра управляет отображением документа. Область 
просмотра перекрывает главное окно, которому, однако, предоставляется 
возможность управлять основными функциями работы с окном, такими как 
масштабирование и минимизация. Класс области просмотра порождается от класса 
CView или производных от него, например класса CScrollView (поддерживающего 
полосы прокрутки). Данный класс также должен быть объявлен динамическим с 
помощью макроса DECLARE_DYNCREATE(). Этот класс предназначен для обработки 
действий, выполняемых пользователем в области просмотра. 
    Класс CView содержит большое число функций, из которых в классе области 
просмотра должна быть переопределена по крайней мере одна: OnDraw(). Она 
вызывается, когда содержимое области просмотра должно быть изменено (при 
перекрытии ее другим окном или изменении данных). В принципе, функция ОпDraw() 
похожа на функцию OnPaint(), рассмотренную ранее. 
    Прототип функции OnDrawQ следующий: 

    virtual void CView::OnDraw(CDC *DC) = 0; 

    Как видите, это виртуальная функция, которая может быть переопределена в 
любом из производных от CView классов. Параметр DC является указателем на 
контекст устройства, связанный с текущей областью просмотра. Таким образом, 
нет необходимости явно получать контекст устройства, как это делалось при 
переопределении функции OnPaintQ. 
    Ниже приведен образец класса, предназначенного для работы с областью просмотра: 

    class CSampleView : public CView 
    1 
    DECLARE_DYNCREATE(CSampleView) 
    // закрытая часть класса 
    public: 
    void OnDraw(CDC *DC) ; 
    // открытая часть класса 
    DECLARE_MESSAGE_MAP() 
    }; 


    466                                     MFC: основы программирования 


    Разрешение динамического создания объектов 

    Включение макрокоманды DECLARE_DYNCREATE() в порождаемый класс является 
первым из двух действий, которые необходимо выполнить, чтобы разрешить 
динамическое создание объектов данного класса. На следующем шаге необходимо 
включить в программный файл макрос IMPLEMENT_DYNCREATE(). Вот примеры 
макрокоманд, разрешающих динамическое создание для рассмотренных выше классов: 
    
    IMPLEMENT_DYNCREATE(CMainWin, CFrameWnd) 
    IMPLEMENT_DYNCREATE(С Sample View, CView) 
    IMPLEMENT_DYNCREATE(CSampleDoc, CDocument) 

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

    Создание шаблона документа 

    После того как были порождены классы документа, области просмотра и 
главного окна, необходимо использовать их для создания шаблона документа. Этот 
шаблон предназначен для объединения перечисленных классов в единый модуль. При 
этом можно создавать шаблоны как составных, так и единых документов, 
используемые в MD1- и SDI-приложениях, соответственно. В данной главе будет 
рассмотрено только создание шаблонов второго типа. 
    Шаблон единого документа является объектом класса CSingleDocTemplate. 
Единственный член класса, который мы будем использовать, — это его 
конструктор, имеющий такой прототип: 

    CSingleDocTemplate:: CSingleDocTemplate(UINT ID, 
   
    CRuntimeClass *Document, 
    CRuntimeClass *FrameWindow, 
    CRuntimeClass *View}; 

    Здесь параметр ID является идентификатором ресурсов шаблона. К ним 
относятся меню, таблица быстрого доступа, пиктограмма, связанная с документом, 
а также строка, содержащая различные параметры, передаваемые программе. 
Параметр Document— это указатель на класс документа, параметр FrameWindow — 
указатель на класс главного окна, а параметр Йен»— указатель на класс области 
просмотра. Эти указатели можно получить с помощью макроса RUNTIME_CLASS(). 
Поэтому предварительно данные классы должны быть объявлены динамическими. 
    Как было сказано только что, параметр ID идентифицирует различные ресурсы, 
связанные с документом. Он также определяет ресурс строки, содержащей 
перечисленные ниже данные: 
    • заголовок окна; 
    • имя документа по умолчанию (если не указано, то используется имя "Untitled"); 
    • название типа документа (для приложений, поддерживающих работу с двумя и 
более типами документов); 
    • название фильтра файлов, отображаемое в выпадающем списке "Files of type"; 
    • расширение файлов для документов данного типа (например ТХТ); 
    • идентификатор типа файлов, используемый в системной базе регистрации; 
    • название типа файлов, используемое в системной базе регистрации. 


    77. Создание и просмотр документов                                 467 

    Указанные элементы содержатся в таком же порядке в общей строке. Все 
элементы разделены между собой символом "новая строка" ('\п'). Если какой-либо 
элемент не определен, ему соответствует только символ '\п', а в конце строки 
его можно вообще не указывать. 
    Ресурсы строк определяются в файле ресурсов с помощью оператора 
STRINGTABLE. Он имеет такой вид: 

    STRINGTABLE 
    { 
    string-IDl "строка" 
    String-102 "строка" 
    string-ID3 "строка" 
    string-IDn "строка" 
    } 

    Здесь параметр string-ID является идентификатором строки. Например, в 
приводимой далее программе используется следующий ресурс строки: 

    STRINGTABLE 
    { 
    IDR_FRAMEWIN "Test App\n\n\nDoc Type (*.pnt)\n.pnt\nDVTest\n 
    DV Test" 
    } 

    В данном случае идентификатором строки служит макроконстанта IDR_FRAMEWIN. 
"Test App" — это заголовок окна. По умолчанию документ не имеет имени, т.е. 
для новых документов будет использоваться имя "Untitled". Название типа 
документа также не определено. В выпадающем списке "Files of type" файлам 
данных документов будет соответствовать элемент "Doc Type (*.pnt)". 
Создаваемые файлы будут иметь расширение PNT. В системной базе регистрации 
документам данного типа будет соответствовать идентификатор "DVTest", а тип 
файлов будет определен как "DV Test". 

    Инициализация приложения 

    Ранее во всех примерах, приводимых в книге, для инициализации приложения 
выполнялась определенная последовательность действий. Все действия 
производились внутри функции CWinApp::InitInstance(), которая вызывалась при 
запуске приложения. Для инициализации приложений, построенных на основе 
архитектуры создания документов, также используется 4>ункция Initlnstance(), 
однако в ней выполняется несколько иная последовательность действий. Примерный 
вид этой функции таков: 
    
    // Инициализация приложения 
    BOOL САрр::Initlnstance() 
    ( 
    CSingleDocTemplate *DocPtr = new CSingleDocTemplate( 


    468                                    MFC: основы программирования 


    IDR_FRAMEWIN, 
    RUNTIME_CLASS(CSampleDoc), 
    RUNTIME_CLASS(CMainWin), 
    RUNTIME_CLASS(CSampleView) 
    )-• 
    AddDocTemplate(DocPtr) ; 
    
    // регистрация приложения в системной базе регистрации 
    EnableShellOpen() ; 
    RegisterShellFileTypes() ; 
    CCommandLineInfo CLInfo; 
    ParseCommandLine(CLInfo) ; 
    if(!ProcessShellCommand(CLInfo)) return FALSE; 
    return TRUE; 
    } 

    Давайте подробнее рассмотрим текст функции. Вначале в ней объявляется 
указатель DocPtr, который инициализируется с помощью конструктора 
CSingleDocTemplateO. Как получить параметры этого конструктора, объяснялось 
ранее. Затем данный указатель используется при вызове функции 
AddDocTemplate(), которая добавляет созданный шаблон документа к списку 
шаблонов, поддерживаемых приложением. Эта функция имеет следующий прототип: 

    void CWinApp::AddDocTemplate(CDocTemplate *DocTemplate); 

    Здесь параметр DocTemplate является указателем на шаблон нового документа. 
После того как шаблон документа добавлен к списку шаблонов, приложение должно 
вызвать функции EnableShelIOpenQ и RegisterShellFileTypesQ. Их прототипы таковы: 

    void CWinApp::EnableShellOpen() ; 
    void CWinApp::RegisterShellFileTypes() ; 

    Функция EnableShellOpen() позволяет пользователю осуществить запуск 
приложения по щелчку на одном из его документов. Функция 
RegisterShelIFileTypesQ регистрирует созданный тип документа в системной базе 
регистрации. Помните, что информация, заносимая в базу, определяется ресурсом 
строки, который указывается при создании шаблона документа. 
    В следующих трех строках кода выполняется обработка аргументов командной 
строки. Как известно, эти аргументы можно задать при запуске 
Windows-приложения с помощью команды Run или из командной строки. В 
приложениях, построенных на основе архитектуры создания документов, в 
командной строке обычно указывается имя документа, открываемого при запуске 
приложения. Информация из командной строки инкапсулируется в классе 
CCommandLineInfo. Функция CWinApp::ParseCommandLine() выполняет синтаксический 
разбор командной строки. Функция CWinApp::ProcessShellCommand() обрабатывает 
командную строку и определяет ее 

    17. Создание и просмотр документов                                 469 

    корректность. Если эта функция возвращает ноль, то функция InitInstanceO 
должна вернуть FALSE. (Если интерпретация аргументов командной строки важна 
для вашего приложения, можете подробнее изучить эти функции.) 

    Сохранение и загрузка документов 

    Одним из наиболее впечатляющих моментов архитектуры создания и просмотра 
документов является простота, с которой можно сохранять документы на диске и 
загружать их оттуда. Это объясняется двумя обстоятельствами. Во-первых, класс 
CObject содержит функцию Serialize(), которая автоматически вызывается при 
сохранении или загрузке документа. Во-вторых, в MFC определен класс CArchive, 
предназначенный для автоматизации почти всех функций работы с файлами. В 
классе CArchive содержатся перегруженные версии операторов ввода-вывода « и », 
предназначенные для работы как со стандартными типами данных, так и с типами 
классов, определенных в иерархии MFC. Как упоминалось в начале главы, 
механизм, обеспечивающий хранение документов, в архитектуре создания и 
просмотра документов называется идентификацией. 
    Функция CObject::Serialize() вызывается при загрузке или сохранении 
документа. Ее прототип таков: 

    virtual void CObject:: Serialize (CArchive SiArchOb) ; 

    Здесь параметр ArchOb является объектом типа CArchive, определяющим поток 
архивирования данных. Функция SerializeQ должна переопределяться в классе 
документа. Пользовательская функция должна записывать и читать документ из 
архивного потока, определяемого параметром ArchOb. 
    Поскольку функция Serialize() вызывается как при сохранении, так и при 
загрузке документа, должен существовать способ различать обе ситуации. Для 
этого используются срункции IsStoringO и IsLoading(), являющиеся членами 
класса CArchive. Прототипы этих функций следующие: 

    BOOL CArchive::IsLoading() const; 
    BOOL CArchive::IsStoring() const; 

    Функция IsLoading() возвращает ненулевое значение, если документ 
загружается, и ноль, если документ сохраняется. С другой стороны, функция 
IsStoringO возвращает ненулевое значение при сохранении документа, а при его 
загрузке — ноль. 
    При записи и сохранении документа внутри функции SerializeQ необходимо 
использовать операторы « и ». В качестве левого операнда этих операций (т.е. 
стоящего слева от оператора) должен присутствовать параметр ArchOb. Класс 
CArchive весьма важен и имеет внушительный размер. Полностью изучить его вы 
сможете при написании собственных программ. 

    470                                     MFC: основы программирования 


    Две важные функции из класса CDocument 

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

    Функция UpdateAIIViewsQ 

    Когда в документ вносятся изменения, приложение должно вызывать функцию 
UpdateAllViews(). Эта функция посылает всем областям просмотра, связанным с 
данным документом, сообщение о том, что экранное представление этих областей 
должно быть изменено. Функция UpdateAllViewsQ имеет следующий прототип: 

    void CDocument: : UpdateAllViews (CView *SencfeJ-, LPARAM lParam=0, 
CObject *ObPtr=NULL) ; 

    Здесь параметр Sender является указателем на объект области просмотра, в 
которой было внесено изменение в документ. Если этот параметр равен NULL, 
значит необходимо обновить содержимое всех областей просмотра, связанных с 
документом. Параметры IParam и ObPtr могут использоваться для оптимизации 
процесса обновления областей просмотра. Для всех областей просмотра, связанных 
с документом (кроме области, определяемой параметром Sender), функция 
UpdateAllViewsQ вызывает функцию CView::OnUpdate(). (Конечно, если параметр 
Sender равен NULL, то изменению подвергнутся все области просмотра.) При 
необходимости, функцию OnUpdateQ можно переопределить. 

    Функция SetModifiedFlagQ 

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

    void CDocument: : SetModifiedFlag (BOOL JsMod-if-ied=TRUE) ; 

    Если параметр IsMociified не равен нулю, то устанавливается флаг 
модификации документа. В противном случае этот флаг сбрасывается. 

    Важная функция класса CView 

    Класс CView тоже содержит большое число различных функций. Одна из 
наиболее часто используемых функций называется GetDocument(). Она возвращает 
указатель на объект класса CDocument, связанный с областью просмотра. Как 
нетрудно догадаться, эта функция используется в тех случаях, когда область 
просмотра должна получить доступ к данным, хранящимся в документе, в частности 
при отображении этих данных. Она также позволяет вызывать функции класса 
CDocument из класса области просмотра. Прототип функции GetDocumentQ следующий: 

    CDocument *CView::GetDocument() const; 

    Функция возвращает указатель на объект документа, связанного с вызвавшим 
функцию объектом области просмотра. 

    17. Создание и просмотр документов                                 471 


    Стандартные идентификаторы 

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

    Таблица 17.1. Стандартные идентификаторы 

    Идентификатор               Вызываемая функция 

    ID_APP_EXITCWinApp::OnAppExit() 
    ID_FILE_CLOSE               CDocument::OnFileClose() 
    ID_FILE_NEW                 CWinApp::OnFileNew() 
    ID_FILE_OPEN                CWinApp::OnFileOpen() 
    ID_FILE_PRINT                CView::OnFilePrint() 
    ID_FILE_PRINT_PREV1EW      CView::OnFilePrintPreview() 
    ID_FILE_PRINT_SETUP         CWinApp::OnFilePrintSetup() 
    ID_FILE_SAVE                 CDocuinent::OnFileSave() 
    ID_FILE_SAVE_AS__________CDocument::OnFileSaveAs()___________ 

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

    Таблица 17.2. Способ обработки идентификаторов 

    Обрабатываются автоматически Должны включаться в очередь сообщений 

    "ГО^Р.ЕХП-ICLFILE_NEW— 
    ID_F1LE_CLOSE              ID_FILE_OPEN 
    ID_F1LE_SAVE               ID_FILE_PRINT 
    ID_FILE_SAVE_AS            ID_FILE_PRINT_PREVIEW 
    ID_FILE_PRINT_SETUP 

    Существует также множество других заранее определенных идентификаторов. 
Например, с большинством функций редактирования связаны стандартные 
идентификаторы, такие как ID_EDIT_CUT, ID_EDIT_PASTE и др. 
    *  Автор не упоминает об одном важном моменте. Если макрокоманды для 
данных идентификаторов не поместить в очередь сообщений, то соответствующие им 
элементы меню могут оказаться неактивными. Это связано с обработчиками 
изменений в меню, которые рассматривались в главе 15. Поэтому автоматическая 
обработка идентификатора не означает, что связанный с ним элемент меню будет 
доступен. — Примеч. ред. 

    472                                     MFC: основы программирования 


    Пример программы работы с документами 

    Теперь, когда теория работы с документами изложена, давайте рассмотрим 
пример программы, построенной на основе архитектуры создания и просмотра 
документов. Будучи очень простой, она тем не менее демонстрирует основные 
принципы и приемы работы с документами. Документ, создаваемый в программе, 
содержит запись координат курсора мыши. В области просмотра каждая следующая 
координата соединяется с предыдущей с помощью прямой линии. Пользователь 
создает документ путем нажатия левой кнопки мыши. При этом записываются 
текущие координаты курсора мыши. Создаваемый документ можно сохранять и 
впоследствии загружать заново. Можно также удалять введенные координаты, 
очищать документ и создавать новый документ. Пример работы программы показан 
на рис. 17.1. 
    Ниже приведен текст файла ресурсов программы. Заметьте, что в него включен 
файл AFXRES.RC, который содержит некоторые константы, используемые 
стандартными функциями работы с документами. 

    ; DOCVIEW.RC 
    tinclude <afxres.h> 
    ^include <afxres.rc> 
    ftinclude "ids.h" 
    IDR_FRAMEWIN MENU 
    { 
    POPUP "File" 
    { 
    MENUITEM "&New\tCtrl-N", ID_FILE_NEW 
    MENUITEM "&Open\Ctrl-0", ID_FILE_OPEN 
    MENUITEM "&Save\tCtrl-S", ID_FILE_SAVE 
    MENUITEM "Save &As\tAlt-S", ID_FILE_SAVE_AS 
    MENUITEM "E&xit\tF4", ID_APP_EXIT 
    } 
    
    POPUP "Options" 
    ( 
    MENUITEM "&Undo\tF2", IDM_UNDO 
    MENUITEM "&Reset\tF3", IDM_RESET 
    } 
    MENUITEM "&Help", IDM_HELP 
    } 
    IDR_FRAMEWIN ACCELERATORS 
    ( 
    VK_F2, IDM_UNDO, VIRTKEY 
    VK_F3, IDM_RESET, VIRTKEY 
    VK_F4, ID_APP_EXIT, VIRTKEY 
    VK_F1, IDM_HELP_INDEX, VIRTKEY 
    ""N", ID_FILE_NEW 


    17. Создание и просмотр документов                                 473 


    ""О", ID_JILE_OPEN 
    ""S", ID_FILE_SAVE 
    "S", ID_FILE_SAVE_AS, ALT } 
    STRINGTABLE 
    { 
    IDR_FRAMEWIN "Test App\n\n\nDoc Type (*.pnt)\n.pnt\nDVTest\nDV 
    Test" 
    } 
    Это текст библиотечного файла IDS.H: 
    // IDS.H 
    #de?ine IDM_UNDO      100 
    #define IDM_RESET     101 
    #define IDM_HELP      102 
    ttdefine IDR_FRAMEWIN 200 
    ftdefine MAXLOC        100 

    Вот текст файла DOCVIEW.H, содержащего объявления порождаемых классов: 

    // DOCVIEW.H 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    ( 
    DECLARE_DYNCREATE(CMainWin) 
    public: 
    CMainWin() ; 
    DECLARE_MESSAGE_MAP() 
    
    // Это класс приложения 
    class CApp : public CWinApp 
    ( 
    public: 
    BOOL Initlnstance () ; 
    DECLARE_MESSAGE__MAP() 
    // Это класс документа 
    class CSampleDoc : public CDocument 
    ( 
    DECLAREJ3YNCREATE(CSampleDoc) 
    int m_Lodndex; 
    CPoint m_Locations[MAXLOC] ; 
    public: 
    CSampleDoc() ; 


    474                                    MFC: основы программирования 


    afx msg void OnUndo(); 
    afx msg void OnResetO; 
    afx msg void OnHelpO; 
    BOOL OnNewDocument(); 
    void Serialize(CArchive &arch); 
    DECLARE_MESSAGE_MAP() 
    }; 
    
    // Это класс области просмотра 
    class CSampleView : public CView 
    ( 
    
    DECLARE_DYNCREATE(CSampleView) 
    public: 
    void OnDraw(CDC *DC) ; 
    afx_msg void OnLButtonDown(UINT flags, CPoint loc) ; 
    DECLARE_MESSAGE_MAP() 
    }; 
    Это текст самой программы: 
    // Демонстрация работы с документами 
    #include <afxwin.h> 
    #include "ids.h" 
    ^include "docview.h" 
    CApp App; // создание экземпляра приложения 
    
    IMPLEMENT_DYNCREATE(CMainWin, CFrameHnd) 
    IMPLEMENT_DYNCREATE(CSampleView, CView) 
    IMPLEMENT_DYNCREATE(CSampleDoc, CDocument) 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CApp, CWinApp) 
    ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) 
    
    ON_COMMAND(ID_FILE_NEW_, CWinApp::OnFileNew) 
    END_MESSAGE_MAP() 
    
    // Это очередь сообщений области просмотра 
    BEGIN_MESSAGE_MAP(CSampleView, CView) 
    
    ON_WM_LBUTTONDOWN() 
    END_MESSAGE_MAP() 
    
    // Это очередь сообщений документа 
    BEGIN_MESSAGE_MAP(CSampleDoc, CDocument) 
    ON_COMMAND(IDM_UNDO, OnUndo) 
    ON__COMMAND(IDM_RESET, OnReset) 
    
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 


    77. Создание и просмотр документов                                 475 


    // Это очередь сообщений главного окна 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    
    // не используется в данном примере 
    END_MESSAGE_MAP() 
    // Инициализация главного окна 
    CMainWin::CMainWin() 
    ( 
    // не используется в данном примере 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance () 
    ( 
    
    CSingleDocTemplate *DocPtr = new CSingleDocTemplate( 
    IDR_FRAMEWIN, 
    RUNTIME_CLASS(CSampleDoc), 
    RUNTIME_CLASS(CMainMin), 
    RUNTIME_CLASS(CSampleView) 
    ) ; 
    AddDocTemplate(DocPtr) ; 
    
    // регистрация приложения в системной базе регистрации 
    EnableShellOpen() ; 
    
    RegisterShellFileTypes() ; 
    CCommandLineInfo CLInfo; 
    ParseCommandLine(CLInfo) ; 
    if (!ProcessShellCommand(CLInfo)) return FALSE; 
    return TRUE; 
    
    // Инициализация документа 
    CSampleDoc::CSampleDoc() 
    ( 
    m Loclndex = 0; 
    } 
    
    // Обработка сообщения IDM_HELP 
    afx_msg void CSampleDoc::OnHelp() 
    ( 
    
    App.m_pMainWnd->MessageBox("Document/View Demo", 
    "Placeholder Help"); 
    } 
    // Обработка запроса на создание нового документа 
    BOOL CSampleDoc::OnNewDocument() 
    { 
    if ( i.CDocument: :OnNewDocument () ) return FALSE; 


    476                                    MFC: основы программирования 


    m_LocIndex = 0; 
    return TRUE; 
    } 
    
    // Загрузка или сохранение документа 
    void CSampleDoc::Serialize(CArcive &arch) 
    { 
    int i; 
    if(arch.isLoading()) ( 
    arch » m Loclndex; 
    for(i=0; i<m Loclndex; i++) arch » m_Locations[i]; 
    } else ( 
    arch « m_Lodndex; 
    for(i=0; i<m Loclndex; i++) arch « m Locations[i]; 
    } 1 
    
    // Отмена последнего действия 
    afx msg void CSampleDoc::OnUndo() 
    { 
    m Loclndex--; 
    if (m_Lodndex<0) m_LocIndex=0; 
    SetModifiedFlag() ; 
    UpdateAllViews(NULL) ; 
    } 
    // Сброс индекса массива и стирание содержимого окна 
    afx_msg void CSampleDoc::OnReset() 
    { 
    m_Lodndex = 0; 
    SetModifiedFlag() ; 
    UpdateAllViews(NULL) ; 
    } 
    // Запись координат курсора мыши 
    afx_msg void CSampleView::OnLButtonDown(UINT flags, CPoint loc) 
    ( 
    CSampleDoc *DocPtr = (CSampleDoc *) GetDocument() ; 
    DocPtr->m Locations[DocPtr->m Loclndex] = loc; 
    DocPtr->m_LocIndex++; 
    if (DocPtr->m_Lodndex == MAXLOX) ( 
    DocPtr->m_LocIndex = MAXLOX; 
    App.m_pMainWnd->MessageBox("Too Many Points", "Error"); 
    ) 
    DocPtr->SetModifiedFlag() ; 
    DocPtr->UpdateAllViews(NULL) ; 
    ) 


    17. Создание и просмотр документов                                 477 


    // Рисование линий 
    void CSampleView::OnDraw(CDC *DC) 
    ( 
    CSampleDoc *DocPtr = (CSampleDoc *) GetDocument() ; 
    int i; 
    
    for(i=0; i<DocPtr->m_Lodndex; i++) 
    DC->LineTo(DocPtr->m_Locations[i] ) ; 
    ) 


    Подробнее о работе программы 

    Программа довольно проста и четко следует принципам архитектуры создания 
документов, описанным в начале главы. Координаты курсора мыши, формирующие 
документ, записываются в массив m_Locations, являющийся членом класса 
CSampleDoc. Размер массива определяется значением константы MAXLOC (которая 
равна 100). Текущее число элементов, записанных в массиве m_Locations, 
хранится в переменной m_LocIndex. Каждый раз, когда пользователь выполняет 
щелчок левой кнопкой мыши, координаты курсора мыши записываются в массив 
m_Locations и значение переменной m_Lodndex увеличивается. Пользователь может 
удалить из документа последние введенные координаты, выбрав в меню Options 
команду Undo. В этом случае программа просто уменьшает на единицу значение 
переменной m Loclndex. 

    Рис. 17.1. Пример работы с документом 


    478 MFC: основы программирования 

    Обратите особое внимание на то, как реализована функция Serialize(). При 
сохранении документа эта функция сначала записывает значение переменной 
mJLodndex, a затем содержимое массива m_Locations. При загрузке документа 
сначала читается значение переменной m_Lodndex, используемое для определения 
числа элементов, которое необходимо прочитать из массива ni_Locations. Для 
выполнения описанных действий требуется совсем немного кода, поскольку класс 
CArchive позволяет работать со всеми основными типами данных. 

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

    В настоящий момент архитектура создания и просмотра документов 
используется в мастере AppWizard в Visual C++. Уже один этот факт убеждает в 
ее важности и долговечности. Кроме того, использование данной архитектуры дает 
заметные преимущества. К примеру, она упрощает загрузку и сохранение данных. 
Кроме того, она позволяет автоматизировать многие рутинные операции, такие как 
вызов диалогового окна "File Open". Конечно, это не единственный способ 
написать хорошую MFCпрограмму. Откровенно говоря, преимущества данной 
архитектуры при написании простых, маленьких приложений далеко не очевидны. С 
другой стороны, написать большую и сложную программу, имеющую множество 
специфических особенностей, гораздо легче, используя стандартный подход. Это 
особенно справедливо для программ, в которых требуется много нестандартных 
элементов управления. Однако для широкого класса программ среднего размера 
архитектура создания и просмотра документов является идеальным решением. 

    В заключение 

    Прочтя данную книгу и разобрав приведенные в ней примеры, вы вполне можете 
считать себя MFC-программистом! Но не слишком обольщайтесь: в книге была 
затронута лишь вершина айсберга. Как и сама Windows, MFC — это очень большая и 
очень сложная среда программирования. Она слишком велика, чтобы ее можно было 
полностью описать в рамках одной книги. Однако теперь вы имеете достаточно 
знаний, чтобы самостоятельно изучить остальные классы и подсистемы библиотеки 
    Первое, что вы наверняка захотите изучить, это работа по технологии OLE 
(Краткий обзор данной темы приведен в Приложении В.) В настоящее время OLE 
становится неотъемлемой частью приложений Windows. Однако это чересчур широкая 
тема, и для ее освещения потребовалось бы написать довольно большую книгу. 
Однако рано или поздно вам придется освоить и ее. 

    17. Создание и просмотр документов                                 479