

Программирование на языке 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 };
читать отзывы (1)
Я надеюсь, что читатель помнит о том, что пока при создании шаблона нашего документа указатель на информацию времени исполнения класса окна представления равен нулю. Следовательно, пока мы можем на некоторое время оставить вопрос о создании окна представления и вернуться в метод 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 (забегая вперед, скажу, что именно эта функция обеспечивает подготовку и выдачу диалогового окна для выбора файла). Этап второй -непосредственно открытие файла и преобразование его в документ. Наверное, логично будет, если мы изложение материала так же разделим на две части. В первой части мы должны рассмотреть, каким образом формируется стандартное диалоговое окно для выбора открываемого файла. Кроме этого, мы должны определить, каким образом мы можем повлиять на процесс формирования этого окна. Во второй части нам необходимо понять, что происходит после того, как имя открываемого файла определено. Другими словами, нам нужно понять, как из файла рождается документ.
