2013年1月15日火曜日

MFC ファイル保存ダイアログのファイル形式を増やす。

MFC CDocumentクラスの保存ダイアログのカスタマイズ。
保存形式(拡張子)の候補を増やす方法。


CDocumentから派生させたクラスを作成しておき、
CDocument::DoSaveメソッドをオーバーライドする。
virtual BOOL DoSave(LPCTSTR lpszPathName, BOOL bReplace = TRUE);

CDocumentの既存のコードを丸ごとオーバーライドさせたメソッドにコピーしておく。
コードはdoccore.cpp(C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\src\mfc\doccore.cpp ※VisualStudio2010の場合)から取得する。
afxdatarecovery.hヘッダや、DELETE_EXCEPTIONマクロはdoccore.cppに記述されていたものでビルドを通す為にこれも一緒にコピーしておく。(DELETE_EXCEPTIONマクロはdoccore.cppがインクルードしていたstdafx.hから取得)
下記、コピーするソースコード。
#include "io.h" // for _access
#include "afxdatarecovery.h"

#define DELETE_EXCEPTION(e) do { if(e) { e->Delete(); } } while (0)

BOOL CDocument::DoSave(LPCTSTR lpszPathName, BOOL bReplace)
    // 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);

            if (AfxGetApp() && AfxGetApp()->GetDataRecoveryHandler())
            {
                // remove "[recovered]" from the title if it exists
                CString strNormalTitle = AfxGetApp()->GetDataRecoveryHandler()->GetNormalDocumentTitle(this);
                if (!strNormalTitle.IsEmpty())
                    newName = strNormalTitle;
            }

            // 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);
        OnDocumentEvent(onAfterSaveDocument);
    }

    return TRUE;        // success
}


DoSaveメソッドは「上書き保存」や、「名前を付けて保存」とした時に呼び出されるメソッドで、lpszPathNameにファイル名が設定されていない場合にファイル選択ダイアログを表示してファイル名を取得している。
DoPromptFileNameメソッドを呼び出しているあたりがファイル選択ダイアログを表示している部分だ。
DoPromptFileNameメソッドは、CWinAppクラスがインスタンスを保持しているCDocManagerクラスのメソッドで、実装はdocmrg.cpp(C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\src\mfc\docmgr.cpp ※VisualStudio2010の場合)に書かれている。内容はCFileDialogを使ってファイル選択ダイアログを表示しているが、その際にCDocTemplateクラスからアプリケーション固有の拡張子を取得し追加しているようだ。MFCウィザードで作成した際に聞かれた拡張子の部分だ。
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;
    ENSURE(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;
}

今回はDoPromptFileNameメソッドの変わりに、自分でCFileDialogクラスを呼び出す事で、選択出来るファイル形式(拡張子)を増やす事とした。
下記がDoPromptFileNameメソッドを呼び出す代わりにオーバーライドしたDoSaveメソッド内に書いたコード。
CFileDialog dlgFile(FALSE, _T("md"), NULL, 
    OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_LONGNAMES,
    _T("Markdown      (*.md)|*.md|")
    _T("Text          (*.txt)|*.txt|")
    _T("HTML          (*.html;*.htm)|*.html;*htm|")
    _T("全てのファイル(*.*)|*.*||"),
    NULL, 0);

CString title;
ENSURE(title.LoadString(bReplace ? AFX_IDS_SAVEFILE : AFX_IDS_SAVEFILECOPY));
dlgFile.m_ofn.lpstrTitle = title;
dlgFile.m_ofn.lpstrFile = newName.GetBuffer(MAX_PATH);
INT_PTR nResult = dlgFile.DoModal();
newName.ReleaseBuffer();
if (IDOK != nResult) return FALSE;
CFileDialogクラスのコンストラクタ関数で、第二引数にデフォルトの拡張子を設定しておくと、
ファイル名に自動で拡張子を付けてくれるようになる。



0 件のコメント:

コメントを投稿