X作成講座 on TC(11)  外部ウィンドウのプロパティ


 今回は外部ウィンドウにプロパティを持たせる方法を説明します。プロパティの取得/設定もイベントの一種なのですが、マニュアルの記述だけでは分かりにくいところもあるようなので、ここにサンプルを載せます。

 今回作るXは、picID というプロパティを持った外部ウィンドウを作ります。 set the picID of window "myWin" to 500 などと実行すると、ウィンドウのピクトが変更される訳です。ソースは X on TC [07] にあるドキュメントウィンドウを改造したもので、プロパティ処理以外は基本的に同じです。変更した部分に ***** を付けてあります。


// --------------------------------------
//	xDocWin template  2000.6.14 ( c )UDI
//	for THINK C 7.1
// --------------------------------------

#include "HyperXCmd.h"
#include "SetUpA4.h"

/* Prototypes */
void createWindow( XCmdPtr paramPtr );
void doEvent( XCmdPtr paramPtr );
WindowPtr getWinPtr( Str255 targetStrName );

typedef struct {				//******
	short		picID;
	PicHandle	picHand;
} myData;

pascal void main( XCmdPtr paramPtr ){
	RememberA0( );
	SetUpA4( );

	if ( paramPtr->paramCount > -1 ){
		createWindow( paramPtr );
	} else {
		doEvent( paramPtr );
	}

	RestoreA4( );
}

void createWindow( XCmdPtr paramPtr ){
	Str255	tempStr, winName;
	short	theLeft, theTop, theRight, theBottom, picID;
	Rect	winRect;
	PicHandle	myPicHand;
	WindowPtr	myWindowPtr;
	myData		**myDHand;		//*****

	ZeroToPas( paramPtr, *paramPtr->params[0], winName );
	if( getWinPtr( winName ) ){
		SysBeep(1);
		return;
	}
	ZeroToPas( paramPtr, *paramPtr->params[1], tempStr );
	picID = StrToNum( paramPtr, tempStr );

	myPicHand = GetPicture( picID );
	if ( myPicHand == nil ){
		paramPtr->returnValue = PasToZero( paramPtr, "\pNot found pict" );
		return;
	}

	if ( paramPtr->paramCount > 2 ){
		ZeroToPas( paramPtr, *paramPtr->params[2], tempStr );
		StrToRect( paramPtr, tempStr, &winRect );
	} else {
		winRect = ( **myPicHand ).picFrame;
		OffsetRect( &winRect, 100,100 );	// move winRect anyplace
	}

	myWindowPtr = NewXWindow(paramPtr, &winRect, winName, false, 8, true, false);
	if ( myWindowPtr == nil ){
		paramPtr->returnValue = PasToZero( paramPtr, "\pCan't create win" );
		return;
	}
	myDHand = ( myData**)NewHandle( sizeof( myData ) );	//****
	( *myDHand )->picHand = myPicHand;			//****
	( *myDHand )->picID = picID;				//****
	SetWRefCon( myWindowPtr, ( long )myDHand );
}

