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

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

Наверное, из описания класса 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 };



Я надеюсь, что читатель помнит о том, что пока при создании шаблона нашего документа указатель на информацию времени исполнения класса окна представления равен нулю. Следова­тельно, пока мы можем на некоторое время оставить вопрос о создании окна представления и вернуться в метод CMDIChildWnd::LoadFrame().

Из текста метода CMDIChildWnd::LoadFrame() кроме сказанно­го выше становится понятным и назначение начальной подстроки строкового ресурса – эта подстрока определяет заголовок созда­ваемого окна фрейма.

Теперь, когда мы знаем все о том, как происходит создание нового документа и создание нового фрейма, нам придется вернуться к методу ь CMultiDocTemplate::OpenDocumentFile() и продолжить его рассмотрение на имя откры­ваемого файла. Текст этого метода, приведенный ниже, можно най­ти в файле doccore.cpp:

BOOL CDocument::OnOpenDocument(LPCTSTR IpszPathName) {

if (IsModifiedO )

TRACEO("Warning: OnOpenDocument replaces an unsaved

document.\n") ;

CFileException fe;

CFile* pFile = GetFile(IpszPathName,

CFile::modeRead|CFile::shareDenyWrite,

&fe) ; if (pFile == NULL) {

ReportSaveLoadException(IpszPathName,

return FALSE;

DeleteContents ();

&fe, FALSE,

AFX_IDP FAILED TO_OPEN_DOC);

SetModifiedFlag(); // dirty during de-serialize

CArchive loadArchive(pFile,

CArchive::load | CArchive:rbNoFlushOnDelete); loadArchive.m_pDocument = this; loadArchive.m_bForceFlat = FALSE; TRY {

CWaitCursor wait;

if (pFile->GetLength() != 0)

Serialize(loadArchive); // load me

loadArchive.Close(); ReleaseFile(pFile, FALSE);

}

CATCH_ALL(e) {

ReleaseFile(pFile, TRUE);

DeleteContents(); // remove failed contents

TRY {

ReportSaveLoadException(IpszPathName, e, FALSE,

AFX_IDP_FAILED_TO_OPEN_DOC);

}

END_TRY

DELETE_EXCEPTION(e); return FALSE;

}

END_CATCH_ALL

SetModifiedFlag(FALSE); // start off with unmodified

return TRUE;

}

Я прошу читателя уделить этому методу ОСОБОЕ внимание. Немного опережая события, скажу, что именно здесь мы уви­дим, в какой момент из файла получается документ, какой ме­тод необходимо переопределить, чтобы всю рутинную работу возложить на MFC.



Итак, подведем промежуточные итоги. К настоящему времени мы выяснили, что необходимо сделать для того, чтобы 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()

{

}



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

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);



Но взглянем на это окно повнимательнее. В основном меня все в нем устраивает, за исключением того, что мне предлагается осу­ществить выбор из всех имеющихся файлов. А если я хочу, чтобы мне был показаны только файлы с расширением «ехе» или «txt»? Могу ли я каким-нибудь образом изменить фильтр? А на этот во­прос ответить не так просто, как на первый. Здесь ответ не лежит на поверхности. Что ж, делать нечего, придется опять разбираться с исходным текстом MFC.

Поставим точку прерывания на методе CWinApp::OnFileOpen() в файле appdlg.cpp и запустим программу на выполнение. Стоп! Выполнение программы приостановилось. Исходный текст мето­да, на котором мы остановились, приведен ниже:

docmgr.cpp берем исходный код

метода CDocManager::OnFileOpen():

void CDocManager::OnFileOpen() {

// prompt the user (with all document templates) CString newName;

if (!DoPromptFileName(newName,

AFX_IDS_OPENFILE, OFN_HIDEREADONLY | FN_FILEMUS TEX1ST,

TRUE, NULL))

return; // open cancelled

AfxGetApp()->OpenDocumentFile(newName)/

// if returns NULL, the userhas already been alerted

CDocManager::OnFileOpen ]

Здесь же явно видно, что процесс открытия файла J разделен на два этапа. Этап

первый – вызов метода DoPromptFileNameO (забегая вперед, скажу, что именно эта функция обеспечивает подготов­ку и выдачу диалогового окна для выбора файла). Этап второй -непосредственно открытие файла и преобразование его в доку­мент. Наверное, логично будет, если мы изложение материала так же разделим на две части. В первой части мы должны рассмот­реть, каким образом формируется стандартное диалоговое окно для выбора открываемого файла. Кроме этого, мы должны опре­делить, каким образом мы можем повлиять на процесс формиро­вания этого окна. Во второй части нам необходимо понять, что про­исходит после того, как имя открываемого файла определено. Дру­гими словами, нам нужно понять, как из файла рождается доку­мент.