Программирование на языке MFC

Мой второй блог в серии программирования

Что мы можем здесь увидеть? То, что в описании этого класса присутствует поле rnJTemplateList класса CPtrList, только подтвер­ждает нашу догадку о том, что de facto объекты класса CDocMan­ager являются списками указателей на какие-то шаблоны. Для того чтобы убедиться, что наша догадка верна, достаточно взглянуть на исходный код методов GetFirstDocTemplatePosition() и GetNext-DocTemplate(), который находится в файле docmgr.cpp:

POSITION CDocManager::GetFirstDocTemplatePosition() const {

return m_templateList.GetHeadPosition() ;

}

CDocTemplate* CDocManager::GetNextDocTemplate(

POSITIONS pos) const

{

return (CDocTemplate*)m_templateList.GetNext(pos) ;

Теперь возникает вопрос о том, что за шаблоны включаются в список. Позвольте, уважаемый читатель, высказать предположе­ние о том, что этими шаблонами являются шаблоны ДОКУМЕН­ТОВ, с которыми работает данное приложение. Я не буду сейчас останавливаться на том, что такое шаблон документа. Это станет ясно из дальнейшего изложения.

Сейчас нам необходимо научиться создавать объект этого класса. Естественно, что для создания объекта мы восполь­зуемся конструктором. Раз конструктор класса CDocManager параметров не имеет, то создание объекта этого класса за­труднений не вызовет. Но каким-то образом нам необходимо передать нашему приложению информацию о том, с документа­ми каких типов (шаблонов) он будет иметь дело! Вспомним, что у класса CWinApp для добавления шаблона в список есть метод AddDocTemplate(), который, фактически является вызовом одно­именного метода класса CDocManager. Наверное, именно этим методом и следует воспользоваться при добавлении шаблона документа!



Наверное, из описания класса CWinApp мы не сумеем извлечь еще какую-то информацию о классе CDocManager. Настало время взглянуть на описание этого класса. Думаю, нет необходимости описывать всю внутреннюю реализацию этого класса. Достаточно будет, если мы рассмотрим назначение наиболее часто используе­мых полей и методов.

Класс CDocManager в файле afxwin.h описан следующим образом:

class CDocManager : public CObject {

DECLARE_DYNAMIC(CDocManager) public:

// Constructor CDocManager();

//Document functions

virtual void AddDocTemplate(CDocTemplate* pTemplate); virtual POSITION GetFirstDocTemplatePosition() const; virtual CDocTemplate* GetNextDocTemplate(

POSITIONS pos) const; virtual void RegisterShellFileTypes(BOOL bCompat); void UnregisterShellFileTypes();

virtual CDocument* OpenDocumentFile(LPCTSTR IpszFileName);

// open named file

virtual BOOL SaveAllModified(); // save before exit virtual void CloseAllDocuments(BOOL bEndSession);

// close documents before exiting virtual int GetOpenDocumentCount();

// helper for standard commdlg dialogs virtual BOOL DoPromptFileName(CString& fileName

UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);

//Commands

// Advanced: process async DDE request

virtual BOOL OnDDECommand(LPTSTR IpszCommand); virtual void OnFileNew(); virtual void OnFileOpen();

// Implementation protected:

CPtrList m_templateList;

int GetDocumentCount(); // helper to count number

// of total documents

public:

static CPtrList* pStaticList;

// for static CDocTemplate objects static BOOL bStaticInit;

// TRUE during static initialization static CDocManager* pStaticDocManager;

// for static CDocTemplate objects

public:

virtual -CDocManager(); #ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const; #endif };



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

class CDocView : public CView {

DECLARE_DYNCREATE ( CDocView ) public:

CDocView();

virtual -CDocView();

>;

IMPLEMENT_DYNCREATE( CDocView, CView )

CDocView::CDocView()

{

}

CDocView::-CDocView()

{

}

Кроме этого, я изменил метод lnitlnstance() класса CDocViewl:

BOOL CDocViewlApp::Initlnstance() {

#ifdef _AFXDLL

Enable3dControls(); #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,

RUNTIME__CLASS ( CDoc ) , RUNTIME_CLASS( CMDIChildWnd ), RUNTIME_CLASS( CDocView ) ) ; AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();

return TRUE;

}

Когда я постарался откомпилировать эту программу, я получил сообщение о том, что у класса, наследуемого от CView, не опреде­лен метод OnDraw(). Ну, конечно же! Откуда же наш произвольный класс окна представления будет знать, каким образом ему необхо­димо перерисовываться? Давайте опять постараемся рассуждать логически. Какое сообщение получает окно, когда ему нужно пе­рерисовать себя? Правильно, WM_PAINT. Но для обработки со­общения WM_PAINT объекты MFC используют метод OnPaintQ.

Не является исключением и объекты класса CView и унаследо­ванных от него. Исходный код этого метода находится в файле viewcore.cpp:

void CView::OnPaint() {

// standard paint routine CPaintDC dc(this); OnPrepareDC(&dc); OnDraw(&dc);

}

Очевидно, что перерисовка осуществляется методом OnDraw(). Взглянем на исходный код этого метода, который также находится в файле viewcore.cpp:

void CView::OnDraw(CDC*)

{

}

To, что этот метод не делает ничего, подтверждает нашу догад­ку о том, что нам нужно переписать именно этот метод. Что ж опи­сание нашего класса CDocView придется немного изменить. Те­перь оно будет выглядеть так:

class CDocView : public CView {

DEСLARE_DYNCREATE ( CDocView ) public:

CDocView();

virtual ~CDocView(); void OnDraw( CDC* pDC );

};

Естественно, нам придется переопределить и добавить в про­грамму метод OnDravtr(). Пока этот метод нас не очень интересует, поэтому оставим его пустым:

void CDocView::OnDraw( CDC* pDC )

{

}

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

Любознательный читатель уже, наверное, приготовил мне во­прос: да, конечно, все что рассказывается, это верно, но каким же образом создается окно представления и как оно относится к окну фрейма и документу?



Документ, естественно, рождается из файла, поэтому первым делом метод, вызывая GetFile(), открывает файл, имя которого, как я уже говорил, он получил в качестве аргумента. Затем начинается самое интересное. На основе файла создается объект класса САг-chive, т. е. АРХИВ. В архитектуре «документ/представление» счи­тается, что программист должен иметь возможность легко сохра­нять созданные в памяти структуры данных в дисковом файле, после чего он должен иметь возможность вновь считать их из фай­ла. Архив позволяет программисту читать и записывать в файл не просто некоторые объемы информации, а ОБЪЕКТЫ всевозмож­ных типов! Именно поэтому его использование в большинстве слу­чаев представляется оправданным. И дальше вызывается метод Serialize(), который работает уже не с файлом, а с архивом! По умолчанию этот метод не делает ничего. Естественно, откуда про­грамме знать, что и как программисту захотелось сохранить в ар­хиве? А для программиста здесь раздолье! Можно проверить фор­мат открытого файла, определить, при необходимости, его струк­туру, выполнить все мыслимые и немыслимые действия. Для того чтобы продемонстрировать работу метода Serialize(), давайте по­пробуем открыть файл в нашей программе и считать его в буфер. Внесем небольшие изменения в наш класс документа – добавим два поля, в которые запишем, во-первых, размер файла, а второй будет являться указателем на буфер, в который мы будем читать файл. После внесенных изменений наша программа выглядит сле­дующим образом:

#include "stdafx.h" #include <afxcview.h> #include "resource.h"

// Объявляем класс приложения, его поля и методы.

class CDoc : public CDocument {

DECLARE_DYNCREATE( CDoc ) int nFileLength; void* pMyFile; public: CDocO ;

void Serialize ( CArchive &ar ); virtual -CDoc();

}/

• IMPLEMENT_DYNCREATE( CDoc, CDocument )

CDoc::CDoc()

{

}

CDoc::-CDoc()

{

}

void CDoc::Serialize( CArchive &ar ) {

if( ar.IsStoring() )

{

}

else {

// Определяем длину файла.

nFileLength = ar.GetFile()->GetLength();’

TRACE( _T( "File length = %d.\n"), nFileLength’ );

// Выделяем буфер в памяти, в который будем считывать файл. pMyFile = new chart nFileLength ];

// Считываем файл в буфер.

ar.Read( pMyFile, nFileLength ); }

}

class CDocViewlApp : public CWinApp {

public:

CDocViewlApp(); protected:

afx_msg void OnFileOpen(); virtual BOOL Inifrlnstance(); DEC LARE_ME S S AGE_MA P()

};

CDocViewlApp::CDocViewlApp()

{

}

void CDocViewlApp::OnFileOpen()

{

}

class CMainFrame : public CMDIFrameWnd {

DECLARE_DYNAMIC( CMainFrame ); public:

CMainFrame();

>;

IMPLEMENT_DYNAMIC (CMainFrame, CMDIFrameWnd)

CMainFrame::CMainFrame()

{

}

BOOL CDocViewlApp::Initlnstance() {

#ifdef _AFXDLL

Enable3dControls(); #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,

RUNTIME_CLASS( CDoc ) , RUNTIME_CLASS( CMDIChildWnd ), NULL );

AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame ( IDR__RESOURCE ) ; m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();

return TRUE;

}

BEGIN_MESSAGE_MAP( CDocViewlApp, CWinApp )

ON_COMMAND( ID_FILE_OPEN, CWinApp::OnFileOpen ) END_MESSAGE_MAP()

CDocViewlApp theApp;

Если мы запустим нашу программу на выполнение (внутри сре­ды разработки) и откроем какой-нибудь файл, то увидим, что на отладочном мониторе появилось сообщение о размере открытого файла. Любознательный читатель может проверить, произойдет ли считывание содержимого файла в буфер. Я абсолютно уверен, что в обычных условиях файл будет считан в буфер без каких-либо проблем.



Пока мы видим, что сразу после проверки того, что указатель на документ и идентификатор ресурсов ненулевые, создается струк­тура типа CCreateContext. Описание этой структуры находится в файле afxext.h:

struct CCreateContext // Creation information structure // All fields are optional and may be NULL

{

// for creating.,new views CRuntimeClass* m_pNewViewClass;

// runtime class of view to create or NULL CDocument* m_pCurrentDoc;

// for creating MDI children (CMDIChildWnd::LoadFrame) CDocTemplate* m_pNewDocTemplate;

// for sharing view/frame state from the // original view/frame

CView* m_pLastView; CFrameWnd* m_pCurrentFrame;

// Implementation CCreateContext();

};

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



На что необходимо обратить внимание? К этому моменту мы еще не представляем характера взаимодействия между докумен­том и фреймом. Поэтому просто предположим, что каким-то обра­зом наш документ будет отображаться в рамках фрейма. Давайте поразмыслим, уважаемый читатель. Мы пишем программу, кото­рая будет работать с многодокументным интерфейсом. Наша про­грамма должна отображать данные в одном из дочерних окон мно­годокументного интерфейса. Следовательно, логично будет в ка­честве фрейма использовать окно класса CMDIChildWnd. Давайте так и поступим, уважаемый читатель. Итак, ниже я привожу текст метода CDocView1App::lnitlnstance() нашей программы:

BOOL CDocViewlApp::Initlnstance () {

#ifdef _AFXDLL

Enable3dControls(); #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,

RUNTIME_CLASS( CDoc ),% RUNTIME_CLASS( CMDIChildWnd ),

NULL );

AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();

return TRUE;

}

