S-JIS[2002-08-03]

使用中ファイルの再読み込み

あるファイルを入力として画面に何か出力するような自作アプリで 「ファイルを修正したので画面を更新したい」と思ったら、現在処理中のファイルを 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に作った」という例で書いています。

  1. クラス名をCTestDocMgr、基本クラスをCDocument(CDocManagerとしたいところだが コンボボックスの中に出てこないので)としてClassWizardで新しいクラスを作成する。
  2. TestDocMgr.h,TestDocMgr.cppの「CDocument」を全て「CDocManager」に置換する。
  3. Serialize,OnNewDocumentは不要なので、TestDocMgr.h,TestDocMgr.cppから削る。
  4. コンパイルすると「error C2039: 'messageMap' : 'CDocManager' のメンバではありません。」と出てくるので TestDocMgr.cppのBEGIN_MESSAGE_MAP,END_MESSAGE_MAPをコメントアウトする。 BEGIN_MESSAGE_MAP〜END_MESSAGE_MAPを全て削ってしまってもいいんだけど、 その中にはClassWizardが使用するデータが入っているので、残しておくのが無難。
  5. これでいいかな と思ったら、リンクで「error LNK2001: 外部シンボル ""protected: virtual struct AFX_MSGMAP const * __thiscall CTestDocMgr::GetMessageMap(void)const "(?GetMessageMap@CTestDocMgr@@MBEPBUAFX_MSGMAP@@XZ)" は未解決です。」と出てくるので TestDocMgr.hのDECLARE_MESSAGE_MAP()を削る。
  6. 念のためCDocManagerのソースを見てみると、オブジェクトの生成方法がDYNCREATEではなくDYNAMICになっているので TestDocMgr.hのDECLARE_DYNCREATEをDECLARE_DYNAMICに、 TestDocMgr.cppのIMPLEMENT_DYNCREATEをIMPLEMENT_DYNAMICに直しておく。
    ついでにコンストラクタもprotectedからpublicにしておく。

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の中から呼ばれているので、 そこにブレークポイントを置いて試しに実行してみればソースの場所に辿り着けます。

test.h:
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);
}
うーーん、やりたい内容の割には長い道のりでした(疲)
VC++ページへ戻る / 技術メモへ戻る
メールの送信先:ひしだま