一、问题的提出
我建立了一个名为MDI的多文档工程,想打开两种格式的文件于是在string table更改如下:
IDR_MDITYPE:/n图像/n新建图像/n图像文件(*.aaa;*.bbb)/n.aaa;.bbb/nMDI.Document/nMDI.Document
IDR_OtherTYPE:/n文本/n新建文本/n文本文件(*.ccc;*.ddd)/n.ccc;.ddd/nMDI.Document/nMDI.Document
并且有三个不一样的菜单IDR_MAINFRAME,IDR_MDITYPE,IDR_OtherTYPE。
同时加了两个新类COtherDoc,COtherView。在CMDIApp::InitInstance()中进行了关联。
现在是问题是:新建时选择"新建图像"则菜单显示的是IDR_MDITYPE的菜单,选择"新建文本"则菜单显示的是IDR_OtherTYPE的菜单,这是正确的。但是当打开一个文件时,无论选择图像文件还是文本文件菜单都是IDR_MDITYPE的菜单。
二、新建与打开的不同
新建的顺序
CWinApp::OnFileNew()//m_pDocManager
{
CDocManager::OnFileNew()//m_pDocTemplateList
{
if(没有文档模板)
return;
if(有多个文档模板)
{
弹出对话框CNewTypeDlg让用户选择;
取得模板指针;
}
CMultiDocTemplate::OpenDocumentFile()
{
new一个文档;
创建子框架;
构建frame,doc,view之间的关系;
CDocument::OnNewDocument()
{
DeleteContents();
}
InitialUpdateFrame();
return pDoc;
}
}
}
打开的顺序
CWinApp::OnFileOpen调用CDocManager::OnFileOpen。
CDocManager::OnFileOpen首先显示文件打开对话框(AFX_IDS_OPENFILE),然后调用CWinApp::OpenDocumentFile(FileName)。
CWinApp::OpenDocumentFile(FileName)调用CDocManager::OpenDocumentFile。
CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)遍历文档模板,对每个模板用MatchDocType(szPath,pOpenDocument)匹配文档类型。匹配时主要根据文件扩展名判断。若文件已经在某个文档中打开,则激活文档的第一个视图,否则用匹配的文档模板pBestTemplate->OpenDocumentFile(szPath)。
CDocTemplate::OpenDocumentFile调用CDocument::OnOpenDocument打开文件。
CDocument::OnOpenDocument打开文件,用DeleteContents删除现有文档内容,创建文件对应的CArchive对象loadArchive,Serialize(loadArchive)读文件内容,SetModifiedFlage(FALSE)。
CWinApp::OnFileOpen()
{
CDocManager::OnFileOpen()
{
CDocManager::DoPromptFileName()//弹出打开文件对话框获得文件名(路径)
{
CFileDialog::DoModal();
}
CWinApp::OpenDocumentFile()
{
CDocManager::OpenDocumentFile()
{对每个模板调用
CDocTemplate::MatchDocType(szPath,pOpenDocument)匹配文档类型匹配时主要根据文件扩展名判断
...调整视图和框架;
CMultiDocTemplate::OpenDocumentFile();
{
判断有无现存文档,是否需要保存
新建框架
判断文件是否存在,调用CDocument::OnOpenDocunment/OnNewDocument
}
}
}
}
通过对比发现,打开文件比新建文件多了CDocManager::OpenDocumentFile()这个过程
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;
TCHAR szPath[_MAX_PATH];
ASSERT(lstrlen(lpszFileName) < _countof(szPath));
TCHAR szTemp[_MAX_PATH];
if (lpszFileName[0] == '/"')
++lpszFileName;
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);
}
通过以上代码可以看出该函数的作用主要是对CDocManager::DoPromptFileName()函数中弹出的打开文件对话框返回的文件名(lpszFileName)进行一些处理,并通过遍历模板找出最适合该文件的模板,这里有个关键的函数CDocTemplate::MatchDocType()。通过对每个模板调用该函数找到最好的模板。
CDocTemplate::Confidence CDocTemplate::MatchDocType(LPCTSTR lpszPathName,
CDocument*& rpDocMatch)
{
ASSERT(lpszPathName != NULL);
rpDocMatch = NULL;
// go through all documents
POSITION pos = GetFirstDocPosition();
while (pos != NULL)
{
CDocument* pDoc = GetNextDoc(pos);
if (AfxComparePath(pDoc->GetPathName(), lpszPathName))
{
// 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] == '.');
LPCTSTR lpszDot = _tcsrchr(lpszPathName, '.');
if (lpszDot != NULL && lstrcmpi(lpszDot, strFilterExt) == 0)
return yesAttemptNative; // extension matches, looks like ours
}
// otherwise we will guess it may work
return yesAttemptForeign;
}
该函数的返回类型为枚举类型定义在CdocTemplate中
enum Confidence
{
noAttempt,
maybeAttemptForeign,
maybeAttemptNative,
yesAttemptForeign,
yesAttemptNative,
yesAlreadyOpen
};
通过代码发现该函数首先对所有文档进行文件名的对比,如果相等则返回yesAlreadyOpen,并将当前文档指针传递给CDocManager::OpenDocumentFile()函数中的pOpenDocument(rpDocMatch = pDoc)。若找不到已打开的文档,则通过对比扩展名返回yesAttemptNative或yesAttemptForeign。此处要到了lstrcmpi()函数,由于我的扩展名是”.aaa;.bbb”(对于图像文件),”.ccc;.ddd”(对于文本文件),因此函数总是返回yesAttemptForeign。因此在CDocManager::OpenDocumentFile()中pBestTemplate就只能是图像类型了。
if (match > bestMatch)
{
bestMatch = match;
pBestTemplate = pTemplate;
}
第一次返回后,match = yesAttemptForeign > bestMatch = noAttempt,之后则相等。
综上,这是由于CDocTemplate::MatchDocType()函数使用了lstrcmpi(),不能对多于一个扩展名的文件进行匹配的缘故。
三、解决方法
为此,自定义CMyMultiDocTemplate类继承自CMultiDocTemplate,只对MatchDocType()函数进行重载。
CMyMultiDocTemplate::Confidence CMyMultiDocTemplate::MatchDocType(LPCTSTR lpszPathName,
CDocument*& rpDocMatch)
{
ASSERT(lpszPathName != NULL);
rpDocMatch = NULL;
// go through all documents
POSITION pos = GetFirstDocPosition();
while (pos != NULL)
{
CDocument* pDoc = GetNextDoc(pos);
if (lstrcmpi(pDoc->GetPathName(), lpszPathName) == 0)//AfxComparePath(pDoc->GetPathName(), lpszPathName))此处由于找不到AfxComparePath所在的头文件,存疑。。。
{
// 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] == '.');
LPCTSTR lpszDot = _tcsrchr(lpszPathName, '.');
int iStart = 0;
if(lpszDot != NULL)
{
do
{
CString strExtention = strFilterExt.Tokenize(_T(";"),iStart);//将扩展名分割
if(iStart != -1)
{
if(lstrcmpi(lpszDot, strExtention) == 0)
return yesAttemptNative;
}
}while(iStart != -1);
}
//if (lpszDot != NULL && lstrcmpi(lpszDot, strFilterExt) == 0)
// return yesAttemptNative; // extension matches, looks like ours
}
// otherwise we will guess it may work
return yesAttemptForeign;
}
最后在CMDIApp::InitInstance()中的文档模板改为CMyMultiDocTemplate类。