一旦用户开始使用多于一个的文档模板对象,MFC就会弹出一个New对话框,让用户选择需要的文档模板类型。当在应用程序的InitInstance()函数中调用AddDocTemplate()来注册多个文档模板对象时,MFC无法知道应该使用哪一个文档模板对象来完成用户的“File->New”请求。因此,MFC弹出一个对话框,该对话框列出了 各种注册过的文档模板对象 以允许用户指出应该使用哪一个来调用CDocTemplate::OpenDocumentFile(),来创建一个新文档,以下MFC的源码,有助于理解这种逻辑:
当应用程序启动时,在InitInstance()中由标准AppWizard生成的代码ProcessShellCommand()将直接调用CWinApp::OnFileNew(),调用顺如下:
- CWinApp::OnFileNew()->CDocManager::OnFileNew()
- Void CWinApp::OnFileNew()
- {
- if(m_pDocManager!=NULL)
- {
- m_pDocManager->OnFileNew();//m_pDocManager为CDocManager类型
- }
- }
- CDocManager::OnFileNew()
- {
- //1、如果没有注册文档模板,提示错误
- if( m_templateList.IsEmpty() )
- {
- TRACE0("ERROR:no document templates registered with CWinApp./n");
- AfxMessageBox("AFX_IDP_FAILED_TO_CREATE_DOC");
- return ;
- }
- //2、默认选择第一个文档模板对象
- CDocTemplate* pTemplate=(CDocTemplate*)m_templateList.GetHead();
- //3、如何有多个注册过的文档模板对象,则弹出“New”对话框
- if( m_templateList.GetCount() > 1)
- {
- //显示对话框提示用户从多个文档模板中选择
- CNewTypeDlg dlg(&m_templateList);
- int nID=dlg.DoModal();
- if(nID==IDOK)
- pTemplate=dlg.m_pSelectedTemplate;//4、存储所选择的模板对象的指针
- else
- return;
- }
- //5、使用所选择的 文档模板对象 来创建新文档、框架窗口和视图
- pTemplate->OpenDocumentFile(NULL);
- }
应用程序启动时,如果不想弹出对话框(直接用用户希望的 文档模板 创建新的空文档) 供用户选择文档模板,遵循以下步骤:
第一步:在应用程序的InitInstance()函数中创建各种文档模板对象,并把指向每个对象的指针存储在 应用程序类 的成员变量中。
第二步:使用ClassWizard在 应用程序类中 增加“File->New”菜单命令的句柄,在句柄内,从存储的 文档模板对象的指针 中选择你希望的文档模板对象指针,传递一个NULL参数来请求创建一个新(空)文档,如下:
void CTestApp::OnFileNew()
{
m_ptDefaultTemplate->OpenDocumentFile(NULL);
}
上面解决方案第二步时,标准的MFC消息映射机制 能够保证 在用户选择File->New菜单命令时调用自己的句柄( CMyApp::OnFileNew() ),而不是默认的CWinApp::OnFileNew()函数。
上面代码段中的CNewTypeDlg类在其表中只显示那些文档模板资源ID的fileNewName字串不为空的文档模板对象。如果只有一个文档模板对象满足这一条件,那么就自动返回该对象,作为所“选择”的模板对象,而不显示对话框。为了更好的理解这种行为,看一下CNewTypeDlg()的伪代码,程序清单如下:
- BOOL CNewTypeDlg::OnInitDialog()
- {
- //1、------得到对话框上的列表框的指针
- CListBox* pListBox = (CListBox*)GetDlgItem(AFX_IDC_LISTBOX);
- ASSERT(pListBox != NULL);
- // 使用文档模板填充列表
- pListBox->ResetContent();
- //2、------遍历应用程序的文档模板表
- POSITION pos = m_pList->GetHeadPosition();
- //通过名称在表中增加所有的CDocTemplates
- while (pos != NULL)
- {
- //3、------对每一个文档模板对象
- CDocTemplate* pTemplate = (CDocTemplate*)m_pList->GetNext(pos);
- ASSERT_KINDOF(CDocTemplate, pTemplate);
- //4、在把字符串添加到列表框中之前,检查“fileNewName”字串是否为空
- CString strTypeName;
- if (pTemplate->GetDocString(strTypeName, CDocTemplate::fileNewName) &&
- !strTypeName.IsEmpty())
- {
- // 增加到列表框
- int nIndex = pListBox->AddString(strTypeName);
- if (nIndex == -1)
- {
- EndDialog(-1);
- return FALSE;
- }
- pListBox->SetItemDataPtr(nIndex, pTemplate);
- }
- }
- int nTemplates = pListBox->GetCount();
- if (nTemplates == 0)
- {
- //5、如果为空,则list->error!
- TRACE0("Error: no document templates to select from!/n");
- EndDialog(-1); // abort
- }
- else if (nTemplates == 1)
- {
- // 6、如果只有一个,则自动选择这个文档模板对象 并赋值给item,不显示对话框
- m_pSelectedTemplate = (CDocTemplate*)pListBox->GetItemDataPtr(0);
- ASSERT_VALID(m_pSelectedTemplate);
- ASSERT_KINDOF(CDocTemplate, m_pSelectedTemplate);
- EndDialog(IDOK); // 完成
- }
- else
- {
- // 把所选择的模板对象设置为第一个
- pListBox->SetCurSel(0);
- }
- //至此,将出现New对话框
- return CDialog::OnInitDialog();
- }
每次当启动一个标准AppWizard生成的应用程序时,MFC就会自动的创建一个新(空)文档,同时还有关联的视图和框架窗口。你可能会很想知道,这个空文档是在代码的什么位置被创建的。同时,如果在应用程序启动时不初始创建任何文档?
如果不想再在应用程序启动时打开一个新(空)文档应遵循以下步骤:
- 在InitInstance()函数中修改代码如下:
- BOOL CTestApp::InitInstance()
- {
- // ......
- // 解析 标准shell命令、DDE、打开文档 的命令行
- CCommandLineInfo cmdInfo;
- //取消默认的OnFileNew调用
- cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;
- ParseCommandLine(cmdInfo);
- // 分派命令行中确定的命令
- if (!ProcessShellCommand(cmdInfo))
- return FALSE;
- //......
- }
解释:
从MFC4.0版本开始,在应用程序启动时,程序在何处初始化新文档就不是非常明显了。你可能理解最好要调用CWinApp::OnFileNew(),但是这种调用来自于何处呢?实际上,导致CWinApp::OnFileNew()调用的整个过程都隐藏在这3行代码中:
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);//遍历命令行,搜寻以字符'/'或'-'开头的选项,根据找到的命令行参数,设置cmdInfo对象的成员变量
if(!processShellCommand(cmdInfo))//该函数根据cmdInfo中的m_nShellCommand变量 进行不同的函数调用
return false;
1、以上代码在堆栈中创建了一个名为cmdInfo的CCommandLineInfo对象,MFC将用该对象分析各种转换开关,并把对象的信息存储在该cmdInfo对象的public成员变量中,其中的各种转换开关可能出现在应用程序的命令行中。
CCommandLineInfo类的定义如下:
class CCommandLineInfo
{
//......
enum {FileNew , FileOpen , FilePrint , FilePrintTo , FileDDE , AppUnregistr ,
FileNothing=-1 }m_nShellCommand;
//......
};
2、cmdInfo对象创建时调用该类的(CCommandLineInfo)构造函数,该构造函数初始化m_nShellCommand成员,赋值CCommandLineInfo::FileNew;
3、CWinApp::ParseCommandLine(cmdInfo)函数遍历命令行,搜寻以字符‘/’或 ‘-’开头的选项。对每一个找到的命令行参数
ParseCommandLine()就对CmdInfo对象应用CCommandLineInfo::ParaseParam()方法,该方法根据各种命令行参数设置CmdInfo对象的成员变量,这些参数由PraseCommandLine()传送。只有在命令行中找到实际参数时,才改变m_nShellCommand成员,否则m_nShellCommand保留它上一次的值(即在构造函数中设置的值CCommandLineInfo::FileNew)。
4、CWinApp::ParseCommandLine(cmdInfo)函数返回后CmdInfo对象作为一个参数调用ProcessShellCommand(cmdInfo)函数,该函数执行基于cmdInfo.m_nShellCommand值的相应动作。最常发生的动作如下表所示:
cmdInfo.m_nShellCommand的数值 ProcessShellCommand()进行的函数调用
CCommandLineInfo::FileNew CWinApp::OnFileNew();//这个函数不是虚函数
CCommandLineInfo::FileOpen OpenDocumentFile(cmdInfo.m_strFileName);
CCommandLineInfo::FilePrint m_pMainWnd->SendMessage(WM_COMMAND,
CCommandLineInfo::FilePrintTo ID_FILE_PRINT_DIRECT);
CCommandLineInfo::FileNothing None
因此,可以看出cmdInfo对象的成员变量m_nShellCommand在第2步中,被初始化为了FileNothing,最终导致了ProcessShellCommand()内部调用什么也不做,正好达到了我们的目的。