создается фрейм этого документа. Нетрудно догадаться, что соз­дание фрейма происходит при вызове метода CDocTemplate:: CreateNewFrame():

CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc,

CFrameWnd* pother)

{

if (pDoc != NULL)

ASSERT_VALID(pDoc); // create a frame wired to the specified document

ASSERT(m_nIDResource != 0); // must have a resource. ID

// to load from

CCreateContext context;

context,m_pCurrentFrame = pother;

context.m_pCurrentDoc = pDoc;

context,m_pNewViewClass = m_pViewClass;

context.m_pNewDocTemplate = this;

if (m_pFrameClass == NULL) ‘ {

TRACEO("Error: you must override

CDocTemplate::CreateNewFrame.\n") ;

ASSERT(FALSE); return NULL;

}

CFrameWnd* pFrame =

(CFrameWnd*)m_pFrameClass->CreateObject() ; if (pFrame == NULL) {

TRACE1("Warning: Dynamic create of frame %hs failed.\n",

m_pFrameClass->m_lpszClassName);

return NULL;

}

ASSERT_KINDOF(CFrameWnd, pFrame);

if (context.m_pNewViewClass == NULL)

TRACEO("Warning: creating frame with no default

view.\n");

// create new from resource

if (!pFrame->LoadFrame(m_nIDResource,

WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles

NULL, &context))

{

TRACEO("Warning: CDocTemplate couldn’t create

a frame.\n"); // frame will be deleted in PostNcDestroy cleanup return NULL;

}

// it worked ! return pFrame;

}

Мне бы хотелось обратить внимание читателя на то, что в каче­стве аргументов методу передаются указатель на документ и ука­затель (пока нулевой), в который будет записан указатель на соз­данный фрейм. Мы уже однажды (при рассмотрении метода DoPromptFileName()) замечали, что методу передаются указате­ли, которые с первого взгляда совершенно не нужны для работы метода. Кажется, здесь тот же случай – ну зачем, скажите, пожа­луйста, при создании фрейма знать указатель на документ? То, что буквально в первых строках метода осуществляется проверка того, не равен ли идентификатор ресурсов нулю, говорит о том, что, вероятнее всего, при создании фрейма опять будут использо­ваться ресурсы. Но, как говорится, поживем – увидим.



Итак, подведем промежуточные итоги. К настоящему времени мы выяснили, что необходимо сделать для того, чтобы MFC нача­ла работать с приложением в соответствии с требованиями архи­тектуры «документ/представление», выяснили, как определить, какой файл мы будем открывать, создали пустой документ. Однако обратите внимание, что до настоящего момента мы не можем ни­коим образом увидеть тот документ, который мы только что созда­ли. Нам просто негде визуализировать наш документ! Для того что­бы увидеть результаты нашей работы, нам необходимо срочно соз­дать окно представления. Следовательно, нам необходимо вернуть­ся к коду метода CMultiDocTemplate::OpenDocumentFile() и посмот­реть, что же происходит после того, как новый документ был соз­дан, каким образом создается окно, в котором будет отображаться содержимое нашего документа.

Вернувшись, мы видим, что после создания нового документа программа пытается создать фрейм для нашего документа. Кажет­ся, нас сейчас ждет очередная неприятность… Так и есть. Мы полу­чили очередное сообщение об ошибке.

Посмотрев внимательнее, мы заметим, что попутно нам было выдано и отладочное сообщение о необходимости переопределить метод CDocTemplate::CreateNewFrame «Еггог: you must override CDocTemplate::CreateNewFrame.». Сейчас, наверное, уже нет не­обходимости разбираться, что произошло. Наверное, читатель уже догадался. Да, дело именно в том, что при определении шаблона документа мы указатель на информацию времени выполнения фрейма нашего документа оставили равным NULL! Что ж, придет­ся внести еще небольшие изменения в нашу программу.



Вносим необходимые изменения в метод InitlnstanceQ нашего приложения:

BOOL CDocViewlApp::Initlnstance() {

fifdef _AFXDLL

Enable3dControls(); #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,

RUNTIME_CLASS(CDoc), NULL, NULL ); AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow );

pMainFrame->UpdateWindow(); return TRUE;

}

Снова компилируем программу, запускаем на выполнение. ..И б-лагополучно минуем то место, на котором в прошлый раз у нас было выдано сообщение об ошибке. Однако нам необходимо по­нять, что происходит при создании документа, т. е. что происходит при работе метода CDocTemplate::CreateNewDocument().

CObject* CRuntimeClass::CreateObject() {

if (m_pfnCreateObject == NULL) {

TRACE(_T("Error: Trying to create object which is not ") _T("DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n"), m_lpszClassName); return NULL;

}

CObject* pObject = NULL;

TRY

{

pObject = (*m_pfnCreateObject) () ;

}

END_TRY

return pObject;(CWJnApp::OnFJIeOpen ) Во-вторых, при помощи метода
S . CMultiDocTemplate::AddDocu-

ЦCDocManager::OnFileOpen J ment() (файл docmulti.cpp)

UcWinAppriOpenDocumentFile) только что созданный

N J документ добавля-

L-[ CDocManager::OpenDocumentFile ] ется в список

CMultiDocTemplate::OpenDocumentFjle] Документов
—’ шаблона:

