1つのドキュメントを異なる2つのビューで表示する

MDI(Multiple-Document Interface:複数文書インターフェース)では、 複数の異なるドキュメント型を同時に開くことが可能である。MDIアプリケーションでは、 1つのドキュメント型について1つのドキュメントテンプレートを定義する必要がある。
通常、VC++でMDIアプリケーションを作成すると、自動的に1つのドキュメントテンプレートが定義され、 テンプレートを構成するドキュメントクラス、ビュークラス、フレームウィンドウクラスが用意されている。

1つのドキュメントの内容を2つの異なるビューで表示するためには、新たなドキュメントテンプレートを追加する必要がある。

例として左図のようなMDIアプリケーションを考える。左図では、 スピンボタンコントロールのついたエディットボックスが貼り付けられたフォームビューと、 スライダコントロールの貼り付けられたフォームビューがある。ここでは両方とも0〜100までの数値を表示することができ、 一方のコントロールの値を変更すると、もう一方のコントロールの値もそれに合わせて変更される。 つまり、2つのビューのコントロールは常に同じ値を示すものとする。

まず、プロジェクトを新規作成する。MFC AppWizard(exe)で新規プロジェクト名をMDITest1とする。 アプリケーションの種類はMDIとし、ビュークラスCMDITest1Viewの基本クラスはCFormViewとする。
プロジェクトが作成されたら、フォームにエディットボックスとスピンボタンコントロールを貼り付ける。 スピンボタンコントロールの使用方法については、スピンボタンコントロールの使用を参照すること。 また、エディットボックスにはint型のDDX変数m_iEdit1とCEdit型のDDX変数m_edit1を定義しておくこと。

次に、スライダコントロール用の新しいビュークラスを作成する。 ResourceViewで新しいフォームを作成したら、このフォームに対応したクラスを新規作成する。 クラス名はCMDITest1View2とし、基本クラスはCFormViewを指定する。
クラスを作成したら、フォームにスライダコントロールを貼り付け、int型のDDX変数m_iSlider1と、 CSliderCtrl型のDDX変数m_slider1を定義しておくこと。

ここからが本題になる。新しいビュークラスを作成したら、これを表示させるために新たなドキュメントテンプレートを定義する。
MDITest.cppを開いてCMDITestApp::InitInstance関数の定義部分を見ると、次のような記述が見つかる。

	CMultiDocTemplate* pDocTemplate;
	pDocTemplate = new CMultiDocTemplate(
		IDR_MDITESTYPE,
		RUNTIME_CLASS(CMDITestDoc),
		RUNTIME_CLASS(CChildFrame), // カスタム MDI 子フレーム
		RUNTIME_CLASS(CMDITestView));
	AddDocTemplate(pDocTemplate);
CMultiDocTemplateクラスは、MDIのドキュメントテンプレートを定義するクラスである。 上記では、ドキュメントとしてCMDITestDoc、フレームにCChildFrame、ビューにCMDITestViewの各クラスを指定している。 最後のCWinApp::AddDocTemplate関数でアプリケーションのテンプレートリストに作成したテンプレートを追加している。
先に作成したCMDITestViewを使用したドキュメントテンプレートを新たに定義するには、次のようにする。異なるのはビュークラスの部分だけで、他のクラスについては先のテンプレートと同じものを使う。
	#include "MDITest1View2.h"
	(中略)
	CMultiDocTemplate* pDocTemplate2;
	pDocTemplate2 = new CMultiDocTemplate(
		IDR_MDITESTYPE,			// ・・・(1)
		RUNTIME_CLASS(CMDITestDoc),
		RUNTIME_CLASS(CChildFrame), // カスタム MDI 子フレーム
		RUNTIME_CLASS(CMDITestView2));
	AddDocTemplate(pDocTemplate2);
この状態でビルドし、アプリケーションを実行すると、最初に次のようなダイアログが表示されるはずである。