void doEvent( XCmdPtr paramPtr ){

	GrafPtr		savePort;
	EventRecord	myEvent;
	WindowPtr	myWindowPtr;
	XWEventInfoPtr	myXWEventInfoPtr;
	PicHandle	myPicHand;
	myData		**myDHand;				//*****
	Str255		*propNamePtr, theStr;			//*****


	myXWEventInfoPtr = ( XWEventInfoPtr)(paramPtr->params[0] );
	myWindowPtr = myXWEventInfoPtr->eventWindow;
	myEvent = myXWEventInfoPtr->event;

	GetPort( &savePort) ;
	SetPort( myWindowPtr );

	switch ( myEvent.what ){
		case mouseDown:
			switch ( FindWindow( myEvent.where, &myWindowPtr ) ){
				case inGoAway:	// closeBox
					if ( TrackGoAway( myWindowPtr, myEvent.where ) ){
						CloseXWindow( paramPtr, myWindowPtr );
					}
					break;

				case inDrag:	// titleBar
					paramPtr->passFlag = true;
					break;

					case inContent:	// window content
					SelectWindow( myWindowPtr );
					break;
			}
			break;

		case xOpenEvt:
			XWAllowReEntrancy(paramPtr, myWindowPtr, true, true);
			ShowWindow( myWindowPtr );
			paramPtr->passFlag = true;
			break;

		case xCloseEvt:
			paramPtr->passFlag = true;
			break;

		case updateEvt:
			BeginUpdate( myWindowPtr );
			myDHand = ( myData** )GetWRefCon( myWindowPtr );	//*****
			myPicHand = ( *myDHand )->picHand;			//*****
			DrawPicture( myPicHand, &( **myPicHand ).picFrame );
			EndUpdate( myWindowPtr );
			break;

		case activateEvt:
			break;

		case app4Evt:
			break;

		case xCursorWithin:
			paramPtr->passFlag = true;	// change arrowCursor by HC
			break;

		case xGetPropEvt:	//*****
			propNamePtr = ( Str255*)myXWEventInfoPtr->eventParams[0];
			if ( EqualString( *propNamePtr, "\ppicID", false, false ) ){
				myDHand = ( myData** )GetWRefCon( myWindowPtr );
				NumToStr( paramPtr, (*myDHand)->picID, theStr );
				myXWEventInfoPtr->eventResult = PasToZero( paramPtr, theStr );
				paramPtr->passFlag = false;
				break;
			}
			paramPtr->passFlag = true;
			break;

		case xSetPropEvt:	//*****
			propNamePtr = ( Str255*)myXWEventInfoPtr->eventParams[0];
			if ( EqualString( *propNamePtr, "\ppicID", false, false ) ){
				ZeroToPas( paramPtr, *( Handle)( myXWEventInfoPtr->eventParams[1] ), theStr );
				myDHand = ( myData** )GetWRefCon( myWindowPtr );
				(*myDHand)->picID = StrToNum( paramPtr, theStr );
				myPicHand = GetPicture( (*myDHand)->picID );
				(*myDHand)->picHand = myPicHand;
				SizeWindow( myWindowPtr, ( *myPicHand )->picFrame.right, ( *myPicHand )->picFrame.bottom, false );
				InvalRect( &( myWindowPtr->portRect ) );
				paramPtr->passFlag = false;
				break;
			}
			paramPtr->passFlag = true;
			break;
		}

	SetPort( savePort );
}

WindowPtr getWinPtr( Str255 targetStrName ){
	Str255	titleStr;
	WindowPtr	theWinPtr;

	theWinPtr = ( WindowPtr)LMGetWindowList();
	while( theWinPtr ){
		GetWTitle( theWinPtr, titleStr );
		if( EqualString( targetStrName, titleStr, true, true ) ){
			return theWinPtr;
		}
		theWinPtr = ( WindowPtr)( ( ( CWindowPeek)theWinPtr )->nextWindow );
	}
	return ( WindowPtr)0;
}

 プロパティを作るにはプロパティ値を保存しておく場所を作らなくてはなりませんが、外部ウィンドウ処理ではグローバル変数をその目的に使うことは出来ないことに注意しましょう。何故なら外部ウィンドウを作るXは、ウィンドウ作成後に一度Xを抜け、イベントの度に改めてXを実行するのです。グローバル変数の値はX呼び出しの度にクリアされてしまいますから、イベントを越えて同じデータを共有することは出来ません。
 マックOSではウィンドウごとのデータを保持するのに refCon というものを利用します。これはウィンドウ構造体の一部として用意されているもので、プログラマが自由に使うことの出来る4バイトの空間です。外部ウィンドウを使うXでもこの refCon を利用することにより、ウィンドウにプロパティを持たせることが出来ます。前回( X on TC [07] )はこの refCon に直接ピクトのハンドルを入れていましたが、今回はここに自前のハンドルを入れて利用します。

   −・−・−・−・−・−・−・−・−・−・−・−・−・−・−・−

 ではソースを追って解説してみましょう。
