单文档,自定义打开/保存对话框

在多文档下,可以通过在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_OtherTYPE的字符串资源时,就必须增加相应的菜单资源,这种作法似乎不是MFC的初衷。如画图等程序只有一个菜单,在新建时不会弹出文件类型选择提示框(不同与多文档),而打开和保存时却能选择多种文件类型。要是通过添加字符串来增加文件类型,虽然在打开和保存时能选择多种文件类型,但是新建时会弹出文件类型选择提示框,而且在一个文档打开的情况下,若新建或打开另一类型的文档,不会提示保存当前文档,而是直接在新的窗口打开。这显然与单文档的“定义”不符。为此,此处通过修改CDocManager::DoPromptFileName()来达到打开与保存多种文件类型的目的。工程名为SDI。
首先考虑打开对话框。第一步是要弄清,打开对话框是什么时候(在哪)弹出来的?
默认情况下,CSDIApp调用CWinApp::OnFileOpen方法处理FileOpen事件:
ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
CWinApp::OnFileOpen又调用CDocManager::OnFileOpen处理FileOpen事件:
void CWinApp::OnFileOpen()
{
       ENSURE(m_pDocManager != NULL);
       m_pDocManager->OnFileOpen();
}
CDocManager::OnFileOpen显示对话框与用户交互,然后调用CWinApp::OpenDocumentFile方法:
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
}
显然,一种可能的解决办法是绕过CWinApp和CDocManager,在CSDIApp::OnFileOpen方法中显示自定义对话框,然后调用CWinApp::OpenDocumentFile方法。
ON_COMMAND(ID_FILE_OPEN, &CSDIApp::OnFileOpen)
void CSDIApp::OnFileOpen()
{
       LPCTSTR szFilter = L"图像文件(*.aaa;*.bbb)|*.aaa;*.bbb|文本文件(*.ccc;*.ddd)|*.ccc;*.ddd||";  
       CFileDialog oFileDlg(TRUE, NULL, NULL, 4|2, szFilter);

       if(oFileDlg.DoModal() == IDOK)
        OpenDocumentFile(oFileDlg.GetFileName());
        // CSDIApp不需要重写CWinApp::OpenDocumentFile方法
}
现在考虑保存对话框。第一步仍然是弄清,保存对话框是什么时候(在哪)弹出来的?
分发消息时,调用了CDocument::DoFileSave虚方法:
BOOL CDocument::DoFileSave()
{
       DWORD dwAttrib = GetFileAttributes(m_strPathName);
       if (dwAttrib & FILE_ATTRIBUTE_READONLY)
       {
               // we do not have read-write access or the file does not (now) exist
               if (!DoSave(NULL))
               {
                      TRACE(traceAppMsg, 0, "Warning: File save with new name failed./n");
                     return FALSE;
               }
        }
        else
       {
               if (!DoSave(m_strPathName))
              {
                     TRACE(traceAppMsg, 0, "Warning: File save failed./n");
                     return FALSE;
               }
       }
        return TRUE;
}
CDocument::DoFileSave调用CDocument::DoSave,也是一个虚方法:(注:没有DoSaveAs方法,lpszPathName参数决定了CDocument::DoSave是表现为“保存”还是“另存为”。)
BOOL CDocument::DoSave(LPCTSTR lpszPathName, BOOL bReplace /*= TRUE*/)
 // Save the document data to a file
 // lpszPathName = path name where to save document file
 // if lpszPathName is NULL then the user will be prompted (SaveAs)
 // note: lpszPathName can be different than 'm_strPathName'
 // if 'bReplace' is TRUE will change file name if successful (SaveAs)
 // if 'bReplace' is FALSE will not change path name (SaveCopyAs)
{
       CString newName = lpszPathName;
       if (newName.IsEmpty())
       {
              CDocTemplate* pTemplate = GetDocTemplate();
              ASSERT(pTemplate != NULL);

               newName = m_strPathName;
               if (bReplace && newName.IsEmpty())
                {
                        newName = m_strTitle;
                        // check for dubious filename
                        int iBad = newName.FindOneOf(_T(":///"));
                        if (iBad != -1)
                                newName.ReleaseBuffer(iBad);

                        // append the default suffix if there is one
                        CString strExt;
                        if (pTemplate->GetDocString(strExt, CDocTemplate::filterExt) &&
                          !strExt.IsEmpty())
                        {
                                ASSERT(strExt[0] == '.');
                                int iStart = 0;
                                newName += strExt.Tokenize(_T(";"), iStart);
                        }
                }

               if (!AfxGetApp()->DoPromptFileName(newName,
                 bReplace ? AFX_IDS_SAVEFILE : AFX_IDS_SAVEFILECOPY,
                  OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, FALSE, pTemplate))
                     return FALSE;       // don't even attempt to save
       }

        CWaitCursor wait;

        if (!OnSaveDocument(newName))
       {
              if (lpszPathName == NULL)
              {
                      // be sure to delete the file
                      TRY
                      {
                              CFile::Remove(newName);
                      }
                     CATCH_ALL(e)
                      {
                             TRACE(traceAppMsg, 0,
                             "Warning: failed to delete file after failed SaveAs./n");
                            DELETE_EXCEPTION(e);
                     }
                        END_CATCH_ALL
                }
               return FALSE;
       }

       // reset the title and change the document name
       if (bReplace)
               SetPathName(newName);

       return TRUE;        // success
}
CDocument::DoSave虚方法调用CWinApp::DoPromptFileName方法弹出保存对话框,后者又调用CDocManager::DoPromptFileName虚方法(情形与打开文件时相同):
BOOL CDocManager::DoPromptFileName(CString& fileName, UINT nIDSTitle,
 DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate)
{
       CFileDialog dlgFile(bOpenFileDialog, NULL, NULL,
       OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, NULL, NULL, 0);

       CString title;
       VERIFY(title.LoadString(nIDSTitle));

       dlgFile.m_ofn.Flags |= lFlags;

       CString strFilter;
       CString strDefault;
       if (pTemplate != NULL)
       {
              ASSERT_VALID(pTemplate);
              _AfxAppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate, &strDefault);
       }
       else
       {
              // do for all doc template
              POSITION pos = m_templateList.GetHeadPosition();
              BOOL bFirst = TRUE;
              while (pos != NULL)
              {
                     pTemplate = (CDocTemplate*)m_templateList.GetNext(pos);
                     _AfxAppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate,
                     bFirst ? &strDefault : NULL);
                     bFirst = FALSE;
              }
       }

       // append the "*.*" all files filter
       CString allFilter;
       VERIFY(allFilter.LoadString(AFX_IDS_ALLFILTER));
       strFilter += allFilter;
       strFilter += (TCHAR)'/0';   // next string please
       strFilter += _T("*.*");
       strFilter += (TCHAR)'/0';   // last string
       dlgFile.m_ofn.nMaxCustFilter++;

       dlgFile.m_ofn.lpstrFilter = strFilter;
       dlgFile.m_ofn.lpstrTitle = title;
       dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH);

       INT_PTR nResult = dlgFile.DoModal();
       fileName.ReleaseBuffer();
       return nResult == IDOK;
}
AFX_STATIC void AFXAPI _AfxAppendFilterSuffix(CString& filter, OPENFILENAME& ofn,
CDocTemplate* pTemplate, CString* pstrDefaultExt)
{
       ENSURE_VALID(pTemplate);
       ASSERT_KINDOF(CDocTemplate, pTemplate);
       CString strFilterExt, strFilterName;
       if (pTemplate->GetDocString(strFilterExt, CDocTemplate::filterExt) &&
       !strFilterExt.IsEmpty() &&
       pTemplate->GetDocString(strFilterName, CDocTemplate::filterName) &&
       !strFilterName.IsEmpty())
       {
              if (pstrDefaultExt != NULL)
                     pstrDefaultExt->Empty();
                     // add to filter
              filter += strFilterName;
              ASSERT(!filter.IsEmpty()); // must have a file type name
              filter += (TCHAR)'/0'; // next string please
              int iStart = 0;
              do
              {
                     CString strExtension = strFilterExt.Tokenize( _T( ";" ), iStart );
                     if (iStart != -1)
                     {
                      // a file based document template - add to filter list
                     // If you hit the following ASSERT, your document template
                     // string is formatted incorrectly. The section of your
                     // document template string that specifies the allowable file
                     // extensions should be formatted as follows:
                     //    .<ext1>;.<ext2>;.<ext3>
                     // Extensions may contain wildcards (e.g. '?', '*'), but must
                      // begin with a '.' and be separated from one another by a ';'.
                     // Example:
                     //    .jpg;.jpeg
                     ASSERT(strExtension[0] == '.');
                            if ((pstrDefaultExt != NULL) && pstrDefaultExt->IsEmpty())
                            {
                                    // set the default extension
                                   *pstrDefaultExt = strExtension.Mid( 1 ); // skip the '.'
                                   ofn.lpstrDefExt = const_cast< LPTSTR >((LPCTSTR)(*pstrDefaultExt));
                                    ofn.nFilterIndex = ofn.nMaxCustFilter + 1; // 1 based number
                            }
                            filter += (TCHAR)'*';
                            filter += strExtension;
                            filter += (TCHAR)';'; // Always append a ';'. The last ';' will get replaced with a '/0' later.
                     }
              } while (iStart != -1);
              filter.SetAt( filter.GetLength()-1, '/0' );; // Replace the last ';' with a '/0'
              ofn.nMaxCustFilter++;
       }
}
看起来,重写CDocManager::DoPromptFileName方法比重写CDocument::DoSave方法要省事些:
BOOL CSDIDocMgr::DoPromptFileName(CString& fileName, UINT nIDSTitle,
   DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate)
{
       LPCTSTR strFilter = L"图像文件(*.aaa)/0*.aaa/0文本文件 (*.bbb)/0*.bbb/0/0";
       CFileDialog dlgFile(bOpenFileDialog, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, strFilter, NULL, 0);
 
        CString title;
       VERIFY(title.LoadString(nIDSTitle));
       dlgFile.m_ofn.lpstrFilter = strFilter;
       dlgFile.m_ofn.lpstrDefExt = _T(".aaa");
       dlgFile.m_ofn.lpstrTitle = title;
       dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH);

       INT_PTR nResult = dlgFile.DoModal();
       fileName.ReleaseBuffer();
       return nResult == IDOK;
}
CSDIDocMgr是派生自CDocManager的类。下一步的问题是:怎么为CWinApp::m_pDocManager创建CSDIDocMgr对象(而非CDocManager对象)?m_pDocManager是在重写的CWinApp::InitInstance虚方法中创建的,具体来说,是在CWinApp::AddDocTemplate方法中创建的,CWinApp::AddDocTemplate方法在创建m_pDocManager之前会检查它是否为NULL,只有当m_pDocManager为NULL时才为它创建CDocManager对象,所以现在并不需要重写CWinApp::AddDocTemplate方法,而只需要在重写的CWinApp::InitInstance方法中、在调用CWinApp::AddDocTemplate方法之前为m_pDocManager创建CSDIDocMgr对象。
一旦重写了CDocManager::DoPromptFileName方法,之前提到的自定义打开对话框文件扩展名的方法也就多此一举了,也不再需要修改资源文件中的IDR_DTriNetTYPE字段,因为无论打开还是保存对话框,最终都是由CDocManager::DoPromptFileName执行的。

 

你可能感兴趣的:(单文档,自定义打开/保存对话框)