これは複数のドキュメントテンプレートが登録されている場合に表示されるダイアログで、 どのテンプレートで新規作成をおこなうかをユーザに聞いているのである。 ここでは上を選択すればエディットボックスを貼り付けたビューが表示され、 下を選択するとスライダコントロールを貼り付けたビューが表示される。
このダイアログを表示させないようにするために、まずResourceViewのString Tableを開く。 ここにIDR_MDITESTYPEというのがあるので、これをコピー&ペーストしてIDR_MDITESTYPE2を作成する。 IDR_MDITESTYPE2のプロパティから、次のように水色の部分を削除する。

\nMDITes\nMDITes\n\n\nMDITest1.Document\nMDITes Document
↓
\nMDITes\n\n\n\nMDITest1.Document\nMDITes Document
ここまでおこなったら、先に記述したドキュメントテンプレートの定義にある(1)の部分を、 IDR_MDITESTYPEからIDR_MDITESTYPE2に変更し、ビルドする。アプリケーションを実行してみると、 ダイアログは表示されずにメインウィンドウが表示され、 エディットボックスが貼り付けられたフォームビューだけが表示されるようになるはずである。

このままではスライダコントロールが貼り付けられたビューが表示されないので、表示させる処理を入れる必要がある。 CMainFrameクラスに、次のような関数を定義してほしい。

void CMainFrame::OpenView(CDocTemplate *pTemplate)
{
	CDocument* pDoc = MDIGetActive()->GetActiveDocument();
	CMDIChildWnd* pNewFrame = (CMDIChildWnd*)pTemplate->CreateNewFrame(pDoc, NULL);
	if( pNewFrame )
		pTemplate->InitialUpdateFrame(pNewFrame, pDoc);
}
CMainFrameクラスにOpenView関数を追加したら、CMDITest1App::InitInstance関数の最後にこれを呼び出す処理を記述する。
BOOL CMDITest1App::InitInstance()
{
	(中略)
	pMainFrame->OpenView(pDocTemplate2);
	return TRUE;
}
これで、アプリケーションを起動すると異なる2つのビューが同時に表示されるようになる。

2つのビューが同時に表示できたら、それぞれのビューにあるコントロールの値の同期を取る処理を記述する。
まず2つのビューに共通のドキュメントであるCMDITest1Docクラスに、publicなint型メンバ変数m_iCountを定義する。 次に、ドキュメントクラスのコンストラクタでこの変数を0で初期化しておく。 それぞれのビューは、この変数の値をもとにコントロールの表示処理をおこなうのである。

エディットボックスのあるビューでは、ClassWizardを使ってエディットボックスのEN_CHANGEメッセージのハンドラ関数である OnChangeEdit1関数を定義する。また、CMDITest1View::OnUpdate関数も定義する。それぞれの関数の内容は以下のようにする。

void CMDITest1View::OnChangeEdit1() 
{
	// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
	if( m_edit1.GetSafeHwnd() == NULL )
		return;
	UpdateData(TRUE);
	((CMDITest1Doc*)GetDocument())->m_iCount = m_iEdit1;
	GetDocument()->UpdateAllViews(NULL);
}

void CMDITest1View::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
	// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
	m_iEdit1 = ((CMDITest1Doc*)GetDocument())->m_iCount;
	UpdateData(FALSE);
}
また、スライダコントロールのあるビューでは、ClassWizardを使ってスライダコントロールの NM_RELEASEDCAPTUREメッセージのハンドラであるOnReleasedcaptureSlider1関数を定義する。 また、CMDITest1View2::OnUpdate関数も定義する。それぞれの関数の内容は以下のようにする。
void CMDITest1View2::OnReleasedcaptureSlider1(NMHDR* pNMHDR, LRESULT* pResult) 
{
	// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
	if( m_slider1.GetSafeHwnd() == NULL )
		return;
	UpdateData(TRUE);
	((CMDITest1Doc*)GetDocument())->m_iCount = m_iSlider1;
	GetDocument()->UpdateAllViews(NULL);
	
	*pResult = 0;
}

void CMDITest1View2::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
	// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
	m_iSlider1 = ((CMDITest1Doc*)GetDocument())->m_iCount;
	UpdateData(FALSE);
}

目次へ