次のページ 前のページ 目次へ

10. ユーザーコントロールの扱い

前章では牌の描画まで行ってみましたが、 あれだけでは画面が切り替わったり、 上にインデックス画面を開いた後に、描いた画像が消えてしまいます。 ここではユーザーコントロールを使ってその問題を解決してみます。

10.1 利用前の各種設定

まずは、何はさておきクラスライブラリのヘルプを開いてください。 ヘルプには各コントロールに必要な初期設定や制限事項、 使い方のサンプルが載っており、非常に参考になります。 これを見て正しく設定しなくては満足に動きませんし、 仮に動いたとしても見えないバグを生みますから、要チェックです!

ユーザーコントロールであれば以下のような初期設定・使い方が必要になります。

  1. コールバック関数を設定する
  2. コールバック関数ではメッセージをチェックしてから処理する
  3. コールバック関数は必ず true を返す
  4. フォームのセットアップ関数で UC_CALL_BACK_WORK_PID に AplForm を設定する
それでは、以下でこれらを順々に説明していきましょう。

10.2 コールバック関数の設定

コールバック関数というのは、 その名の通りシステム側から何かあった時に呼び出してもらう ユーザー独自の処理の事で、SZABの場合フォームエディタから設定しておきます。 GameFormを開いて GameAreaUCを選択し、 プロパティの UC_CALL_BACK_PID を編集してください。 鉛筆のアイコンを押して関数名を決め、 紙のようなアイコンでテキストエディタを開き、 コールバック関数を記述します。

コールバック関数名の設定

[コールバック関数 初期状態]
BOOLEAN GameAreaUC_CB(MESSAGE* Message, WINDOWHD WnHandle, RECTANGLE* UserRect, void** Work)
{
}

ところで、 上のダイアログで設定した関数名と、 エディタで開いたソース中の関数名は、必ず一致させねばなりません。 そうでないとビルドの途中で『関数が存在しない』と怒られてしまいます。 ボタン等のイベント関数は良いのですが、 こうしたコールバック関数では注意しましょう。

10.3 コールバック関数の記述の仕方

この関数には、ユーザーコントロールが受けたメッセージのうち、 ヘルプに書かれてある2つを除いた全てが毎回まわってきています。 従って、本当に自分(GameAreaUC)宛ての物なのかの判断や、 この領域の描き直しも含めて、 メッセージを識別して処理する必要があります。 この辺りは、前にイベントドリブンの処理の例としてあげたように、 switch文による振り分けが一般的でしょう。
[例]
	switch( Message->CommonMsg.MessageID ) {
	case RedispID:		/* 再描画 */
		break;
	case PenDownID:		/* ペンが押された */
		break;
	case PenStillDownID:	/* ペンが押され続けている */
		break;
	case PenUpID:		/* ペンが離された */
		break;
	}

10.4 コールバック関数の戻り値

制限事項を見ると、必ず true を戻さないといけません。 これは何も考えずに、 関数から呼び出し元へ戻るところで次の1文を実行させてやりましょう。 なお、これを忘れると、処理の状態により動いたり動かなかったり、 原因が特定しづらくなります。 戻り値を戻す関数では、十分注意してください。
[例]	return( true );

10.5 フォームセットアップで行うべき処理

ちょっとピンと来ないかも知れませんが、 各コントロールは私たちの書くコードの外にいるため、 基本的にはこちらの情報へのアクセス手段がありません。 しかし当然ながらそれじゃ使い物になりませんので、 ユーザーコントロールが持つ UC_CALL_BACK_WORK_PID に、 こちらの情報構造体へのポインタ AplForm を設定しておき、 ポインタ経由でアクセスさせるわけです。

この設定は、フォームとコントロールが出来上がった直後で、 まだイベントメッセージが配送される前に行わねばなりません。 つまり、FormSetup()内に書く必要があるのです。 具体的には、 フォーム部品(フォームエディタで一番下にある部品)を選び、 プロパティの FormSetUp を選んで コールバック関数を設定してください。
[例]
	void GameFormSetUp(APLFORMPTR AplForm, MESSAGE* Message)
	{
		/* ユーザーコントロールの Work 変数に値を設定する(必須) */
		SetCtrlProperty( GameAreaUC, UC_CALL_BACK_WORK_PID,
					pValue, AplForm, AplForm->Control );
	}

