在很多软件中添加新文件的时候都可以选择创建好几种,每种对应的视图都不一样,而MFC的MDI默认只是对应一种,就算添加模板也会在新建时弹出对话框让用户选择。这种选择甚是烦,现在要靠自定义方式来实现。
首先,描述下MDI的结构和创建文档的过程。
STEP1:在App的InitInstance函数中,程序创建文档模板。并调用App的AddDocTemplate方法来加入模板到模板列表里。
CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_FILE_HTML,//IDR_KHSTYPE, RUNTIME_CLASS(CKHSDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CKHSView)); if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate);STEP2:当在菜单或者别的什么地方点击新建文档时会调用App底层的CWinApp::OnFileNew,这个函数的底层就是调用了一个叫m_pDocManager的OnFileNew()函数。
void CWinApp::OnFileNew() { if (m_pDocManager != NULL) m_pDocManager->OnFileNew(); }STEP3:这个m_pDocManager就很重要了,他是App中一个叫CDocManager类的实例。它管理文档模板的列表。他的m_templateList保存了所有的文档模板,在新建文档时会在这个列表中寻找合适的模板,然后调用模板的OpenDocumentFile函数创建文档。
void CDocManager::OnFileNew() { if (m_templateList.IsEmpty()) { TRACE(traceAppMsg, 0, "Error: no document templates registered with CWinApp.\n"); AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); return; } CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead(); if (m_templateList.GetCount() > 1) { // more than one document template to choose from // bring up dialog prompting user CNewTypeDlg dlg(&m_templateList); INT_PTR nID = dlg.DoModal(); if (nID == IDOK) pTemplate = dlg.m_pSelectedTemplate; else return; // none - cancel operation } ASSERT(pTemplate != NULL); ASSERT_KINDOF(CDocTemplate, pTemplate); pTemplate->OpenDocumentFile(NULL); // if returns NULL, the user has already been alerted }在if判断文中,就是弹出选择文档模板的对话框,真是对他恨得咬牙啊。删除它们就需要重建这个类。下面是重建这个管理类的方法。
class CDocManagerEx : public CDocManager { public: CDocManagerEx(); virtual ~CDocManagerEx(); };STEP2:重载void OnFileNew()函数,可以把MFC的DocManager的OnFileNew函数拷贝过来。这个函数的内容已经看过了,不再累述。CDocManager在atlmfc\src\mfc的docmgr.cpp中。
m_pDocManager = new CDocManagerEx(this); //这就是替换DocManager的地方。 // Register the application's document templates. Document templates // serve as the connection between documents, frame windows and views CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_FILE_HTML,//IDR_KHSTYPE, RUNTIME_CLASS(CKHSDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CKHSView)); if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate);这样之后文档管理类就被成功替换了,如果仅仅是想删除那个可恶的对话框那就到此结束了。但是我们的目标是在画面上点击不同的新建文档菜单可以生成不同的文档视图结构,所以还要改变默认的消息映射结构。默认的是ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew),如果想添加多个映射表。
BEGIN_MESSAGE_MAP(CKHSApp, CWinApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) // Standard file based document commands ON_COMMAND(ID_NEW_HTML, CxxxApp::OnHTMLFileNew) //我们添加的内容 ON_COMMAND(ID_NEW_TXT, CxxxApp::OnTXTFileNew) //我们添加的内容 ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) // Standard print setup command ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) END_MESSAGE_MAP()当然,也要在自己的App类中添加对应的函数。
void CKHSApp::OnHTMLFileNew()
{
m_strNewFileType = STR_NEW_FILETYPE_HTML; //这里是一个标记,表明不同的文件类型,原因是m_pDocManager的OnFileNew并不能添加参数。
if (m_pDocManager != NULL) //所以只能在外部用添加标记的方法来处理,今后有新的方法可以更新也行。
m_pDocManager->OnFileNew();
}
这里标记添加好以后再看看,我们上面替换m_pDocManager时使用了一个this参数,CDocManagerEx可以通过这个指针获取App类中的m_strNewFileType在CDocManagerEx中使用。
void CDocManagerEx::OnFileNew() { ASSERT(m_pKHSApp != NULL); //这里的指针就是构造函数里得到的App类的指针。 if (m_templateList.IsEmpty()) { TRACE(traceAppMsg, 0, "Error: no document templates registered with CWinApp.\n"); AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); return; } CString strType = m_pKHSApp->getNewFileType(); //获取窗口上点击的菜单的类型 CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead(); if (m_templateList.GetCount() > 1) { POSITION pos = m_templateList.GetHeadPosition(); while (pos != NULL) { CDocTemplate* pBestTemplate = (CDocTemplate*)m_templateList.GetNext(pos); ASSERT_KINDOF(CDocTemplate, pBestTemplate); CString strTypeName; if (pBestTemplate->GetDocString(strTypeName, CDocTemplate::fileNewName) && //通过这个函数调用GetDocString获取模板的新建文档 !strTypeName.IsEmpty()) //的文件名,这个和传递进来的参数对比,如果一致就 { //设置当前的模板为匹配一致的模板。 if (strTypeName == strType) { pTemplate = pBestTemplate; } } } } ASSERT(pTemplate != NULL); ASSERT_KINDOF(CDocTemplate, pTemplate); pTemplate->OpenDocumentFile(NULL); }但是,但是,但是,GetDocString获取的文件名在哪里设置呢?我们怎么知道他得到的内容是什么呢?看看创建模板的时候使用的参数吧。
CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_FILE_HTML,//IDR_KHSTYPE, RUNTIME_CLASS(CKHSDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CKHSView));第一个参数ID的内容看起来是一个ICON的ID,实际上它也是一个String类型的资源。打开rc文件可以发现这个ID他的内容是
\nHTML\nHTML\n\n\nHTML.Document\nHTML.Document这里有很多个段他们对应的内容就是如下的内容
enum DocStringIndex { 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: regFileTypeId, // REGEDIT visible registered file type identifier regFileTypeName, // Shell visible registered file type name };这个就是GetDocString第二个参数的内容,可以一个一个是尝试具体的内容。这样一来就完成了整个替换设计。慢慢尝试吧。