typedef struct {				//******
	short		picID;
	PicHandle	picHand;
} myData;
 ここでは自分のデータを入れるための構造体を宣言しています。今回はウィンドウに表示するピクトのIDをプロパティとして持たせるので、ピクトIDを入れる short と、ピクトのハンドルを入れる PicHandle型のメンバーを作っておきます。

 ウィンドウを作る前に、
	myData		**myDHand;		//*****
 を宣言しておきます。 myDHand というハンドル変数が指している先は myData であるという意味ですね。
 ウィンドウを作ったらこの myDHand をウィンドウの refCon に入れます。
	myDHand = ( myData**)NewHandle( sizeof( myData ) );	//****
	( *myDHand )->picHand = myPicHand;			//****
	( *myDHand )->picID = picID;				//****
	SetWRefCon( myWindowPtr, ( long )myDHand );
 まず NewHandle で自分のデータを格納するメモリブロックを作ります。それから picHandメンバーにピクトのハンドル、picIDメンバーにはピクトのIDを入れます。そしてこのメモリブロックのハンドルを、refCon に入れます。 X on TC [07] の時に比べてワンクッション余分に入っていることになります。こうすることによって、4バイトを越えるデータを refCon に保存することが出来ます。 refCon は long型で定義されているので、myDHand は long にキャストしています。


 前回のプログラムを思い出して下さい。最初にウィンドウを作る時に、ウィンドウの refCon にピクトハンドルを入れました。そして updateEvt でそのハンドルを取り出し、ウィンドウ内に描画します。
 今回もデータの格納方法が変わっただけで流れは一緒です。再描画の時はまず refCon から myDHand を読み出し(例によって long から目的の型へのキャストが必要になります)、そこからピクトハンドルを取り出してウィンドウに描画します。
	case updateEvt:
		BeginUpdate( myWindowPtr );
		myDHand = ( myData** )GetWRefCon( myWindowPtr );	//*****
		myPicHand = ( *myDHand )->picHand;			//*****
		DrawPicture( myPicHand, &( **myPicHand ).picFrame );
		EndUpdate( myWindowPtr );
		break;

   −・−・−・−・−・−・−・−・−・−・−・−・−・−・−・−


 さて、ここからプロパティの処理に入ります。
	case xGetPropEvt:	//*****
		propNamePtr = ( Str255*)myXWEventInfoPtr->eventParams[0];
		if ( EqualString( *propNamePtr, "\ppicID", false, false ) ){
			myDHand = ( myData** )GetWRefCon( myWindowPtr );
			NumToStr( paramPtr, (*myDHand)->picID, theStr );
			myXWEventInfoPtr->eventResult = PasToZero( paramPtr, theStr );
			paramPtr->passFlag = false;
			break;
		}
		paramPtr->passFlag = true;
		break;
 HyperTalk から get the [propName] of window [winName] が実行されると、イベントとして xGetPropEvt が送られて来ます。この時の [propName] に相当するプロパティ名は、( Str255*)myXWEventInfoPtr->eventParams[0] で取り出すことが出来ます。
 取り出したプロパティ名が自分が処理したいものであるかを調べ( EqualString )、処理すべきなら作業を行って、paramPtr->passFlag に false を設定して抜けます。この要領でいくつでもプロパティを作ることが出来ます。
 処理したくない(対応する気のない)プロパティ名だった場合は、paramPtr->passFlag に true を設定してプログラムを抜けます。以後このプロパティの処理は HyperCard に任されます。 rect や visible は HyperCard が対応することが出来るので、これらのプロパティは(Xで対応せずに) HyperCard に任せれば良いでしょう。 HyperCard にも処理出来ないプロパティだった場合は、「そのプロパティは取り出せません」というエラーメッセージを出して HyperTalk が止まることになります。

 処理の内容を見てみましょう。ウィンドウを作る時に保存しておいた myDHand の中の picIDメンバーを取り出しています。これを一旦P文字列に変換し、更にC文字列ハンドルに変換してから、myXWEventInfoPtr->eventResult に書き込みます。 HyperTalk はここに書かれたC文字列ハンドルをプロパティの返値と解釈します。 paramPtr->returnValue に書き込んでも読みとってくれませんのでご注意。


 大抵のプロパティの読み出しは自前のデータをC文字列ハンドルに変換して返すだけなので、余り複雑になることは無いと思います。一方、多くの場合、プロパティの設定はデータの置き換えやウィンドウの再描画等が伴います。
	case xSetPropEvt:	//*****
		propNamePtr = ( Str255*)myXWEventInfoPtr->eventParams[0];
		if ( EqualString( *propNamePtr, "\ppicID", false, false ) ){
			ZeroToPas( paramPtr, *( Handle)( myXWEventInfoPtr->eventParams[1] ), theStr );
			myDHand = ( myData** )GetWRefCon( myWindowPtr );
			(*myDHand)->picID = StrToNum( paramPtr, theStr );
			myPicHand = GetPicture( (*myDHand)->picID );
			(*myDHand)->picHand = myPicHand;
			SizeWindow( myWindowPtr, ( *myPicHand )->picFrame.right, ( *myPicHand )->picFrame.bottom, false );
			InvalRect( &( myWindowPtr->portRect ) );
			paramPtr->passFlag = false;
			break;
		}
		paramPtr->passFlag = true;
		break;
 HyperTalk から set the [propName] of window [winName] to [value] が実行されると、この xSetPropEvt が送られて来ます。
 プロパティ名の取り出しは xGetPropEvt と同じです。また設定する値([value])は myXWEventInfoPtr->eventParams[1] にC文字列ハンドルとして格納されていますが、この配列が long で定義されているために ( Handle) でキャストする必要があります。あーC言語ってメンドクサイ(^^;)
 値を読み出して、それを数値に変換( ZeroToPas、StrToNum )したものを、myDHand の picIDメンバーに書き込んでいます。更にこのIDからピクトのハンドルを得て( GetPicture )、それを myDHand の picHandメンバーに書き込みます。ここでは省略していますが、そのIDのピクトリソースが本当にあるのかどうか、チェックした方が良いでしょう。
 このプログラムでは picHand はリソースハンドルなので、古いハンドルはそのまま無視して上書きしています。古いハンドルは不要になったらリソースマネージャが破棄してくれるでしょう。もしこのハンドルが独立したピクトハンドルだった場合は、プログラムから処分する必要があります。このへんのデータ管理については後ほど少し触れてみます。

 さてピクトのIDとハンドルを書き換えたら、ウィンドウも整えなければなりません。まずウィンドウの大きさを新しいピクトの大きさに合わせます。ピクトの right を幅、bottom を高さとして、SizeWindow を呼び出します。そしてウィンドウのレクトを InvalRect に渡すと、強制的にウィンドウ全体の再描画が起こります(実際に作業するのは updateEvt ルーチン)。これで新しいピクトが表示されます。

   −・−・−・−・−・−・−・−・−・−・−・−・−・−・−・−


 実際にコンパイルして実験してみましょう。最初に、id が 1000 のピクトリソースと、1001 のピクトリソースをスタックに入れておきます。
    xWin "testWin", 1000
 を実行すると、id 1000 のピクトを表示する外部ウィンドウが現れます。前回のXと同じ動作ですね。このウィンドウを表示させたままで、
      set the picID of window "testWin" to 1001
 を実行すると、id 1001 のピクトが表示され、ピクトに合わせてウィンドウの大きさも変更されます。ウィンドウ内全体がちゃんと描き直されるのは、InvalRect を実行しておいたお陰です。(これをコメントアウトするとどうなるか実験するのも面白いです) プロパティがどうなっているか確認するために、
      put the picID of window "testWin"
 を実行してみます。変更後の id である 1001 が表示されます。めでたしめでたし。

   −・−・−・−・−・−・−・−・−・−・−・−・−・−・−・−


 さて、refCon に保存するデータについて少し考えてみます。

 ウィンドウにピクトを表示させたい場合は、ピクトのハンドルなりIDなりを refCon (や、refCon に繋がったハンドル)に保存することになります。例えば今回のようにピクトのリソースハンドルを保存して再描画の時にそれを使う方法は、実は必ずしもお奨め出来ない方法です。もしもウィンドウ表示中に他のXからピクトリソースを変更されたら、それまで使っていたリソースハンドルが無効になってしまうからです。滅多に無い事でしょうし、そもそも行儀の悪いことではありますが、無いとは言い切れません。
 ではピクトをIDで管理して、再描画の度にリソースを読み込んだら安全でしょうか。他のXからピクトリソースを変更された場合でも取りあえず爆弾を出さずに動き続けることは出来ますが、新しいピクトリソースを表示出来るのは再描画される部分だけであり、結局は完全に対応することは出来ません。また再描画の度にディスクアクセスが発生して、結構うっとおしい感じがします。

 一番安全なのは、ピクトリソースハンドルを複製して、そのハンドルを refCon なりに保存する方法です。このハンドルはリソースから独立したハンドルになりますから、リソース操作の影響を受けません。またピクトファイルを読んでウィンドウに表示する場合にも対応できます。その代わり、ウィンドウを閉じる時や、プロパティ操作によってピクトを変更する時は、不要になったハンドルをプログラマが責任を持って破棄する必要があります。

 また、多少高度な部類に入りますが、仮想スクリーンを使う方法があります。最初に仮想スクリーンにピクトを描画して、refCon にはその PixMap を保存します。再描画はイメージの転送だけで済むのでとても高速ですし、色数のコントロールも出来ます。やろうと思えば画像の合成だって可能でしょう。 HyperCard に標準で付いている Picture XCMD はこの方法でピクトを表示していると思われます。ただプログラムが複雑になるのが難点です。
 外部ウィンドウの目的や性質によっても方法は異なってくると思いますので、色々研究してみて下さい。


Next



THINK C Lab.

UDI's HomePage
inserted by FC2 system