

Программирование на языке MFC
Мой второй блог в серии программирования
Что мы можем здесь увидеть? То, что в описании этого класса присутствует поле rnJTemplateList класса CPtrList, только подтверждает нашу догадку о том, что de facto объекты класса CDocManager являются списками указателей на какие-то шаблоны. Для того чтобы убедиться, что наша догадка верна, достаточно взглянуть на исходный код методов 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. Наверное, именно этим методом и следует воспользоваться при добавлении шаблона документа!
читать отзывы (0)
Наверное, из описания класса 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(), но имеет ли это смысл?
На основании сказанного делаем вывод:
Строка ресурсов, идентификатор которой указывается при создании шаблона документа, должна состоять из подстрок, отделенных друг от друга символом-разделителем (по умолчанию – ‘\п’). При этом пятая и четвертая подстроки соответственно хранят наименование фильтра и непосредственно фильтр, используемые при формировании стандартного диалогового окна открытия файла.
Значит, вот для чего используются шаблоны документов в процессе формирования диалога открытия файла! Только для того, чтобы выбрать ПОДСТРОКИ, описывающие тип файла нашего документа! Кажется, мы нашли еще один ответ на те вопросы, которые сами себе и поставили! Теперь становится ясным смысл пятого аргумента у 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(), то придем к выводу, что пятая и четвертая подстроки хранят соответственно фильтр и название фильтра, используемые при формировании стандартного диалогового окна открытия файла! Выскажем также предположение, что в перечислении DocStringlndex в некотором смысле определены назначения подстрок.
Что нам для этого нужно сделать? Практически ничего. 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;
Здесь мы увидим кое-что интересное. Так… 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));
}
Что здесь может быть важным для дальнейшего понимания предупреждает нас о том, что указатели на информацию времени выполнения не должны быть равными 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);
Так… Для инициализации шаблона документа нам нужна информация времени исполнения о каких-то трех составляющих. Судя по названиям этих составляющих, речь в данном случае идет о документе, окне фрейма и окне представления. Значит, этот так называемый шаблон документа – это не просто шаблон, а это некий контейнер, который соединяет в себе воедино информацию о трех важнейших составляющих архитектуры «документ/представление». Тем более важно понять, как он создается, какие данные он использует, и как эти данные взаимодействуют друг с другом.
Взглянув на этот текст, мы можем сделать вывод, который в значительной степени облегчит нам жизнь.
В приложении 1У|ожно не создавать явным образом объект класса CDocManager. Если объект класса CDocManager не был создан вручную до момента добавления первого шаблона, то в момент добавления первого шаблона документа этот объект будет создан автоматически.
Давайте вспомним также, что при вызове метода CWinApp::Add-DocTemplate(CDocTemplate*pTemplate) фактически вызывается метод объекта класса CDocManager:: AddDocTemplate( CDocTemplate* pTemplate). Из этого факта можно сделать еще один вывод.
Для того, чтобы передать нашему приложению информацию о том, с какими типами документов ему придется работать, нам необходимо до создания (или открытия) документа добавить в список шаблонов шаблон нашего документа. Всю остальную работу MFC, вероятно, сделает самостоятельно!
Зная все это, можно более подробно описдть причину, которая вызвала появление сообщения об ошибке. Ошибка, из-за которой мы полезли в глубины MFC, заключается в том, что мы не добавили в приложение ни одного шаблона документа, из-за чего не был проинициализирован m_pDocManager и мы получили соответствующее сообщение. Ура! Причину ошибки мы определили! Но ведь нам нужно не только определить причину ошибки, но и устранить ее! Другими словами, нам необходимо создать шаблон документа, а потом добавить его в список шаблонов.
Здесь мне представляется необходимым еще раз привести исходный код метода CWinApp::AddDocTemplate():
void CWinApp::AddDocTemplate(CDocTemplate* pTemplate) {
if (m_pDocManager == NULL)
m_pDocManager = new CDocManager; m_pDocManager->AddDocTemplate(pTemplate);
}
