Visual C++ 学習日誌 No.8

1997年12月07日

セルフ描画コントロール

CD Playerの開発がとん挫して、また違うことをやっている今日この頃です。
いやあ、いきなり大きいのを作るとやっぱきついですね。
プログラミングは設計が大事だというのがよくわかりました。

で、今回は、コントロールのセルフ描画です。
現在開発中のプログラムで、チェックボックス付きのリストボックスを使いたくなったんです。
MFCのクラスの派生の順番をツリー状にした図を見たことがあると思いますが、 その中のコントロールのCListBoxの下にCCheckListBoxがぶる下がっているのを知っていますか?
つまり、CCListBoxから、派生されたクラスです。
実は、これを使えば簡単にチェックボックス付きのリストボックスが使えるんです。
でも、どうやって使うかわかりますか?ダイアログボックスのリソースエディタにも、そんなコントロールは存在していません。
僕も、正直言って悩みました。
そこで知ったのが、セルフ描画サブクラス化です。
簡単な説明をするので、詳しい説明は、下に書いてある参考文献を参考にしてください。

まず、サブクラス化のクラスですが、これはC++のクラスではありません
これは、Windowsのシステムにおける、ウィンドウクラスというものです。
ウィンドウクラスはウィンドウの振る舞いを決めるものです。
MFCを使っている限り、これを意識する機会は少ないと思います。
実はこの中に、ウィンドウプロシージャというメッセージ対する動作を指定する情報が入っているのです。
MFCでは、通常これはデフォルトの値が適用されます。ただ、ウィンドウプロシージャだけは、 デフォルトが何もしないことになっているので、プログラマがメッセージハンドラを定義しているのです。
また、EditListBoxはWindowsにはじめから用意されているウィンドウクラスです。

チェックボックス付きのリストボックスのように、すでにあるものとちょっと違ったコントロールを作りたいことはよくあります。
そんなとき、よくオーナー描画コントロールというものを使って、 ダイアログボックスのウィンドウプロシージャが描画を肩代わりすることによって、特殊な表示をします。
これは、SDKを使ったWindowsプログラミングをしたことがある人はよく知っていると思います。
でも、僕はSDKを使ったプログラミングをしたことがないので、知っているのは概念だけです。
この方法は便利ですが、ただ1つ難点があって、コードの再利用性が低いのです。
ダイアログボックスのウィンドウプロシージャ内に描画のコードが入ってしまうからです。

そこで、MFCでは、セルフ描画という技法を使います。
この場合、描画はコントロール自身が担当します。これなら、コードの再利用性があがります。
でも、Windowsにはじめから用意されているコントロールにどうやって新しい機能を付けるのか?
これは、サブクラス化という技法によって、コントロールに新しい機能を持たせることができます。
C++の派生クラスに似ていますが、別物です。 ややこしい名前を付けてくれたMicrosoftに文句を言いましょう(^^;
どうやって実現しているかといえば、ウィンドウプロシージャを入れ替えているんです。
そして、新たに必要な機能だけを新しいウィンドウプロシージャで処理し、 元のままでいい処理は、元のウィンドウクラス(スーパークラスという)の ウィンドウプロシージャに任せます。
なお、サブクラス化は動的に行えます。

以上、受け売りでした(^^;
それでは、具体的に、僕がどうやってCCheckListBoxを使ったのかを解説したいと思います。

まず、ダイアログボックスに普通にListBoxを配置します。
ここで、ListBoxのプロパティーに設定する項目が、通常と違います。
実は、僕はここで、はじめからつまづいていたんです。 最後まで作って、さあコンパイルして、実行と思ったら見事にアサートがかかってしまいました。
しばらく原因が分かりませんでしたが、 何のことはなく「スタイル」の「オーナー描画」を「可変」にしてあったのを「固定」にし、 かつ、「文字列あり」チェックを入れたら、簡単に直ってしまいました。
簡単とはいっても、それまでに何十回もの試行錯誤をしていますが。
なお、この「オーナ描画」はデフォルトで「しない」になっています。

それから、CListBoxから派生させた派生クラスを作成するんですが、 今回はすでに用意されているCCheckListBoxクラスを使用します。

ダイアログボックスにCCheckListBox型のメンバ変数を追加します。
例えばこんなふうです。(ダイアログボックスのクラスをCCheckDlgとします)

class CCheckDlg {

	..........

protected:
	CCheckListBox m_CheckListBox;

	..........
};


それから、ダイアログボックスのクラスの中のOnInitDialog()はじめに、 サブクラス化を実行するコードを記述します。
BOOL CCheckDlg::OnInitDialog
{
	// サブクラス化
	m_CheckListBox.SubclassDlgItem(IDC_CHECK_LISTBOX, this);
	// IDC_CHECK_LISTBOXはコントロールのID

	.......
}
これで、サブクラス化が行われます。 実は、僕ははじめこのコードを後ろの方に書いて、原因不明のエラーの悩まされました。
それは、"Detected memory leaks"というメッセージが、プログラム終了後にでるのです。
何だろうと思って調べたら、CCheckListBox::SetCheck()で起こっていました。
実は、DoDataExchange()でコントロールを初期化していたんですが、そこでその関数を使っていました。
よく考えたら当たり前で、サブクラス化をする前に、初期化してたんですね。
今考えたら、全くばかげた間違いでした。
でも、スケルトンコードのコメントには
「// TODO: 特別な初期化を行う時はこの場所に追加してください。」
って後ろの方に書いてあるもので、ついそこに書いてしまったんです。

これで、終わりです。今回はすでに用意されたクラスを使いましたが、 自分で派生クラスを作っても同じようにできます。この方法に関しては、下に書いてある参考文献を読んでみてください。

なお、このままでは、リストボックスの中は空っぽですので、DoDataExchange()の中ででも、 ちゃんと初期化してやってください。

参考文献:「Visual C++ プログラミングテクニック」 田口景介 著 アスキー出版局


ああ、色を変えまくったら、タグだらけになって苦労したぁ。
テキストエディタで書いているもので

[BACK | NEXT]


back
Visual C++ Laboratoryのページへ戻る


E-Mail:wbs03748@mail.wbs.ne.jp
質問・感想などのメールはこちらへ。


Copyright(C) 1997 Shingo Nakagawa / e-mail:wbs03748@mail.wbs.ne.jp