MfC打开过程详解及应用
本文主要介绍:在MFC中,菜单打开命令的响应过程。
一、MFC打开命令的响应过程:
File->Open 对应的ID为ID_FILE_OPEN,其响应过程如下:
注:如果自己已将ID_FLIE_OPEN在MFC中重载了,则会直接响应重载函数,不会按以下过程响应。
1.点击File->Open,首先响应的函数为: CWinApp::OnFileOpen(),其函数原型为:
void CWinApp::OnFileOpen()
{
ASSERT(m_pDocManager != NULL);
m_pDocManager->OnFileOpen();
}
2.由上面的程序可知,接着调用的是: CDocManager::onFileOpen(),该函数功能是:显示打开文件的对话框,并获取文件的路径,其函数原型为:
void CDocManager::OnFileOpen()
{
// prompt the user (with all document templates)
CString newName; //弹出打开文件的对话框,获取文件路径
if (!DoPromptFileName(newName, AFX_IDS_OPENFILE,OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))
return; // open cancelled
AfxGetApp()->OpenDocumentFile(newName);
// if returns NULL, the user has already been alerted
}
3.接着调用函数: CWinApp::OpenDocumentFile(LPCTSTR lpszFileName),其函数原型为:
CDocument* CWinApp::OpenDocumentFile(LPCTSTR lpszFileName)
{
ASSERT(m_pDocManager != NULL);
return m_pDocManager->OpenDocumentFile(lpszFileName);
}
4.再调用函数: CDocManager::OpenDocumentFile(LPCTSTR lpszFileName),该函数遍历文档模板,对每个文档进行匹配,若该文件已经在某个文档中打开,则会激活该文档视图,否则用匹配的文档模板,调用下一个打开函数,其原型为:
CDocument* CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)
{
// find the highest confidence
POSITION pos = m_templateList.GetHeadPosition();
CDocTemplate::Confidence bestMatch = CDocTemplate::noAttempt;
CDocTemplate* pBestTemplate = NULL;
CDocument* pOpenDocument = NULL;
lstrcpyn(szTemp, lpszFileName, _MAX_PATH);
LPTSTR lpszLast = _tcsrchr(szTemp, '"');
if (lpszLast != 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
TRACE0("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
{
TRACE0("Error: Can not find a view for document to activate.\n");
}
return pOpenDocument;
}
if (pBestTemplate == NULL)
{
AfxMessageBox(AFX_IDP_FAILED_TO_OPEN_DOC);
return NULL;
}
return pBestTemplate->OpenDocumentFile(szPath);
}
5.调用函数:CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,BOOL bMakeVisible),该函数是多文档打开函数,先创建文档的框架窗口,然后判断路径是否为空,如果为空,则重新设置文档路径;最后,调用InitialUpdateFrame显示框架窗口。其原型为:
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,BOOL bMakeVisible)
{
CDocument* pDocument = CreateNewDocument();
if (pDocument == NULL)
{
TRACE0("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)
{
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
delete pDocument; // explicit delete on error
return NULL;
}
ASSERT_VALID(pFrame);
if (lpszPathName == NULL)
{
// create a new document - with default document name
SetDefaultTitle(pDocument);
// avoid creating temporary compound file when starting up invisible
if (!bMakeVisible)
pDocument->m_bEmbedded = TRUE;
if (!pDocument->OnNewDocument())
{
// user has be alerted to what failed in OnNewDocument
TRACE0("CDocument::OnNewDocument returned FALSE.\n");
pFrame->DestroyWindow();
return NULL;
}
// it worked, now bump untitled count
m_nUntitledCount++;
}
else
{
// open an existing document
CWaitCursor wait;
if (!pDocument->OnOpenDocument(lpszPathName))
{
// user has be alerted to what failed in OnOpenDocument
TRACE0("CDocument::OnOpenDocument returned FALSE.\n");
pFrame->DestroyWindow();
return NULL;
}
pDocument->SetPathName(lpszPathName);
}
InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
return pDocument;
}
6.最后调用函数:CDocument::OnOpenDocument(LPCTSTR lpszPathName),该函数一般在“***Doc.cpp”中重载(*** 为工程名),因为不同的文件打开的过程不同,所以可以根据需求,在“***Doc.cpp”中改写OnOpenDocument(LPCTSTR lpszPathName)函数,从而打开相应的文件。其默认生成原型为:
BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)
{
if (IsModified())
TRACE0("Warning: OnOpenDocument replaces an unsaved document.\n");
CFileException fe;
CFile* pFile = GetFile(lpszPathName,CFile::modeRead|CFile::shareDenyWrite, &fe);
if (pFile == NULL)
{
ReportSaveLoadException(lpszPathName, &fe,FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
return FALSE;
}
DeleteContents();
SetModifiedFlag(); // dirty during de-serialize
CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);
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(lpszPathName, 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打开命令的响应过程如下:
1.首先调用CWinApp::OnFileOpen(),该函数调用CDocManager::OnFileOpen()函数;
2.CDocManager::OnFileOpen()显示打开文件的对话框,并获取文件的路径,然后该函数调用CWinApp::OpenDocumentFile(LPCTSTR lpszFileName)函数;
3.CWinApp::OpenDocumentFile(LPCTSTR lpszFileName)函数直接调用CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)函数;
4.CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)函数遍历文档模板,对每个文档进行匹配,若该文件已经在某个文档中打开,则会激活该文档视图,否则用匹配的文档模板,调用CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,BOOL bMakeVisible)函数;
5.CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,BOOL bMakeVisible)函数先创建文档的框架窗口,然后判断路径是否为空,如果为空,则重新设置文档路径;接着,调用InitialUpdateFrame显示框架窗口,最后调用CDocument::OnOpenDocument(LPCTSTR lpszPathName)函数;
6.CDocument::OnOpenDocument(LPCTSTR lpszPathName)函数一般在“***Doc.cpp”中重载(*** 为工程名),因为不同的文件打开的过程不同,所以可以根据需求,在“***Doc.cpp”中改写OnOpenDocument(LPCTSTR lpszPathName)函数,从而打开相应的文件。
三、运用
由于打开命令的响应过程,是一系列函数依次调用,因此,可以调用其中的一个环节,来打开文件。以打开图片为例,介绍一下应用:
在打开文件时,弹出的对话框默认的文件“所有文件(*.*)”,如下图:
有时候我们需要打开特定的文件类型,如BMP、Jpg、Tif等类型,即需要弹出下面的对话框:
该功能实现的过程如下:(***代表工程名)
1.在“***.cpp”中重载ID_FILE_OPEN的响应函数OnFileOpen() ,即对ID_FILE_OPEN在C***App的类中添加一个响应函数,函数名为OnFileOpen();
注:如果ID_FILE_OPEN已经重载为:ON_COMMAND(ID_FILE_OPEN, &CWinAppEx::OnFileOpen),要把这行代码屏蔽,不然点击打开时,仍默认原来的响应,不会响应自己新重载的函数。
2.编写C***App::OnFileOpen()函数:
void CMyIMGApp::OnFileOpen()
{
// TODO: Add your command handler code here
CString strOpenFilter = "所有文件(*.*)|*.bmp; *.dib; *.gif; *.jpg; *.jpe; *.jpeg; *.tif; *.tiff; *.raw|位图文件 (*.bmp;*.dib)|*.bmp; *.dib|GIF文件 (*.gif)|*.gif|JPEG文件 (*.jpg;*.jpe;*.jpeg)|*.jpg; *.jpe; *.jpeg|TIFF文件 (*.tif;*.tiff)|*.tif; *.tiff|RAW文件(*.raw)|*.raw|";
CFileDialog FileDlg(TRUE, "*.bmp", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, strOpenFilter);
if (FileDlg.DoModal() == IDOK)
OpenDocumentFile(FileDlg.m_ofn.lpstrFile);
}
该函数是直接调用了上述环节的CWinApp::OpenDocumentFile(LPCTSTR lpszFileName)函数,从而实现文件的打开。
说明:点击打开菜单时,直接的响应函数是自己重载后的函数,通过重载只是改变了前两个环节,最后通过调用OpenDocumentFile(LPCTSTR lpszFileName)函数,使后面函数依次被调用。
到此知道了文档是如何被打开的,接着往下看...
当打开一个文档时,MFC如何选择所使用的文档模板对象?
当把一个 文档名 作为 变量 来调用CWinApp::OpenDocumentFile()时,MFC是 如何选择 用来 打开一个给定文档的 文档模板对象的呢?
如果跟踪CWinApp::OpenDocumentFile()函数的整个调用过程,会发现该函数只是通过存储在 应用程序对象 中的m_pDocManager指针简单的调用CDocManager::OpenDocumentFile()函数,后面的这个函数完成选择与文档扩展名最匹配的文档模板对象的全部工作。
为了完成这一部分的处理,CDocManager::OpenDocumentFile()函数遍历它的文档模板对象表,并为表的每个成员(成员为文档模板对象)调用CDocTemplate::MatchDocType()函数,传递的参数是 要打开的 文档的 文件名。
而CDocTemplate::MatchDocType()执行下面的逻辑:
(1)遍历当前打开的文档对象的列表,查找它们中的哪一个的文件名与我们的变量相同。如果能找到,返回 指向这个文档对象的指针 和 yesAlreadyOpen确认值。
(2)如果文件名与所有打开的文档都不匹配,那么检查它的扩展名是否与 文档模板字符串 中的filterExt字串匹配。如果匹配的话,返回一个yesAttemptNative确认值。否则返回yesAttemptForeign确认值。
在遍历中,CDocManager::OpenDocumentFile()函数始终保留一个指向文档模板对象的指针,该对象提供了最后的确认,最后的确认值决定了CDocManager::OpenDocumentFile()函数将要发生的动作。
@如果文件名与一个已经打开的文档对象匹配(确认值==yesAlreadyOpen),那么激活这个文档的框架窗口和视图。
@如果文件的扩展名与某个文档模板对象的filterExt字串匹配(确认值==yesAlreadyNative),那么把这个文档模板对象作为最好的候选模板来使用,并调用它的OpenDocumentFile()函数。
@如果文件名的扩展名与任何文档模板对象的filterExt都不匹配(确认值==yesAlreadyForeign),那么随即的选择第一个注册过的文档模板对象作为最匹配的对象,并调用它的OpenDocumentFile()函数。注意:这种随即的选择通常都是错误的,在这种情况下,文档对象的Serialize()函数将可能触发一个错误异常,CDocument::OnOpenDocument()函数的默认句柄将捕获该异常并显示一条错误信息。
从上面的分析中可以看出,如果打算把自己的 某个文档模板对象 作为所有 扩展名与特殊filterExt字串 不匹配 的文档的默认模板,那么该 默认的文档模板对象 应该是AddDocTemplate()注册的第一个模板。
其中核心的CDocManager::OpenDocumentFile()函数的伪代码如下:
深入挖掘IDR_MAINFRAME