CDocTemplate::CreateNewDocum^n?)

^CMultiDocTemplate::AddDocument]

void CMultiDocTemplate::AddDocument(CDocument* pDoc) {

ASSERT_VALID(pDoc);

CDocTemplate::AddDocument(pDoc);

ASSERT(m_docList.Find(pDoc, NULL) == NULL);

// must not be in list

m_docList.AddTail(pDoc);

}

CWinAPP::OnFileOpen ) A этот мет0* Фактически представ-
————————– J ляет собой вызов одноименного

ч

CDocManager::OnFileOpen ] метода базового класса

CWinApp::OpenPocumentFilQ (фаЙЛ doctemPlcPP):

^-{CDocManager^OpenDocumentFile")

L^CMultiDocTemplate::OpenDocumentFile] CDocTemplate::CreateNewDocument] CMultiDocTemplate::AddDocument] L^CDocTemplate::AddDocument ]

void CDocTemplate::AddDocument(CDocument* pDoc) {

ASSERT_VALID(pDoc);

ASSERT(pDoc->m_pDocTemplate == NULL); // no template attached yet pDoc->m_pDocTemplate = this;

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

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



Итак, метод CDocTemplate::MatchDocType() используется для того, чтобы проверить, совпадает ли расширение открываемого файла с указанным в шаблоне документа. Зафиксировав этот факт, мы можем вернуться к методу CDocManager::OpenDocumentFile().

CDocument* CMultiDocTemplate::OpenDocumentFile(

LPCTSTR IpszPathName, BOOL bMakeVisible)

{

CDocument* pDocument = CreateNewDocument();

if (pDocument == NULL)

{

TRACEO("CDocTemplate::CreateNewDocument

returned NULL.\n"); AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); return NULL;

}

ASSERT_VALID(pDocument);

BOOL bAutoDelete = pDocument->m_bAutoDelete; pDocument->m_bAutoDelete = FALSE;

// don’t destroy if something goes wrong CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL); pDocument->m_bAutoDelete = bAutoDelete; if (pFrame == NULL)

{

Af xMessageBox (AFX_IDP_FAILED_TO_CREATE_DOC) ;

delete pDocument; // explicit delete on error

return NULL;

}

ASSERT_VALID(pFrame);

if (IpszPathName == NULL) {

// create a new document – with default document name SetDefaultTitle(pDocument);

// avoid creating temporary compound file when // starting up invisible

if (IbMakeVisible)

pDocument->m_bEmbedded = TRUE;

if (!pDocument->OnNewDocument ())

{

// user has be alerted to what failed in OnNewDocument

TRACEO("CDocument::OnNewDocument returned FALSE An"); pFrame->DestroyWindow(); return NULL;

}

// it worked, now bump untitled count m_nUntitledCount++;

}

else {

// open an existing document CWaitCursor wait;

if (!pDocument->OnOpenDocument(IpszPathName)) {

// user has be alerted to what failed in OnOpenDocument TRACEO("CDocument::OnOpenDocument returned FALSE An"); pFrame->DestroyWindow (); return NULL;

}

pDocument->SetPathName(IpszPathName);

}

InitialUpdateFrame(pFrame, pDocument, bMakeVisible); return pDocument;

В качестве единственного аргумента используется имя откры­ваемого файла.

Во-первых, мы получили отладочное сообщение о -том, что нам необходимо переопределить метод CreateNewDocu-ment(): «Еггог: you must override CDocTemplate::CreateNew-Document.» Во-вторых, мы получаем сообщение об ошибке.

Что же могло случиться? Заглянем в исходный код метода CDocTemplate::CreateNewDocument():

CDocument* CDocTemplate::CreateNewDocument() {

// default implementation constructs one from CRuntimeClass if (m_pDocClass == NULL) {

TRACEO("Error: you must override

CDocTemplate::CreateNewDocument.\n") ;

ASSERT(FALSE); return NULL;

}

CDocument* pDocument =

(CDocument*)m_pDocClass->CreateObject() ; if (pDocument == NULL) {

TRACE1("Warning: Dynamic create of document type

%hs failed.\n", m_pDocClass->m_lpszClassName) ; return NULL;

}

ASSERT_KINDOF(CDocument, pDocument); AddDocument(pDocument); return pDocument;

Конечно! Мы же в нашей программе оставили указатель на ин­формацию времени исполнения объекта класса CDocument, равную NULL! А здесь первым делом осуществляется проверка, не равен ли нулю этот указатель! Что ж, делать нечего. Для того чтобы мы могли хоть немного продвинуться вперед, изменим нашу программу, доба­вив в начало ее описание класса нашего документа:

class CDoc : public CDocument {

DECLARE_DYNCREATE( CDoc ) public:

CDoc () ; virtual -CDoc(); >;

IMPLEMENT_DYNCREATE( CDoc, CDocument )

CDoc::CDoc()

{

}

CDoc::~CDoc()

{

}



Невооруженным глазом видно, что в начале функции ‘CDocTemplate::MatchDocType] производятся всевоз можные подготовительные действия, а также проверяется, не является ли выбран­ный нами файл ссылкой на другой файл. С точки зрения архитек­туры «документ/представление» все это не очень интересно. Ин­тересное начинается с момента начала перебора списка шабло­нов. Обратите внимание, уважаемый читатель, на то, что для каж­дого шаблона в списке шаблонов вызывается метод CDocTemplate::MatchDocType(). Этот метод определяет, насколько расширение открываемого файла соответствует указанному в шаб­лонах. В качестве аргументов этому методу передаются имя фай­ла, который мы хотим открыть, и переменная типа "указатель на документ", которая равна NULL. Ниже я привожу исходный текст этого метода. Его можно найти в файле docmgr.cpp:

CDocTemplate:Confidence

CDocTemplate::MatchDocType(LPCTSTR IpszPathName,

CDocument*& rpDocMatch)

{

ASSERT(IpszPathName != NULL); rpDocMatch = NULL;

// go through all documents

POSITION pos = GetFirstDocPosition();

while (pos != NULL)

{

CDocument* pDoc = GetNextDoc(pos);

if (AfxComparePath(pDoc->GetPathName(), IpszPathName)) {

// already open

rpDocMatch = pDoc; return yesAlreadyOpen;

}

}

// see if it matches our default suffix CString strFilterExt; if (GetDocString(strFilterExt,

CDocTemplate::filterExt) && !strFilterExt.IsEmpty())

{

// see if extension matches

ASSERT(strFilterExt[0] == 4.’);

LPCTSTR IpszDot = _tcsrchr(IpszPathName, ‘.’);

if (IpszDot != NULL &&

lstrcmpi(IpszDot, strFilterExt) == 0) return yesAttemptNative; // extension matches,

// looks like ours

}

// otherwise we will guess it may work return yesAttemptForeign;

}

Обратите внимание на следующие обстоятельства. Если файл с именем, совпадающим с первым аргументом метода, уже открыт в соответствии с текущим шаблоном, то указатель на открытый документ сохраняется в указателе, переданном в качестве второго документа. Другими словами, создается новая ССЫЛКА на откры­тый ранее документ, при этом вторая копия документа не создает­ся. В этом случае метод возвращает значение yesAlreadyOpen.

Если файл ранее не был открыт в соответствии с текущим шабло­ном, то выбирается подстрока, содержащая расширение файла (помните, читатель, описание метода CDocTemplate::GetDocString()?), из переданной шаблону строки ресурсов. Если эта подстрока сов­падает с расширением открываемого файла, то метод возвращает значение yesAttemptNative. Указатель на документ, переданный в качестве второго аргумента, при этом не изменяется и остается равным NULL.

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

Любознательный читатель, вероятно, тут же задаст мне вопрос. Как же так, ведь в перечислении CDocTemplate: Confidence исполь­зуются еще три значения, noAttempt, maybeAttemptForeign и may-beAttemptNative? А в каких случаях возвращаются эти значения? Здесь я могу только догадываться. Возможно, программисту по­требуется иногда делать предположение о том, что открываемый файл соответствует текущему шаблону вне зависимости от рас­ширения. Например, если шаблон файла предусматривает расши­рение «arj», а пользователь выбрал файл с расширением «aDD» (где «DD» означает две любые цифры), то, наверное, программист сможет, переопределив метод CDocTemplate::MatchDocType(), вер­нуть значение maybeAttemptNative. С другой стороны, если поль­зователь в этом случае выбрал файл с расширением, скажем, «txt», то программист может сделать предположение о том, что откры­ваемый файл не соответствует шаблону, и вернуть значение тау-beAttemptForeign. Наверное, возможны еще какие-то случае, когда программисту потребуется использовать значение noAttempt.



Итак, мы прошли уже достаточно большой путь. Самое главное, поняли, что необходимо делать в начале работы в рамках архитек­туры «документ/представление». Мы разобрались, какие ресурсы нам необходимо подготовить, создали шаблон документа, подго­товили к отображению окно выбора файла. Теперь нам необходи­мо выяснить, что происходит в ходе открытия файла.

CDocument* CDocManager::OpenDocumentFile(LPCTSTR IpszFileName) {

// find the highest confidence

POSITION pos = m_templateList.GetHeadPosition (); CDocTemplate:Confidence bestMatch =

CDocTemplate::noAttempt;

CDocTemplate* pBestTemplate = NULL; CDocument* pOpenDocument = NULL;

TCHAR szPath[_MAX_PATH];

ASSERT(lstrlen(IpszFileName) < _countof(szPath)) ;

TCHAR szTemp[_MAX_PATH];

if (IpszFileName[0] == yV" )

++lpszFileName;
lstrcpyn(szTemp, IpszFileName, _MAX_^ATH);
LPTSTR IpszLast = _tcsrchr(szTemp,
) ;

if (IpszLast != NULL)

*lpszLast = 0; AfxFullPath(szPath, szTemp); TCHAR szLinkName[_MAX_PATH]; if (AfxResolveShortcut(AfxGetMainWnd(),

szPath,

szLinkName,

_MAX_PATH)) lstrcpy(szPath, szLinkName);

while (pos != NULL) {

CDocTemplate* pTemplate =

(CDocTemplate*)m_templateList.GetNext(pos) ; ASSERT_KINDOF(CDocTemplate, pTemplate);

CDocTemplate::Confidence match;

ASSERT(pOpenDocument == NULL);

match = pTemplate->MatchDocType(szPath,

pOpenDocument);

if (match > bestMatch) {

bestMatch = match; pBestTemplate = pTemplate;

}

if (match == CDocTemplate::yesAlreadyOpen)
break;
// stop here

}

if (pOpenDocument != NULL) {

POSITION pos = pOpenDocument->GetFirstViewPosition() ; if (pos != NULL)

{

CView* pView = pOpenDocument->GetNextView(pos);

// get first one

ASSERT_VALID(pView);

CFrameWnd* pFrame = pView->GetParentFrame(); if (pFrame != NULL)

pFrame->ActivateFrame(); else

TRACEO("Error: Can not find a frame for document

to activate.\n");

CFrameWnd* pAppFrame;

if (pFrame != (pAppFrame =

(CFrameWnd*)AfxGetApp()->m_pMainWnd))

{

ASSERT_KINDOF(CFrameWnd, pAppFrame); pAppFrame->ActivateFrame();

}

}

else {

TRACEO("Error: Can not find a view for document

to activate An") ;

}

return pOpenDocument;

if (pBestTemplate == NULL) {

AfxMessageBox(AFX_IDP_FAILED_TO_OPEN_DOC) . return NULL;

return pBestTemplate->OpenDocumentFile(szPath);



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

IDRJDOCUMENT "\nExe-file\nExe-file\n

Executable files (*.exe)\n.exe\n\n"

Мне бы хотелось, чтобы в диалоговом окне мне предлагалось осуществить выбор из ехе-файлов. Естественно, мне необходимо указать идентификатор ресурса в методе lnitlnstance() моего при­ложения. После внесенных изменений он стал выглядеть следую­щим образом:

BOOL CDocViewlApp::Initlnstance () {

#ifdef _AFXDLL

Enable3dControls(); #else

Enable3dControlsStatic (); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( IDRJDOCUMENT,

NULL, NULL, NULL );

AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE );

m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ) ; pMainFrame->UpdateWindow() ;

return TRUE;

}

Посмотрим, правильны ли мои рассуждения и сработает ли в данном случае моя программа. Итак, компилируем… Запуска­ем… Выбираем «File», «Ореп» и…

Окно, появившееся на отображении, вы видите на 13. © Очень надеюсь, что того материала, который мы только что изу­чили, достаточно для понимания того, что происходит при подго­товке стандартного диалога. Надеюсь, что и в этом случае мы ус­пешно решили все поставленные задачи – мы поняли «физику» процесса и осознали возможную степень влияния программиста на процесс создания диалогового окна. Конечно, я мог бы объяс­нять все мелочи, встречающиеся в исходном коде, например, ото­бражение диалогового окна при помощи метода DoModal(), но имеет ли это смысл?



03.02.2010

На основании сказанного делаем вывод:

Строка ресурсов, идентификатор которой указывается при создании шаблона документа, должна состоять из подстрок, отделенных друг от друга символом-разделителем (по умол­чанию – ‘\п’). При этом пятая и четвертая подстроки соот­ветственно хранят наименование фильтра и непосредствен­но фильтр, используемые при формировании стандартно­го диалогового окна открытия файла.

Значит, вот для чего используются шаблоны документов в про­цессе формирования диалога открытия файла! Только для того, чтобы выбрать ПОДСТРОКИ, описывающие тип файла нашего до­кумента! Кажется, мы нашли еще один ответ на те вопросы, кото­рые сами себе и поставили! Теперь становится ясным смысл пято­го аргумента у DoPromptFileName().

Замечу попутно, что программист может изменить значение сим­вола-разделителя. Если он хочет в качестве разделителя исполь­зовать не символ ‘\п\ а любой другой, то все, что ему нужно, – это перекрыть метод CDocTemplate::GetDocString() и при вызове в нем AfxExtractSubString() в качестве четвертого аргумента добавить значение символа-разделителя. И все!

Ну, и, наверное, последний вопрос о подготовке стандартного окна. Допустим, что мы в шаблоне указали интересующее нас рас­ширение файлов. Сможем ли мы в диалоговом окне открыть файл не только с этим расширением? Ответ на этот вопрос следует из текста метода DoPromptFileName(). Разработчики MFC предвиде­ли это. В ЛЮБОМ случае к списку фильтров добавляется фильтр для открытия любого файла «АН Files ( *.*)».



Итак, функция AfxExtractSubString() разбирает строку (второй аргумент функции, LPCTSTR IpszFullString, – указатель на стро­ку), состоящую из подстрок, разделенных символом-разделите-лем(четвертый аргумент функции, TCHAR chSep, – символ раз­делитель), и возвращает указатель на объект класса CString (первый аргумент функции, CString& rString), в который будет записана подстрока с указанным индексом (третий аргумент функции, int iSubString, и есть номер подстроки). Соответствен­но, метод CDocTemplate::GetDocString() тоже возвращает указа­тель на подстроку с указанным индексом, но при этом индекс строки у него не превышает максимального значения, описан­ного в перечислении DocStringlndex, кроме этого, этот метод вынужден пользоваться значением символа-разделителя, при­нятого по умолчанию.



Мы разобрали назначение всех аргументов, за исключением пятого. Казалось бы, к чему при выдаче диалогового окна нам нуж­но знать шаблон документа? Да, наверное, весь шаблон нам знать ни к чему. Но не зря же мы ранее заметили, что шаблон документа является центром архитектуры «документ/представление» и хра­нилищем всей информации!

Обратите внимание, ува­жаемый читатель, что поэтому наш путь лежит к исходному тексту этой функции, кото­рый находится в файле docmgr.cpp:

AFX_STATIC void AFXAPI _AfxAppendFilterSuffix (

CStringS filter, OPENFILENAME& ofn, CDocTemplate*pTemplate, CString* pstrDefaultExt)

{

ASSERT_VALID(pTemplate);

ASSERT_KINDOF(CDocTemplate, pTemplate);

CString strFilterExt, strFilterName;

if (pTemplate->GetDocString(strFilterExt,

CDocTemplate::filterExt) && !strFilterExt.IsEmpty() && pTemplate->GetDocString(strFilterName, CDocTemplate::filterName)&& !strFilterName.IsEmpty())

{

// a file based document template – add to filter list ASSERT(strFilterExt[0] == Л.’); if (pstrDefaultExt != NULL) {

// set the default extension

*pstrDefaultExt = ((LPCTSTR)strFilterExt) + 1;

// skip the Л.’

ofn.lpstrDefExt = (LPTSTR)(LPCTSTR)(*pstrDefaultExt); ofn.nFilterlndex = ofn.nMaxCustFilter + 1;

// 1 based number

// add to filter

filter += strFilterName; ASSERT (! filter. IsEmpty ());// must have a file type name filter += (TCHAR)’\0′; // next string please filter += (TCHAR)’*'; filter += strFilterExt;

filter += (TCHAR)’\0′; // next string please ofn.nMaxCustFilter++;

(CDocManager::OnFileOpen ) Сердцем этого метода яв-

I _____ ляются, в свою очередь, вы-

|{ CDocManager::DoPromptFileName J зовы методов CDocTem-

I—{^AfxAppendFilterSuffix ) plate::GetDocString():

L-[CDocTemplate::GetDocString]

BOOL CDocTemplate::GetDocString(CStrirvg& rString,

enum DocStringlndex i) const

{

return AfxExtractSubString(rString, m_strDocStrings, (int)i); }

Несмотря на кажущуюся простоту вызова, здесь можно заме­тить кое-что интересное. Во-первых, в классе CDocTemplate есть перечисление DocStringlndex:

enum DocStringlndex {

windowTitle, // default window title

docName, // user visible name for default document

fileNewName, // user visible name for FileNew

// for file based documents:

filterName, // user visible name for FileOpen filterExt, // user visible extension for FileOpen // for file based documents with Shell open support: regFileTypeld, // REGEDIT visible registered

// file type identifier regFileTypeName, // Shell visible registered

// file type name

};

(CDocManager::OnFileOpen ] Об этом перечислении мы

I r —————— v достоточно скоро вспомним.

4CDocManager::DoPromptFileNameJ Во-вторых, наконец-то о се-

L-T_AfxAppendFilterSuffix Л бе напоминает та строка,

[ ( —----------------------- которую мы загрузили из ре-

L{CDocTemplate::GetDocStringj Сурс0в при создании шабло-

L_f AfxExtractSubstring] на Документа! Интересно

также, что в совокупности

именно с этой строкой индекс из перечисления передается функ­ции AfxExtractSubString(). Значит, этот индекс является либо номе­ром символа в строке, либо номером какой-то подстроки? Для того чтобы получить ответ и на этот вопрос, нам нужно лезть еще даль­ше в дебри MFC. Ниже я привожу определение (файл afxwin.h) и тек­ст (файл winstr.cpp) функции AfxExtractSubString():

BOOL AFXAPI AfxExtractSubString(CString& rString, LPCTSTR IpszFullString, int iSubString, TCHAR chSep = Лп‘);

BOOL AFXAPI AfxExtractSubString(CString& rString,

LPCTSTR IpszFullString, int iSubString, TCHAR chSep)

{

if (IpszFullString == NULL) return FALSE;

while (iSubString—) {

IpszFullString = _tcschr(IpszFullString, chSep);

if (IpszFullString == NULL)

{

rString.Empty(); // return empty string as well

return FALSE;

}

lpszFullString++; // point past the separator

}

LPCTSTR lpchEnd = _tcschr(IpszFullString, chSep); int nLen = (lpchEnd == NULL) ?

lstrlen(IpszFullString) : (int)(lpchEnd -

IpszFullString);

ASSERT(nLen >= 0);

memcpy(rString.GetBufferSetLength(nLen),

IpszFullString,

nLen*sizedf" (TCHAR) ) ; return TRUE;

}

Так вот оно что! Оказывается, строка, индекс ресурса которой мы указали при создании шаблона документа, состоит из подстрок! Каждая подстрока отделена от предыдущей символом-разделите­лем! Функции передается указатель на строку, состоящую из под­строк (второй аргумент), индекс подстроки (третий аргумент) и зна­чение символа-разделителя (по умолчанию это VT). Первый аргу­мент – это указатель на подстроку с соответствующим индексом. Если мы посмотрим на тексты DoPromptFileName() и __AfxAppend-FilterSuffix(), то придем к выводу, что пятая и четвертая подстроки хранят соответственно фильтр и название фильтра, используемые при формировании стандартного диалогового окна открытия фай­ла! Выскажем также предположение, что в перечислении DocString­lndex в некотором смысле определены назначения подстрок.



Что нам для этого нужно сделать? Практически ничего. MFC уже подготовила за нас все необходимые данные, нам нужно только пару раз щелкнуть мышкой в диалоговом окне – и файл выбран! Есть, правда, два «но».

«Но» первое. Мы хотим знать «физику» процесса подготовки диалогового окна. «Но» второе – мы хотим изменить список выда­ваемых на отображение файлов, используя для этого другой фильтр. Чтобы ответить на этот вопрос, нам, естественно, придет­ся опять лезть в дебри MFC, и, в частности, начать придется с ме­тода DoPromptFileName():

BOOL CDocManager::QpPromptFileName(CStringS fileName,

UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog,

CDocTemplate* pTemplate)

CFileDialog dlgFile(bOpenFileDialog);

CString title;

VERIFY(title.LoadString(nIDSTitle));

dlgFile,m_ofn.Flags |= lFlags;

CString strFilter; CString strDefault; if (pTemplate != NULL) {

ASSERT_VALID(pTemplate);

_Af xAppendFilterSuf f ix (strFilter, *~

dlgFile,m_ofn,

pTemplate,

&strDefault);

}

else {

// do for all doc template

POSITION pos = m_templateList.GetHeadPosition() ; BOOL bFirst = TRUE;

while (pos != NULL) {

CDocTemplate* pTemplate =

(CDocTemplate*)m_templateList.GetNext(pos) ; _AfxAppendFilterSuffix(strFilter,

dlgFile.m_ofn, pTemplate, bFirst ? SstrDefault : NULL) ;

bFirst = FALSE;

}

}

// append the "*.*" all files filter CString allFilter;

VERIFY(allFilter.LoadString(AFX_IDS_ALLFILTER) ) ; strFilter += allFilter;

strFilter += (TCHAR)’\0′; // next string please strFilter += _T(XX*.*");

strFilter += (TCHAR)’\0′; // last string dlgFile.m_ofn.nMaxCustFilter++;

dlgFile.m_ofn.IpstrFilter = strFilter; dlgFile.m_ofn.IpstrTitle = title;

dlgFile.m_ofп.IpstrFile = fileName.GetBuffer(_МАХ_РАТН);

int nResult = dlgFile.DoModal(); fileName.ReleaseBuffer(); return nResult == IDOK;



03.02.2010

Здесь мы увидим кое-что интересное. Так… MFC пытается за­грузить меню и таблицу акселераторов… Но их идентификатор совпадает с идентификатором строки, которую пытается загрузить конструктор базового класса! Отсюда следующие выводы.

Вывод первый. В состав ресурсов, идентификатор которых используется при создании шаблона документа, могут входить строка, меню и таблица акселераторов. Вывод второй (обратный первому). Идентификаторы строки, меню и таблицы акселераторов, которые будут использоваться при работе программы, написанной в соответствии с архитектурой «документ/представление»,должны быть ОДИНАКОВЫМИ!

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

Метод CWinApp::AddDocTemplate(CDocTemplate* pTemplate) ни­чего интересного для нас в себе не содержит. В нем происходит инициализация списка шаблонов и добавление шаблона в этот список. Мне кажется, что более подробное рассмотрение этого вопроса ничего существенного к пониманию нами процесса созда­ния шаблона не добавит.

Пример

Ну, кажется, «охота на ресурсы» завершена успешно. Мы выяс­нили, какие ресурсы могут входить в состав приложения. Обрати­те, пожалуйста, внимание на то, что мы НИ РАЗУ не заглянули в документацию, предлагаемую MSDN, или в какие-нибудь другие пособия по MFC. Исходный код MFC позволил нам самостоятель­но сделать выводы, приведенные выше.

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



Теперь можно вернуться к CMultiDocTemplate::CMultiDocTemplate(). Обратите, пожалуйста, внимание, уважаемый читатель на второй вызов LoadTemplate():

void CMultiDocTemplate::LoadTemplate() {

CDocTemplate::LoadTemplate();

if (m_nIDResource != 0 && m_hMenuShared == NULL) {

HINSTANCE hlnst = AfxFindResourceHandle(

MAKEINTRESOURCE(m_nIDResource), RT_MENU); m_hMenuShared = ::LoadMenu(hlnst,

MAKEINTRESOURCE(m_nIDResource)); m_hAccelTable = ::LoadAccelerators(hlnst,

MAKEINTRESOURCE(m_nIDResource));

}



03.02.2010

Что здесь может быть важным для дальнейшего понимания предупреждает нас о том, что указатели на информацию времени выполнения не должны быть равными NULL. Во-вторых, класс документа должен быть унаследован от CDocument, класс фрейма – от CFrameWnd и, наконец, класс представления – от CView. В-третьих, поля m__nlDResource, m_pDocClass, m_pFrameClass и mjDViewClass инициализируются теми значениями, которые мы указали при вызове конструктора. Здесь нет ничего странного, и это мы могли предполагать. Больший интерес вызывает обращение к функции LoadTemplate():

void CDocTemplate::LoadTemplate() {

if (m_strDocStrings.IsEmpty() &&

!m_strDocStrings.LoadString(m_nIDResource) )

{

TRACE1 ("Warning: no document names irv. string for

template #%d.\n", m_nIDResource) ;

}

if (m_nIDEmbeddingResource != 0 && m_hMenuEmbedding == NULL)

{

// load menu to be used while editing an embedding // (as a server)

HINSTANCE hlnst = AfxFindResourceHandle(

MAKEINTRESOURCE(m_nIDEmbeddingResource) ,

RT_MENU);

m_hMenuEmbedding = ::LoadMenu(hlnst,

MAKEINTRESOURCE(m_nIDEmbeddingResource) ) ; m_hAccelEmbedding = ::LoadAccelerators(hlnst,

MAKEINTRESOURCE(m_nIDEmbeddingResource) ) ;

}

if (m_nIDServerResource != 0 && m_hMenuInPlaceServer == NULL)

{

// load menu to be used while editing in-place // (as a server)

HINSTANCE hlnst = AfxFindResourceHandle(

MAKEINTRESOURCE(m_nIDServerResource) , RT_MENU);

m_hMenuInPlaceServer = ::LoadMenu(hlnst,

MAKEINTRESOURCE(m_nIDServerResource) ) ;

m_hAccelInPlaceServer = ::LoadAccelerators(hlnst,

MAKEINTRESOURCE(m_nIDServerResource) ) ;

}

if (m_nIDContainerResource != 0 && m_hMenuInPlace == NULL) {

// load menu to be used while in-place editing // session (as a container)

HINSTANCE hlnst = AfxFindResourceHandle(

MAKEINTRESOURCE(m_nIDContainerResource), RT_MENU);

m_hMenuInPlace = ::LoadMenu(hlnst,

MAKEINTRESOURCE(m_nIDContainerResource)); m_hAccelInPlace = ::LoadAccelerators(hlnst,

MAKEINTRESOURCE(m nIDContainerResource));

(CMultiDocTemplate::CMultiDocTemplate)

4CMultiDocTemplate::LoadTemplate ]

Взглянем повнимательнее на текст этого метода. Во-перВых, мы заметим, что в поле m_strDocStrings загружается содержимое СТРОКИ, зафужаемой из ресурсов. И, что очень важно, идентификатор этой строки равен тому, который мы указали при вызове конструктора. Если строка по каким-то причинам в ресурсах не найдена, то нам будет выдано отладочное сообщение «Warning: no document names in string for template…» Сле­довательно, MFC подсказывает нам, что в строковом ресурсе долж­ны быть какие-то имена документов. Запомним это. Забегая вперед, скажу, что нам этот факт еще пригодится.



fcMultiDocTemplate::CMultiDocTemplatej Первым, естественно, был вызван сам конструктор. Его исходный текст находится в файле docmulti.cpp:

CMultiDocTemplate::CMultiDocTemplate(UINT nIDResource,

CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass) : CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClags)

{

ASSERT(m_docList.IsEmpty()); m_hMenuShared = NULL; m_hAccelTable = NULL;

m_nUntitledCount = 0; // start at 1

// load resources in constructor if not // statically allocated if (ICDocManager::bStaticInit) LoadTemplate();

(CMultiDocTemplate-CMultiDocTemplateJ Очевидно, что аргументы,
I г~ ~ ^ с которыми производится

L-{CDocTemplate::CDocTemplate ] вызов конструктора CMultl.

DocTemplate(), передаются конструктору родительского класса CDocTemplate, чей исходный код находится в файле doctempl.cpp:

CDocTemplate::CDocTemplate(UINT nIDResource,

CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)

{

ASSERT_VALID_IDR(nIDResource); ASSERT(pDocClass == NULL ||

pDocClass-IsDerivedFrom(RUNTIME_CLASS(CDocument) ) ) ; ASSERT(pFrameClass == NULL ||

pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd) ) ) ASSERT(pViewClass == NULL ||

pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)) ) ;

m_nIDResource = nIDResource; m_nIDServerResource = NULL; m_nIDEmbeddingResource = NULL; m nIDContainerResource = NULL;

m_pDocClass = pDocClass; m_pFrameClass = pFrameClass; m_pViewClass = pViewClass; m_p01eFrameClass = NULL; m_p01eViewClass = NULL;

m_pAttachedFactory = NULL; m_hMenuInPlace = NULL;

m_hAccelInPlace = NULL; m_hMenuEmbedding = NULL; m_hAccelEmbedding = NULL; m_hMenuInPlaceServer = NULL; m_hAccelInPlaceServer = NULL;

// add to pStaticList if constructed as static

// instead of on heap

if (CDocManager:rbStaticInit)

{

m_bAutoDelete = FALSE;

if (CDocManager::pStaticList == NULL)

CDocManager:rpStaticList = new CPtrList; if (CDocManager::pStaticDocManager == NULL)

CDocManager::pStaticDocManager = new CDocManager; CDocManager::pStaticList->AddTail(this);

else

{

m_bAutoDelete = TRUE; // usually allocated on the heap LoadTemplate();



Я постарался откомпилировать эту программу, но получил массу диагностических сообщений, из которых следовало, что класс CDocTemplate является абстрактным и для того, чтобы на­следовать от него другие классы, мне надо переопределить массу методов. Меня это не устраивало. Во-первых, я только изучаю возможности MFC, следовательно, у меня нет желания пере­писывать половину методов. Во-вторых, a priori я уверен, что разработчики MFC довели работу до логического завершения и предоставили программисту классы, унаследованные от CDocTemplate, готовые к немедленному использованию. По­копавшись в исходных текстах MFC, я обнаружил, что от CDocTemplate наследуются два класса – CSingleDocTemplate и CMultiDocTemplate. Судя по привеленным комментариям, пер­вый из этих классов предназначен для работы с однодокументным интерфейсом, а второй – для работы с MDI. Я подумал и ре­шил, что моя программа должна поддерживать многодокумент­ный интерфейс, поэтому в своей программе вместо абстрактного класса CDocTemplate я использовал производный от него класс CMultiDocTemplate. Мой расчет на то, что это не должно оказать особого влияния на понимание архитектуры «документ/пред­ставление», оказался верен. Еще одной особенностью этой программы было то, что я понятия не имел о том, идентификатор каких ресурсов я должен указывать в качестве первого аргумента конструктора и надеялся выяснить это при помощи MFC. Поэтому идентификатор ресурсов я сделал равным нулю.

После внесенных изменений я получил следующий код:

BOOL CDocViewlApp::Initlnstance() {

#ifdef _AFXDLL

Enable3dControls() ; #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings();

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( 0,

NULL, NULL, NULL );

AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();

return TRUE;

Программа откомпилировалась нормально, но, запустив про­грамму на выполнение, я немедленно получил сообщение об ошиб­ке, представленное на 11.

Заглянув в место возникновения ошибки, я узнал, что сообще­ние выдано макросом ASSERT_VALID_IDR, который находится в файле afxpriv.h:

#define ASSERT_VALID_IDR(nIDR) ASSERT((nIDR) != 0 &&

(nIDR) < 0×8000)

Значит, идентификатор ресурсов не может быть нулем или более 0×7fff. Попутно замечу, что в том же файле указано, что номера ресурсов от 0 до 0×6fff могут использоваться програм­мистами, а номера от 0×7000 до 0×7fff зарезервированы для ре­сурсов MFC и стандартных ресурсов Windows. Так… Это не бог весть что, но уже какая-то отправная информация для нас есть. Изменим значение нашего идентификатора ресурсов, скажем, на 0×6fff.. Замечательно! Программа откомпилировалась без ошибок. Я поставил точку прерывания на конструктор шаблона документа и стал смотреть, что происходит во время создания шаблона.



После приведенных изменений метод CDocView1::lnitlnstance() имел следующее содержание:

BOOL CDocViewlApp::Initlnstance() {

#ifdef _AFXDLL

Enable3dControls (); #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate; pDocTemplate = new CDocTemplate( 0,

NULL ),

NULL,

NULL );

AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();

return TRUE;

}



Вспомним, что класс CDocManager является хранилищем указа­телей на шаблоны документов. С другой стороны, метод AddDocTem-plate() в качестве аргумента использует указатель на объект класса CDocTemplate. Следовательно, в этот момент в игру вступает объект класса CDocTemplate. Как следует из названия, он представляет со­бой именно шаблон документа. Подробное описание этого класса мы приведем позже, а сейчас заметим только, что конструктор этого класса имеет следующий вид:

protected:

CDocTemplate(UINT nIDResource,

CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass);

Так… Для инициализации шаблона документа нам нужна ин­формация времени исполнения о каких-то трех составляющих. Судя по названиям этих составляющих, речь в данном случае идет о до­кументе, окне фрейма и окне представления. Значит, этот так на­зываемый шаблон документа – это не просто шаблон, а это не­кий контейнер, который соединяет в себе воедино информацию о трех важнейших составляющих архитектуры «документ/представ­ление». Тем более важно понять, как он создается, какие данные он использует, и как эти данные взаимодействуют друг с другом.



03.02.2010

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

В приложении 1У|ожно не создавать явным образом объект класса CDocManager. Если объект класса CDocManager не был создан вручную до момента добавления первого шаб­лона, то в момент добавления первого шаблона документа этот объект будет создан автоматически.

Давайте вспомним также, что при вызове метода CWinApp::Add-DocTemplate(CDocTemplate*pTemplate) фактически вызывается метод объекта класса CDocManager:: AddDocTemplate( CDocTemplate* pTem­plate). Из этого факта можно сделать еще один вывод.

Для того, чтобы передать нашему приложению информа­цию о том, с какими типами документов ему придется рабо­тать, нам необходимо до создания (или открытия) документа добавить в список шаблонов шаблон нашего документа. Всю остальную работу MFC, вероятно, сделает самостоя­тельно!

Зная все это, можно более подробно описдть причину, которая вызвала появление сообщения об ошибке. Ошибка, из-за которой мы полезли в глубины MFC, заключается в том, что мы не добави­ли в приложение ни одного шаблона документа, из-за чего не был проинициализирован m_pDocManager и мы получили соответствую­щее сообщение. Ура! Причину ошибки мы определили! Но ведь нам нужно не только определить причину ошибки, но и устранить ее! Другими словами, нам необходимо создать шаблон документа, а потом добавить его в список шаблонов.



Здесь мне представляется необходимым еще раз привести ис­ходный код метода CWinApp::AddDocTemplate():

void CWinApp::AddDocTemplate(CDocTemplate* pTemplate) {

if (m_pDocManager == NULL)

m_pDocManager = new CDocManager; m_pDocManager->AddDocTemplate(pTemplate);

}