デル株式会社

CWinApp 雑記

Last Updated: Jan.5,2007
使用しているコンパイラは、Microsoft Visual C++ 6.0 です。これ以外のバージョンでは記述と食い違うことがあるかも知れません。

3. 複数の拡張子を選択可能にする

MFC アプリケーションでは、ひとつのドキュメントに対してひとつの拡張子しか扱えず、ファイルオープンダイアログでフィルタを選択することが出来ません。例えば、以下のようなリソースを定義して、

IDS_APP_FILTER "テキスト ファイル (*.txt)|*.txt|HTML ファイル (*.html;*.htm)|*.html;*.htm|すべてのファイル (*.*)|*.*||"

というフィルタを使用できるようにしたいと思います。なお、以下の説明では、

としています。

既存のファイルを開く場合

CWinApp::OnFileOpen のソースでは単に CDocManager::OnFileOpen を呼び出し、CDocManager::OnFileOpen では、CDocManager::DoPromptFileName と CWinApp::OpenDocumentFile を呼び出しています。この CDocManager::DoPromptFileName がファイルオープンダイアログを表示しているようです。

そこでとりあえず、CMyApp::OnFileOpen を以下のようにしておきます。CMyApp::DoMyPromptFileName については後ほど説明します。

void CMyApp::OnFileOpen()
{
    CString newName;
    if (!DoMyPromptFileName(newName, AFX_IDS_OPENFILE,
        OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))
        return;
    OpenDocumentFile(newName);
}

ファイルを保存する場合

ここで、CDocManager::DoPromptFileName が呼び出されている MFC のソースの個所を検索してみますと、上記以外では、CWinApp::DoPromptFileName だけです。しかも、CDocManager::DoPromptFileName を呼び出しているだけの言わば「仲介関数」です。また、CWinApp::DoPromptFileName が呼び出されている個所も CDocument::DoSave からだけなので修正するには好都合です。

既存のファイルを開く ファイルを保存

CWinApp::OnFileOpen

CDocument::DoSave

CDocManager::OnFileOpen

CWinApp::DoPromptFileName

CDocManager::DoPromptFileName

保存を司っているのは、CDocument::DoSave なので、この(仮想)関数をオーバーライドします。コードは CDocument::DoSave を丸写しにして、CWinApp::DoPromptFileName の部分を CMyApp::DoMyPromptFileName に置き換えます。

BOOL CMyDoc::DoSave(LPCTSTR lpszPathName, BOOL bReplace)
{
    CString newName = lpszPathName;
    if (newName.IsEmpty())
    {
        CDocTemplate* pTemplate = GetDocTemplate();
        ASSERT(pTemplate != NULL);
        newName = m_strPathName;
        if (bReplace && newName.IsEmpty())
        {
            newName = m_strTitle;
            int iBad = newName.FindOneOf(_T(" #%;/\\"));
            if (iBad != -1)
                newName.ReleaseBuffer(iBad);
            CString strExt;
            if (pTemplate->GetDocString(strExt, CDocTemplate::filterExt) &&
                !strExt.IsEmpty())
            {
                ASSERT(strExt[0] == '.');
                newName += strExt;
            }
        }
        // ** 以下を修正 **
        if (!((CMyApp*)AfxGetApp())->DoMyPromptFileName(newName,
            bReplace ? AFX_IDS_SAVEFILE : AFX_IDS_SAVEFILECOPY,
            OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, FALSE, pTemplate))
            return FALSE;
        // ** ここまで **
    }
    CWaitCursor wait;
    if (!OnSaveDocument(newName))
    {
        if (lpszPathName == NULL)
        {
            TRY
            {
                CFile::Remove(newName);
            }
            CATCH_ALL(e)
            {
                TRACE0("Warning: failed to delete file after failed SaveAs.\n");
                do { e->Delete(); } while (0); // ** エラーが出るので修正 **
            }
            END_CATCH_ALL
        }
        return FALSE;
    }
    if (bReplace)
        SetPathName(newName);
    return TRUE;
}

DoMyPromptFileName の実装

いよいよ、CMyApp::DoMyPromptFileName を実装します。CDocManager::DoPromptFileName は、ドキュメントテンプレート文字列からフィルタを作成して、ファイルオープンダイアログを表示するだけの単純なもので、同じ動作を実装してやればうまくいきそうです。もちろん、フィルタは独自のものを使いますが。ちなみに引数の最後の pTemplate は実はもう不要になっています。

BOOL CMyApp::DoMyPromptFileName(
    CString& fileName, UINT nIDSTitle, DWORD lFlags,
    BOOL bOpenFileDialog, CDocTemplate* pTemplate)
{
    CFileDialog dlgFile(bOpenFileDialog, _T("txt"));
    CString title;
    VERIFY(title.LoadString(nIDSTitle));
    dlgFile.m_ofn.Flags |= lFlags;
    CString strFilter;
    VERIFY(strFilter.LoadString(IDS_APP_FILTER)); // ** 独自のもの **
    int nCount = strFilter.Replace(_T('|'), _T('\0'));
    dlgFile.m_ofn.nMaxCustFilter = nCount / 2;
    dlgFile.m_ofn.lpstrFilter = strFilter;
    dlgFile.m_ofn.lpstrTitle = title;
    dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH);
    int nResult = dlgFile.DoModal();
    fileName.ReleaseBuffer();
    return nResult == IDOK;
}

こっちの方がソースもすっきりしていて気分がいいですね。


Back Copyright (c) 2007 AOK. All Rights Reserved.