多文档,新建文件类型选择对话框的设置与设计

    技术2022-05-19  22

    在文档窗口创建的时候 ,它缺省总是会新建一个新文档 ,如果是单文档,则直接新建一个文档,如果是多文档,则会弹出文件类型选择对话框。 那么怎么让它不新建文档呢?就这个问题 , 我对文档视图窗口应用程序启动时的文档创建机制 , 稍稍的浅浅挖了一下 , 做了一个详细的分析 , 希望能够对初学者有所帮助 。 在App文件的InitInstance()函数中,有如下几行代码: CCommandLineInfo  cmdInfo; ParseCommandLine(cmdInfo);           if (!ProcessShellCommand(cmdInfo)) return FALSE; 这几行代码是程序启动时创建新文档的关键代码 。   1: 我们首先来看看让CCommandLineInfo类是个什么东西:( 部分源代码 ) //in afxwin.h  class CCommandLineInfo : public CObject {     public:     // Sets default values    CCommandLineInfo();    BOOL m_bShowSplash;    BOOL m_bRunEmbedded;    BOOL m_bRunAutomated;      enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, AppRegister,    AppUnregister, FileNothing = -1 } m_nShellCommand;    // not valid for FileNew  CString m_strFileName;    . . .    ~CCommandLineInfo();    . . .  };   这里要重点注意enum {FileNew, . . . , FileNothing = -1 }m_nShellCommand; 这里联合类型定义的m_nShellCommand 就是外壳程序执行的命令类型 , 如果m_nShellCommand设置为FileNew ,那么程序就会创建新文档。如果想在文档开始时不创建新文档 , 就必须将m_nShellCommand设置为FilleNothing 。下面我们再看看CCommandLineInfo的构造函数 。 //in appcore.cpp  CCommandLineInfo::CCommandLineInfo()  {         m_bShowSplash   = TRUE;         m_bRunEmbedded  = FALSE;         m_bRunAutomated = FALSE;         m_nShellCommand = FileNew;  } 这里很明白的看出 , 构造函数中 , 缺省将 m_nShellCommand设置为 FileNew 。   2:再来看看ParseCommandLine(cmdInfo); 函数 。   void CWinApp::ParseCommandLine(CCommandLineInfo& rCmdInfo) {     for (int i = 1; i < __argc; i++)     {         LPCTSTR pszParam = __targv[i];         BOOL bFlag = FALSE;         BOOL bLast = ((i + 1) == __argc);         if (pszParam[0] == '-' || pszParam[0] == '/')         {             // remove flag specifier             bFlag = TRUE;             ++pszParam;         }         rCmdInfo.ParseParam(pszParam, bFlag, bLast);     } } 可以看出ParseCommandLine主要是对输入的命令行参数做一些分析 , 并调用ParseParam来进行处理 .继续分析 ParseParam函数 , 查看如下源代码: void CCommandLineInfo::ParseParam(const TCHAR* pszParam,BOOL bFlag,BOOL bLast) {     if (bFlag)     {         USES_CONVERSION;         ParseParamFlag(T2CA(pszParam));     }     else         ParseParamNotFlag(pszParam);       ParseLast(bLast); } 其它的函数撇开不看 , 我们重点来分析一下ParseParamFlag()和ParseLast()函数 。 void CCommandLineInfo::ParseParamFlag(const char* pszParam) {     // OLE command switches are case insensitive, while     // shell command switches are case sensitive       if (lstrcmpA(pszParam, "pt") == 0)         m_nShellCommand = FilePrintTo;     else if (lstrcmpA(pszParam, "p") == 0)         m_nShellCommand = FilePrint;     else if (lstrcmpiA(pszParam, "Unregister") == 0 ||              lstrcmpiA(pszParam, "Unregserver") == 0)         m_nShellCommand = AppUnregister;     else if (lstrcmpA(pszParam, "dde") == 0)     {         AfxOleSetUserCtrl(FALSE);         m_nShellCommand = FileDDE;     }     else if (lstrcmpiA(pszParam, "Embedding") == 0)     {         AfxOleSetUserCtrl(FALSE);         m_bRunEmbedded = TRUE;         m_bShowSplash = FALSE;     }     else if (lstrcmpiA(pszParam, "Automation") == 0)     {         AfxOleSetUserCtrl(FALSE);         m_bRunAutomated = TRUE;         m_bShowSplash = FALSE;     } } ParseParamFlag判断传过来的字符串 ,判断它的参数类型 , 并根据参数类型做不同的处理 。 void CCommandLineInfo::ParseLast(BOOL bLast) {     if (bLast)     {         if (m_nShellCommand == FileNew && !m_strFileName.IsEmpty())             m_nShellCommand = FileOpen;         m_bShowSplash = !m_bRunEmbedded && !m_bRunAutomated;     } } ParseLast会判断是否是是FileNew打开新文档 , 如果是打开新文档 , 并且打开的文档名不为空的话, 就假定用户想打开这个文档 , 把命令设置为FileOpen 。 最后 , 我们可以总结一下ParseCommandLine的作用 . ParseCommandLine的作用主要是分析命令行参数,如果没有命令行参数 ,ParseCommandLine()就假定用户想新建一个文档,于是设置一个FileNew命令,如果命令行参数中有一个文件名,ParseCommandLine()就假定用户想打开该文件,于是设置一个FileOpen命令。   3: 最后 , 我们来重点看看外壳命令解析的主角 : ProcessShellCommand ();(部分源代码) BOOL CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)  {       BOOL bResult = TRUE;       switch (rCmdInfo.m_nShellCommand)      {           case CCommandLineInfo::FileNew:                   if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))                          OnFileNew();                   if (m_pMainWnd == NULL)                          bResult = FALSE;                   break;         case CCommandLineInfo::FileOpen:      . . .         case CCommandLineInfo::FilePrintTo:    . . .         case CCommandLineInfo::FilePrint:      . . .         case CCommandLineInfo::FileDDE:       . . .         case CCommandLineInfo::AppRegister:   . . .         case CCommandLineInfo::AppUnregister: . . .         . . .       } } 代码看到这里 , 一切都很明白了 . ProcessShellCommand分析m_nShellCommand ,并根据m_nShellCommand不同的类型值进行不同的处理 。 再来分析下面两行代码:          CCommandLineInfo cmdInfo;          ParseCommandLine(cmdInfo);        if (!ProcessShellCommand(cmdInfo)) return FALSE;   1: 当CCommandLineInfo cmdInfo进行定义时 , 首先调用构造函数 , 构造函数中m_nShellCommand被设置为FileNew   2: 然后执行ParseCommandLine(cmdInfo);对命令进行分析 。   3: 最后执行ProcessShellCommand (cmdInfo) , ProcessShellCommand ()判断m_nShellCommand为FileNew , 于是调用OnFileNew()创建了一个新的文档 。    这也就是创建新文档的来龙去脉 。   最后, 我们看怎么样解决不想在应用程序启动时的创建新文档的问题: 直接在InitInstance()函数中用如下代码代替原来的几行即可: CCommandLineInfo cmdInfo; cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing; ParseCommandLine(cmdInfo);        if (!ProcessShellCommand(cmdInfo)) return FALSE; 在多文档情况下,此时再单击新建,则会弹出文件类型选择对话框。我们经常需要变更的就是这个默认的"新建文件"操作, 毕竟那个mfc默认的"文档类型选择"对话框无论是从外观上还是功能上都很难满足我们的需要。

    默认"文档类型选择"对话框的定义为: class CNewTypeDlg : public CDialog 可以在VC6.0安装目录的vc98/mfc/src/docmgr.cpp文件中找到. 下面将会仿照这个对话框来设计自定义的"文档类型选择"对话框, 并可以添加自己需要的各种功能。 首先观察mfc"新建文件"操作的过程源码. 对于ID_FILE_NEW消息, 利用VC6.0 IDE的搜索功能,找到: /VC98/MFC/SRC/APPDLG.CPP(25):void CWinApp::OnFileNew()如下: void CWinApp::OnFileNew() { if (m_pDocManager != NULL)        m_pDocManager->OnFileNew(); } m_pDocManager是CWinApp的成员. 类型为CDocManager* 然后继续找到: /VC98/MFC/SRC/DOCMGR.CPP(802):void CDocManager::OnFileNew()如下: void CDocManager::OnFileNew() { if (m_templateList.IsEmpty()) {        TRACE0("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 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 } 在这个函数中, 就可以发现CNewTypeDlg的踪迹。

    工程的名字假定为My, 则我们有CMyApp派生类。 我们所需要做的工作是: 1, 在菜单中添加一个新的菜单项例如ID_FILE_NEW_1, 并添加消息响应函数例如OnFileNew1(); 或者直接为原来的ID_FILE_NEW添加消息响应函数OnFileNew. 这里采用后一种。 2, 添加CMyApp::OnFileNew()后, 不要忘记删除My.cpp中MESSAGE_MAP里的这一项: ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) 3, 在CMyApp::OnFileNew()中, 把上面的CWinApp::OnFileNew()和CDocManager::OnFileNew()的代码拷贝过去, 并加以改造如下: void CMyApp::OnFileNew() { // from CWinApp::OnFileNew(): if (m_pDocManager == NULL) {    return; }

    // from CDocManager::OnFileNew(): // 由于无法访问CDocManager的保护数据成员m_templateList // 故要使用CDocManager::GetFirstDocTemplatePosition() // 和CDocManager::GetNextDocTemplate()接口来获得所有文档模板类指针 // 并将其添加到自定义的列表m_DocTplList中 POSITION pos = m_pDocManager->GetFirstDocTemplatePosition(); if (pos == NULL) {    TRACE0("Error: no document templates registered with CWinApp./n");    AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);    return; }

    // 遍历获得各个文档模板类指针 // 注意, 在CMyApp的定义中添加成员CPtrList m_DocTplList用以放置这些指针 CDocTemplate* pTemplate = NULL;

    while (pos != NULL) {    pTemplate = m_pDocManager->GetNextDocTemplate(pos);    ASSERT(pTemplate != NULL);    this->m_DocTplList.AddTail(pTemplate); }

    // 如果文档类型超过1个则使用自定义的"文档类型选择"对话框CDlgChooseClass // 在资源编辑器中添加一个对话框, 并创建对话框类CDlgChooseClass // 在对话框中放置一个ListBox控件, 用来容纳多个文档类型 // 稍后有具体说明 if (m_DocTplList.GetCount() > 1) {    CDlgChooseClass dlg(&m_DocTplList);   // 输入文档模板类指针的列表    int nID = dlg.DoModal(); // 对话框CDlgChooseClass完成选择文档类型           // 并且可以有其他自定义的操作    if (nID == IDOK)    {     pTemplate = dlg.m_pSelectedTemplate;    }

    else    {     m_DocTplList.RemoveAll(); // 注意: 清空自定义的列表     return;     // none - cancel operation    } }

    m_DocTplList.RemoveAll(); // 注意: 清空自定义的列表

    // 检查返回的值 ASSERT(pTemplate != NULL); ASSERT_KINDOF(CDocTemplate, pTemplate);

    // 选定的文档模板类: 创建新文档 pTemplate->OpenDocumentFile(NULL);    // if returns NULL, the user has already been alerted }

    接下来是设计CDlgChooseClass, 仿照CNewTypeDlg的定义进行(vc98/mfc/src/docmgr.cpp). class CDlgChooseClass : public CDialog { // Construction public:        // 修改默认构造函数, 增加一个参数CPtrList* pList        CDlgChooseClass(CPtrList* pList, CWnd* pParent = NULL);

           // Dialog Data

    public:        CDocTemplate* m_pSelectedTemplate; // 选定的文档模板类        //{{AFX_DATA(CDlgChooseClass)        enum { IDD = IDD_DLG_CHOOSE_CLASS };        // NOTE: the ClassWizard will add data members here        //}}AFX_DATA

           // Overrides        // ClassWizard generated virtual function overrides        //{{AFX_VIRTUAL(CDlgChooseClass) protected:        virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support        //}}AFX_VIRTUAL

           // Implementation protected:        CPtrList* m_pList;        // 文档模板类指针列表

           // Generated message map functions        //{{AFX_MSG(CDlgChooseClass)        virtual BOOL OnInitDialog();        virtual void OnOK();        //}}AFX_MSG        DECLARE_MESSAGE_MAP() };

    实现OnInitDialog()和OnOk()如下:

    基本上是拷贝CNewTypeDlg::OnInitDialog()和CNewTypeDlg::OnOk()的代码, 并略做修改 BOOL CDlgChooseClass::OnInitDialog()  {        // IDC_DOC_TPL_LIST是ListBox的ID        CListBox* pListBox = (CListBox*)GetDlgItem(IDC_DOC_TPL_LIST);        ASSERT(pListBox != NULL);

           // fill with document templates in list        pListBox->ResetContent();

           POSITION pos = m_pList->GetHeadPosition();        // add all the CDocTemplates in the list by name        while (pos != NULL)        {               CDocTemplate* pTemplate = (CDocTemplate*)m_pList->GetNext(pos);               ASSERT_KINDOF(CDocTemplate, pTemplate);

                  // 这里是添加fileNewName来代表各个文档模板。也可以设计其他的显示方式               CString strTypeName;               if (pTemplate->GetDocString(strTypeName, CDocTemplate::fileNewName) &&               !strTypeName.IsEmpty())               {                // add it to the listbox                int nIndex = pListBox->AddString(strTypeName);                       if (nIndex == -1)                       {                             EndDialog(-1);                             return FALSE;                       }                       pListBox->SetItemDataPtr(nIndex, pTemplate);               }

           }

           int nTemplates = pListBox->GetCount();        if (nTemplates == 0)        {               TRACE0("Error: no document templates to select from!/n");               EndDialog(-1); // abort        }        else if (nTemplates == 1)        {               // get the first/only item               m_pSelectedTemplate = (CDocTemplate*)pListBox->GetItemDataPtr(0);               ASSERT_VALID(m_pSelectedTemplate);               ASSERT_KINDOF(CDocTemplate, m_pSelectedTemplate);               EndDialog(IDOK);    // done        } else {        // set selection to the first one (NOT SORTED)        pListBox->SetCurSel(0); }

           return CDialog::OnInitDialog(); }

    void CDlgChooseClass::OnOK() 

    {        // TODO: Add extra validation here        CListBox* pListBox = (CListBox*)GetDlgItem(IDC_DOC_TPL_LIST);        ASSERT(pListBox != NULL);        // if listbox has selection, set the selected template        int nIndex; if ((nIndex = pListBox->GetCurSel()) == -1) {        // no selection        m_pSelectedTemplate = NULL; } else {        m_pSelectedTemplate = (CDocTemplate*)pListBox->GetItemDataPtr(nIndex);        ASSERT_VALID(m_pSelectedTemplate);        ASSERT_KINDOF(CDocTemplate, m_pSelectedTemplate); }

    CDialog::OnOK(); }

    当然, 构造函数也要注意修改: CDlgChooseClass::CDlgChooseClass(CPtrList* pList, CWnd* pParent /* = NULL */) : CDialog(CDlgChooseClass::IDD, pParent) { //{{AFX_DATA_INIT(CDlgChooseClass)    // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT

           m_pList = pList; // 文档模板类指针列表        m_pSelectedTemplate = NULL;   // 最后选择的文档模板类

    }

    到这里就完成了. 进入程序, 选择"文件"-"新建", 或者直接Ctrl+N, 出来的已经是我们自定义的CDlgChooseClass对话框了, 且基本功能和原来默认的完全一样(选择文档类型). 要增加新的功能, 或者改善外观? 这就是对话框设计的问题了。

     


    最新回复(0)