テクニカルレシピ(Visual C++のレシピ)

Home>>Visual C++>>Doc-View LastUpdate: 2009-01-21

データ処理と描画処理を分ける

Visual C++にはDoc-Viewアーキテクチャという概念があります。
これは主にシングルドキュメントアプリを開発するときに必要となる概念です。
(ダイアログベースについてはこのページの一番下を見てください。)

このDocやViewと呼ばれるものはDocクラス、Viewクラスの略で、
シングルドキュメントではこのクラス、関連ファイルが自動生成されます。

Doc-Viewアーキテクチャを簡単に説明すると、
Docクラスはデータ処理だけ、Viewクラスは描画処理だけやるということです。

例えばコントロールデバイスコンテキストといったGUIに関する操作は、
描画処理にあたるのでViewクラスに記述します。

一方、イベントハンドラの中では様々なデータ処理が必要になります。
このときイベントハンドラ関数自体はViewクラスに作成しますが、
データ処理はDocクラスのメンバ関数を呼び出して行います。

簡単なルール

Doc-Viewは作ってみないとわかりにくい概念です。
私は下の3つのルールに気をつけています。

@画面表示に関係する変数は全てDocクラスのメンバ変数にする。
Viewクラスのイベントハンドラ関数内に閉じた変数にはしないということ。

Aデータ処理はViewクラスに記述しない。
Docクラスのメンバ関数を呼び出して行うということ。

B描画処理はViewクラスのOnDrawに全て記述する。
Viewクラスのイベントハンドラ関数内に描画処理を記述しないということ。
イベントハンドラではInvalidate、UpdateWindowを使ってOnDrawを呼ぶだけ。

具体例

ここではイベントハンドラの項で作成したシングルドキュメントアプリに、
実際にDoc-Viewアーキテクチャに従った記述を加えていきます。
画面上の白い領域に「Hello World」と表示させるようにしてみましょう。

まずは出力する文字列用の変数として、「CString」型のメンバ変数「m_str」をDocクラスに設定します。
(文字列は表示する「データ」です。表示に関する「データ」は全てDocクラスのメンバにします。)

次にViewクラスのOnDraw関数に移動して下さい。
OnDraw関数は描画の基本となる関数です(描画処理のたびにこの関数が呼ばれます)。
必要な描画処理は全てこの関数に書き込みましょう。

最初の行(@)に「CString2Doc* pDoc = GetDocument();」と書いてありますね。
ここは非常に大事です。
GetDocumentはDocクラスのポインタを取得するためのAPIです。
ここでは「pDoc」がDocクラスのポインタになるので、
ViewクラスはpDocを通じてDocクラスのメンバにアクセスすることが出来ます。

TODOコメント以下にはAの「pDC->TextOutW(0,0,pDoc->m_str);」を追加しています。
これはpDC(シングルドキュメントのデバイスコンテキスト)に、
変数「pDoc->m_str」に記述された文字列を表示するという意味です。

もしm_strに何も文字が入力されていない場合は何も表示されず、
文字が入力されている場合には文字が表示されることになります。

次にm_strに文字列を入力するためのイベントハンドラを設定します。
Viewクラスに追加したイベントハンドラ関数に移動して下さい。

この関数はViewクラスの関数なので、
直接m_strに文字を入力する処理を書いてはいけません。

面倒ですが、一度GetDocumentでDocクラスのポインタを取得し、
メンバ関数(SetString)を呼び出して処理させるようにしましょう。
なお最後のInvalidateは再描画命令です(OnDrawを呼び出しています)。

DocクラスではViewクラスから呼ばれるSetStringの中身を記述します。
ここでは単純に"Hello World "という文字列をm_strに足しているだけです。

ここまで記述し、プログラムを実行すると、
「HelloWorld」というメニューか「HW」というアイコンを押すたびに、
「Hello World」という文字列が表示されるようになります。
(ソースファイルはサンプルコードのString2.zipにまとめています。)

Doc-Viewの意義

Doc-Viewはプログラミングしてみないと理解しにくい概念だと思いますが、
なんとなくのイメージは伝わったでしょうか。

なぜこんなアーキテクチャを取っているのか疑問に思う人がいると思いますが、
実際にアプリ開発を進めるとこの重要性がわかってきます。

最も意義があるのはデータを保存するアプリを作るときです。
きちんとデータがDocクラスにまとまっていると、データの保存、展開が楽になります。

GUI処理に関していうと、再描画処理にはDoc-Viewが不可欠です。
単純にイベントハンドラに全ての描画処理とデータを書いてしまうと、
適切な再描画が行われません。

負荷の高いアプリをマルチスレッドにする場合も、この概念は役に立ちます。
データ処理と画面表示をそれぞれ独立したスレッドにすることが出来るからです。

ダイアログベースでのDoc-View

ダイアログベースではDoc-Viewを意識しなくても簡単なアプリを作れますが、
中級者以上を目指すならDoc-Viewの概念は重要です。

ダイアログベースの留意点はシングルベースととても近いです。
@画面表示に関連するデータはC***Dlgクラスのメンバ変数にする。
AイベントハンドラもC***Dlgクラスにできるので、直接メンバを処理してもいい。
B描画処理はC***DlgクラスのOnPaintに全ての処理を記述する。

当サイトの「初めの一歩」、「初級アプリ」のところでは
あえてDoc-Viewに従わずダイアログアプリを作っていて、
「中級者への道」ではこのとき生じる問題点を解説しています。
興味のある方は読んでみてください。

イベントハンドラ:前項<< ページの先頭に戻る >>次項:文字表示