ここで設定しておいた値は、 コールバック関数の第4引数 void **Work で渡されますので、 次のようにコールバック関数の中で AplForm = *Work のようにして読み出します。 Workそのものではなく*Workとして間接参照している点に注意してください。
BOOLEAN GameAreaUC_CB(MESSAGE* Message, WINDOWHD WnHandle, RECTANGLE* UserRect, void** Work)
{
	APLFORMPTR	AplForm;	/* ローカル変数の宣言 */

	AplForm = *Work;		/* ローカル変数AplFormに値を入れる */

	/* ・・・後はコールバック関数の処理 */

	return( true );			/* 必ず true で戻る */
}

10.6 再描画イベントの目的

ここからは、コールバック関数の中身に入って説明しましょう。

目に見えるコントロールの場合、 絶対に必要になるのがこの『再描画処理』です。 前に DispImageRes() を使った時、確かに牌を描いたはずなのに、 上に何か表示すると消えてしまうという現象が起きました。 これは、先の例が一時的に画面に絵を描いただけに過ぎず、 『消えたので描き直して欲しい』という要求を誰も処理していない為です (このシステムからの要求こそが、再描画イベントです)。

大体のウィンドウ・システムではこんな感じで、 『自分が描いた内容はシステムの要求でいつでも復元できるように』 しておかなくてはなりません。 ボタン等の各コントロールは自前で再描画処理を持っているため、 こちらで何もしなくても自分を描画してくれますが、 ユーザーコントロールの場合は、そうした処理はないので コールバック関数のcase RedispID: のところに 再描画処理を書いてあげるわけです。

10.7 ユーザーコントロールの描画

コールバック関数にはコントロールの領域を示す UserRect が引数として渡されますので、 これから範囲を得て描画を行います。

四川省では背景の描画と牌の描画とがありますが、 まだゲーム本体まで作り込んでいないので、 とりあえず背景を塗りつぶした後、ALLDRAW()関数を呼ぶように作っておきます (今のところ1個の牌を表示してるだけですが、 実際のゲームではここを拡張すれば済みます)。
BOOLEAN GameAreaUC_CB(MESSAGE* Message, WINDOWHD WnHandle, RECTANGLE* UserRect, void** Work)
{
	APLFORMPTR	AplForm;
	POINT		Start,End;
	COLOR		fg;

		/* AplForm ポインタの値を 引数のWork から得る */
	AplForm = *Work;	/* 必ずFormSetup()内で UC_CALL_BACK_WORK_PID 設定済のこと! */

	switch( Message->CommonMsg.MessageID ) {
	case RedispID:
			/* ユーザーコントロールの左上の座標と右上の座標を Start, End に設定 */
		Start.x	= UserRect->left;
		Start.y	= UserRect->top;
		End.x	= UserRect->left + UserRect->width;
		End.y	= UserRect->top  + UserRect->height;

			/* 凹んだ感じの枠を描画 */
		DrawFrame( WnHandle, &Start, &End, GT_NORMAL,
			NORM_GCTABLESET, 2*4, DF_HOLLOWED, FSET, NULL );

			/* 2 [dot] ずつ内側に領域を狭める */
		Start.x += 2*4;
		Start.y += 2*4;
		End.x   -= 2*4;
		End.y   -= 2*4;

			/* 塗りつぶす色の設定 */
		SetColorRGB( &fg, 0, 102, 51 );
		SetColorInfo( &fg, 0 );

			/* 狭めた矩形領域の塗りつぶし */
		AreaClear( WnHandle, &Start, &End, &fg, FSET, NULL );

			/* 後は、牌を読み込みながら描く */
		ALLDRAW();

		break;
	}

	return( true );
}

void ALLDRAW( void )
{
	RECTANGLE	myrect;
	RESOURCE	myres;

	myrect.left = 230 * 4;	/* 4を掛けるのを忘れずに! */
	myrect.top  = 180 * 4;	/* (同上) */

	strcpy( myres.ResFile, "WST3IM00" );	/* リソースブロック */
	myres.ResID	= souzu1ID;		/* 末尾に ID が必要! */

	DrawImageRes( AplForm->FormWnHandle, &myrect, 0, &myres, NORM_SCALE, NULL );
}
さて、ここまで終わったら、再度ビルドして実際の動作を確かめてみてください。 再描画イベントへの対応を行ったことで、インデックス等を開いた後でも ちゃんと牌が表示されているはずです。 コントロールの背景も奇麗に塗りつぶされていますね。

次章ではグローバル変数とEXECワークの使い方を学びつつ、 実際のゲームと同様、画面配列変数 G[][] の内容に基づいた牌描画などを行いたいと思います。


次のページ 前のページ 目次へ