あるファイルを入力として画面に何か出力するような自作アプリで
「ファイルを修正したので画面を更新したい」と思ったら、現在処理中のファイルを
1回閉じて 再度同じファイルを読み込まなくてはなりません。
というのは、MFCのお節介で、現在開いているファイルを直接読み込もうとすると
「既に開いてるぜ」ということで、読み込ませてくれないからです。
そこで、この余計なお節介をやめさせる方法です。
ファイルを開く時の関数呼び出しは、次のようになっています(SDIの場合)。
CWinApp::OnFileOpen | |
↓ | |
CWinApp::OpenDocumentFile | |
↓ | |
CDocManager::OpenDocumentFile | ここで、既に開いているかどうか判断 |
↓ | |
CSingleDocTemplate::OpenDocumentFile | |
↓ | |
CDocument::OnOpenDocument | |
↓ | OnOpenDocumentに独自の実装をしてなければSerializeが呼ばれる |
CDocument::Serialize |
というわけで、CDocManager::OpenDocumentFileの中を変えます。 ところが これがなかなか大変なんですね〜(泣)
まず、CDocManagerの派生クラスを用意します。
以下、「CTestDocMgrをTestDocMgr.h,TestDocMgr.cppに作った」という例で書いています。
TestDocMgr.h:
// TestDocMgr.h : ヘッダー ファイル // ///////////////////////////////////////////////////////////////////////////// // CTestDocMgr ドキュメント class CTestDocMgr : public CDocManager { public: CTestDocMgr(); DECLARE_DYNAMIC(CTestDocMgr) // アトリビュート public: // オペレーション public: // オーバーライド // ClassWizard は仮想関数を生成しオーバーライドします。 //{{AFX_VIRTUAL(CTestDocMgr) //}}AFX_VIRTUAL // インプリメンテーション public: virtual ~CTestDocMgr(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif // メッセージ マップ関数の生成 protected: //{{AFX_MSG(CTestDocMgr) // メモ - ClassWizard はこの位置にメンバ関数を追加または削除します。 //}}AFX_MSG };TestDocMgr.cpp
// TestDocMgr.cpp : インプリメンテーション ファイル // #include "stdafx.h" #include "test.h" #include "TestDocMgr.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CTestDocMgr IMPLEMENT_DYNAMIC(CTestDocMgr, CDocManager) CTestDocMgr::CTestDocMgr() { } CTestDocMgr::~CTestDocMgr() { } //BEGIN_MESSAGE_MAP(CTestDocMgr, CDocManager) //{{AFX_MSG_MAP(CTestDocMgr) // メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。 //}}AFX_MSG_MAP //END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CTestDocMgr 診断 #ifdef _DEBUG void CTestDocMgr::AssertValid() const { CDocManager::AssertValid(); } void CTestDocMgr::Dump(CDumpContext& dc) const { CDocManager::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CTestDocMgr コマンドようやく準備が完成〜!あれ? もしかして、使う人は、このソースをそのままコピーすればいいだけ…?
で、このクラスをCTestAppのCDocManagerと置き換えます。
CWinApp::AddDocTemplateの中でCDocManagerをnewしているので、
CWinApp::AddDocTemplateをオーバーライドして、中身をそっくりコピーして、
CDocManagerの部分だけCTestDocMgrに置換しましょう。
ただ、これまたClassWizardではAddDocTemplateが出てこないので 自分でコーディングしなければなりません。
CWinApp::AddDocTemplateはCTestApp::InitInstanceの中から呼ばれているので、
そこにブレークポイントを置いて試しに実行してみればソースの場所に辿り着けます。
class CTestApp : public CWinApp
{
〜
// インプリメンテーション
void AddDocTemplate(CDocTemplate* pTemplate);
〜
};
test.cpp:
#include "TestDocMgr.h" 〜 void CTestApp::AddDocTemplate(CDocTemplate* pTemplate) { if (m_pDocManager == NULL) m_pDocManager = new CTestDocMgr; m_pDocManager->AddDocTemplate(pTemplate); }これで、独自のCTestDocMgrが使われるようになります。
で、肝心のCDocManager::OpenDocumentFileのオーバーライドを作りましょう。 なんで?と思った人、フローをもう一度見て下さい。 もともと、OpenDocumentFileの中身を変えたかったんですよ(笑)
例によってClassWizardは対応していないので、自分でCDocManager::OpenDocumentFileのソースを コピーしてこなくてはいけません。
ソースの場所を探すには、まずCTestDocMgr::OpenDocumentFileの中で CDocManager::OpenDocumentFileを呼び出すようにコーディングし、 ブレークポイントを置いてソースを探すのが楽でしょうか。
CDocument* CTestDocMgr::OpenDocumentFile(LPCTSTR lpszFileName) { ● return CDocManager::OpenDocumentFile(lpszFileName); }
ちなみにそのソース、すごく長いです(VC++4.0)。
この中で変えたい部分は、指定されたファイルが既に開いているかどうか判断する部分だけです。
「if (match == CDocTemplate::yesAlreadyOpen) break;」という部分がそれなので、
この時にpOpenDocumentという変数の中身をNULLにすることで「開いているファイルが見つからなかった」ことにします。
これは試行錯誤の結果です。もっといい手があるかも?
ところがその状態でコンパイルしてみると、マクロや関数の定義が足りないために コンパイルエラーになってしまいます。
「ファイルから検索」を利用して探した結果、足りないものはafximpl.hで宣言されているのを見つけたのですが、
これはMFCのソース用のヘッダーファイルなのでパスが通っていません。
単純にincludeしただけでは「インクルードファイルがオープンできません」と言われてしまいます。
仕方が無いので、例によって 必要なところだけコピーしてくることにしましょう。
これら全ての作業をした結果が、以下のソースです。
TestDocMgr.h:
class CTestDocMgr : public CDocManager
{
〜
virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); // open named file
〜
};
TestDocMgr.cpp:
//afximpl.hから必要なマクロ定義・関数宣言だけ持ってきた #define _countof(array) (sizeof(array)/sizeof(array[0])) BOOL AFXAPI AfxFullPath(LPTSTR lpszPathOut, LPCTSTR lpszFileIn); BOOL AFXAPI AfxComparePath(LPCTSTR lpszPath1, LPCTSTR lpszPath2); #ifndef _MAC BOOL AFXAPI AfxResolveShortcut(CWnd* pWnd, LPCTSTR pszShortcutFile,LPTSTR pszPath, int cchPath); #endif CDocument* CTestDocMgr::OpenDocumentFile(LPCTSTR lpszFileName) { // find the highest confidence POSITION pos = m_templateList.GetHeadPosition(); CDocTemplate::Confidence bestMatch = CDocTemplate::noAttempt; CDocTemplate* pBestTemplate = NULL; CDocument* pOpenDocument = NULL; TCHAR szPath[_MAX_PATH]; ASSERT(lstrlen(lpszFileName) < _countof(szPath)); #ifndef _MAC TCHAR szTemp[_MAX_PATH]; if (lpszFileName[0] == '\"') ++lpszFileName; lstrcpyn(szTemp, lpszFileName, _MAX_PATH); LPTSTR lpszLast = _tcsrchr(szTemp, '\"'); if (lpszLast != NULL) *lpszLast = 0; AfxFullPath(szPath, szTemp); TCHAR szLinkName[_MAX_PATH]; if (AfxResolveShortcut(AfxGetMainWnd(), szPath, szLinkName, _MAX_PATH)) lstrcpy(szPath, szLinkName); #else lstrcpyn(szPath, lpszFileName, _MAX_PATH); WIN32_FIND_DATA fileData; HANDLE hFind = FindFirstFile(lpszFileName, &fileData); if (hFind != INVALID_HANDLE_VALUE) VERIFY(FindClose(hFind)); else fileData.dwFileType = 0; // won't match any type #endif while (pos != NULL) { CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetNext(pos); ASSERT_KINDOF(CDocTemplate, pTemplate); CDocTemplate::Confidence match; ASSERT(pOpenDocument == NULL); #ifndef _MAC match = pTemplate->MatchDocType(szPath, pOpenDocument); #else match = pTemplate->MatchDocType(szPath, fileData.dwFileType, pOpenDocument); #endif if (match > bestMatch) { bestMatch = match; pBestTemplate = pTemplate; } if (match == CDocTemplate::yesAlreadyOpen){ pOpenDocument=NULL; //「開いているファイルが見つからなかった」ことにする break; // stop here } } if (pOpenDocument != NULL) { POSITION pos = pOpenDocument->GetFirstViewPosition(); if (pos != NULL) { CView* pView = pOpenDocument->GetNextView(pos); // get first one ASSERT_VALID(pView); CFrameWnd* pFrame = pView->GetParentFrame(); if (pFrame != NULL) pFrame->ActivateFrame(); else TRACE0("Error: Can not find a frame for document to activate.\n"); CFrameWnd* pAppFrame; if (pFrame != (pAppFrame = (CFrameWnd*)AfxGetApp()->m_pMainWnd)) { ASSERT_KINDOF(CFrameWnd, pAppFrame); pAppFrame->ActivateFrame(); } } else { TRACE0("Error: Can not find a view for document to activate.\n"); } return pOpenDocument; } if (pBestTemplate == NULL) { AfxMessageBox(AFX_IDP_FAILED_TO_OPEN_DOC); return NULL; } return pBestTemplate->OpenDocumentFile(szPath); }うーーん、やりたい内容の割には長い道のりでした(疲)