● MOSA MPJ digital(2001/01/10)

  〜 コントロールを使おう! その3 〜

  このコラムは、MOSAの会員にのみ配布されているプログラマ向け
  デジタルマガジン MPJ digitalの2000年12月号に執筆したものです。


今回はダイアログへではなく、一般的なウィンドウにコントロールを配置する方法を解説したいと思います。また例題としてTabコントロールを取り上げ、その特徴や使用上の注意、属性の変更方法などについても解説してみます。

CreateNewWindow()でドキュメントやフローティングウィンドウを作成したら、そのWindowPtr(最近はWindowRef)を確保しておきます。コントロールをウィンドウに実装するのには、必ずこのWindowPtrが必要となります。通常、ウィンドウ上へコントロールを実装するのには次の2種類の方法が考えられます。まず一つ目は、ソースコード上でNewControl()にタイプ、タイトル、矩形枠などの各種パラメータを渡して作成する方法です。もう一つは、先んじて用意しておいた'CNTL'リソースを、GetNewControl()で呼び出して実装する方法です。どちらの方法でも結果は同じになりますが、表示位置が固定されているコントロールや、タイトルに日本語を表示しなければならないコントロールなどは、先んじて'CNTL'リソースを作成しておく方が便利です。ちなみに私の場合には、ウィンドウサイズの変更で表示位置がダイナミックに変わるコントロール(左右、上下のスクロールバー、ウィンドウのヘッダー、フッター等)はNewControl()で作成し、それ以外のコントロールは、GetNewControl()でリソースから呼び出すようにしています。


  図1-2つのスクロールバーと5つのコントロールをウィンドウに実装する例

コントロールの「表示位置」(矩形枠)をきちんと決めるのは結構面倒な作業です。つまり、ウィンドウ上にコントロールを美しくレイアウトする作業のことですね(笑)。例えば、NewControl()には表示位置を矩形枠として渡しますので、矩形情報はソースコード内に数値として埋め込まれています。比べて'CNTL'リソースから呼び出す場合には、その矩形枠をパラメータとしてリソースに設定しておく必要があります。どちらも数値を手入力で変更できるわけですが、'CNTL'リソースの位置決めには、Resorcererのダイアログアイテムのレイアウトツールを利用すると便利です。まずは配置用のウィンドウに見立てた少し大きめなダイアログ('DLOG'と'DITL')リソースを作ります。これはダミーで、実際に後から利用するのは、そこに配置した'CNTL'リソースのみとなります。ダイアログアイテムのレイアウトツールを、ウィンドウ上にコントロールをレイアウトするためのツールとして借用するわけです。

GetNewControl()で呼ばれたコントロールの表示位置は、'CNTL'リソース自身が保持している矩形枠です。前回もお話しましたが、Resorcererは、ダイアログアイテムの表示枠が変更されると、コントロール自身の表示枠もそれと一致させます。しかしResEditの場合には、ダイアログアイテムの原点座標が移動するだけです。よって、ResEditでダイアログ上に'CNTL'リソースを配置し、それをGetNewControl() 呼んだとしても、その位置はウィンドウ上に再現しませんので注意してください。また、Resorcererの場合でも、何かの拍子に配置位置がコントロールの矩形枠へ反映されない場合があります(バグ?)。こうした場合には、マウスドラッグや矢印キーで再度移動させれば、ちゃんと反映されますので試してみてください。

  
  図2-ダイアログアイテムのレイアウトツールをウィンドウ用に借用する

アプリケーション開発中、ウィンドウに実装したコントロールを個別に操作したい場合や、全部まとめて操作したい場合が出てきます。ところが、System 7が出た頃のControl Managerには、それを実現するための有効なAPIが存在しませんでした。そこで我々は、配置したコントロールのControlHandle(最近ではControlRef)を外部変数などに保存し、必要時にそれをアクセスするといた美しくない方法を取っていました。最近になり、Carbon環境であれば(CarbonLibをリンクした場合のみ)、SetControlID() とGetControlByID()と言う2つの便利なAPIを使えるようになり、コントロールの個別操作が容易になりました。

SetControlID() では、コントロールに固有の「Signature」(File Typeと同じ4文字識別子)と「ID番号」を付加することができます。よって、実装先のウィンドウのWidnowPtrさえ分かれば、今度はGetControlByID()にSignatureとID番号を渡すことで、いつでも目的のControlHandleを得ることができます。逆に全部まとめて操作したい場合には、CreateRootControl()でルートコントロールを作成しておきます。ルートコントロールとは、複数のコントロールを階層化し管理するための親コントロールのような物です。Mac OS 8でAppearance Managerが導入されたのと同時に利用できるようになりました。例えば、ウィンドウが背後に回った時には、ウィンドウのすべてのコントロールをDeactivate(ハイライト表示)にする必要があります。こうした処理も、CreateRootControl()で登録したルートコントロールを、処理時にGetRootControl()で呼び出せば簡単に実現できます。

  
  図3-SetControlID()、getMyControlID()、GetRootControl()の使用例

では例題として、ウィンドウ上にTabコントロールを配置し、Tabタイトルをマウスクリックすることで、ウィンドウに表示されるコントロールアイテム(複数のボタンなど)を切り替える処理を実現してみます。まずは先んじて、この機能をダイアログ上で実現してみます。最初の仕事は、Tabコントロール用の'CNTL'と'tab#'リソースを用意することです。Tabコントロールの場合、各Tabに表示するタイトル(文字列)やアイコン情報は、'CNTL'リソースから分離されて'tab#'リソースに保存されています。Resorcererで'tab#'リソースを編集したら、そのリソースのID番号を'CNTL'リソース側の「tab# ID」(通常の初期値のカラム)に入力しておきます。これでTabコントロールに必要なリソースの準備は完了となります。

  
  図4-'CNTL'リソースのために先んじて'tab#'リソースを編集しておく

Tabで切り替えるダイアログアイテムを編集する場合には、まずはTabコントロールだけを配置したダイアログを作ります(これが本体)。それ以外に、各Tabタイトルに属するアイテムリストを、それぞれ別の'DITL'リソースとして編集します。4つTabタイトルがあれば、4つのDITL'リソースを作るわけですね。実際のダイアログの処理では、Tabタイトルのどれかがマウスクリックされたら、現在表示されているダイアログアイテムのうち、Tabを除いたすべてのアイテムをShortenDIT()で削除します。例えばTabを含めて7つのダイアログアイテムがあるとすると、

ShortenDITL( dptr,6 );

といった具合に処理します。これによりTabを除いた6つのアイテムが全部削除されます。その後、新規に表示させたい別グループのアイテムリストをAppendDITL()で付加します。例えば、クリックされたTabタイトルのアイテムリストがID=1002の'DITL'リソースに保存されているとすると、その処理は、

AppendDITL( dptr,GetResource( 'DITL',1002 ),overlayDITL );

といった感じになります。

 
  図5-ShortenDITL()とAppendDITL()で表示するアイテムリストを切り替える

Tabコントロールのような階層化コントロール(コントロール内にコントロールを埋め込む)をダイアログで利用するには、前回お話したように'dlgx'リソースを作成し、そのビット1(kDialogFlagsUseControlHierarchy)をオンにしておく必要があります。この処理を行わないと、Tab以外のコントロールが思った通りに表示されません。ここでは試しに、'dlgx'リソースを作らないとどうなるのかを実験してみます。まずは、Tabコントロールをアイテムリストの一番最初に登録して表示させてみます。すると表示されるべきダイアログアイテムが、Tabに隠れて現れません。アイテムがありそうなところをマウスクリックしてみると、クリックは可能ですが、クリック後にアイテムの残骸が表示されてしまいます。

逆に、Tabコントロールをすべてのアイテムリストの一番最後に追加してみると、若干の表示異常を除いては一見問題なさそうに見えます。しかし、すべてのアイテムはTabコントロールの矩形エリア内に隠されているので、マウスクリックしても反応がありません。マウスクリックに対しては、Tabコントロールの矩形領域が優先されているわけです。これは至極当然な結果です。Macintoshのコントローラは、先にリストに登録された方が(Item Numberが小さい方が)上位に表示されます。そのくせ、マウスクリックの優先順位は後に登録された方が先となります。どう考えても「先に登録したアイテムの方が下に表示される」のが常識的だと思うのですが...。まあ、文句を言っても始まりませんが(笑)こうした問題に対処するために考え出されたのが、コントロールの階層化であり、'dlgx'リソースの役割というわけです。


  図6-左側がTabコントロールを最初に登録した場合、右側が最後に登録した場合

次は、Tabコントロールを一般的なウィンドウで利用してみます。'CNTL'と'tab#'リソースを用意する所まではダイアログの場合と同じです。ダイアログ同様、一般的なウィンドウでもコントロールの階層化をしておかないと、表示やマウスクリックに対する反応に不都合が起こります。ただし、ウィンドウの場合には'dlgx'リソースの仕組みを利用できませんので、それと同じ役割をするAPIを使い指示をします。まずは、最初にお話した手順どおり、CreateRootControl()でルートコントローラを作成します。続いてリソースに保存されているTabコントロールをGetNewControl() で呼び出します。最後に、各Tabタイトルに属するコントロールを呼び出しながら、EmbedControl()でそれらをTabコントロールに「埋め込み」ます。この部分が、ダイアログでの'dlgx'リソースのビット1をオンにするのと同じ役割をしています。Control Managerには、この埋め込み作業をすべて自動でおこなってくれるAutoEmbedControl()も用意されています。また、配置したコントロールを後から個別認識するために、SetControlID()でSignatureとID番号を割り振るのも忘れないでください。


 図7-Tabコントロールとそれに属するコントロールをウィンドウに登録している例

さて、このままだと特定のTabタイトルが選択されているにもかかわらず、すべてのコントロールアイテムがウィンドウ上に表示されてしまいます。よって、対象外のコントロールを消したり、再度表示したりするメンテナンスルーチンが必要となります。選択されていないTabタイトルに属するコントロールは、GetControlByID()で得たControlHandleをHideControl()に渡して一時的に消しておきます。HideControl()されたコントロールは表示されないだけでなく、ユーザのマウスクリックにも反応しません。この状態で別タイトルがクリックされたら、今度はそちらに属するコントロールをShowControl()で表示させ、今まで表示されていた方は消してしまいます。Tabタイトルの数分だけこうした切り替え処理をすることで、目的を達成することがでるわけです。


 図8-Tabタイトルを切り替えた時に呼び出すメンテナンスルーチンの例

Tabタイトルの数が多くなると、ウィンドウの幅にすべてのタイトルが収まらない場合があります。こうした時には、Tabタイトルのフォントサイズを小さくして対応する必要があります。「タイトルのフォントサイズ」など、コントロールタイプによって定義されている色々な属性は、SetControlData()で変更することができます。Tabタイトルのフォントサイズを設定し直すのには、SetControlData()に属性の識別子(Tagと言う)である「kControlFontStyleTag」やControlFontStyleRec構造体のポインターを渡します。先んじて、この構造体のflagsメンバーにはkControlUseSizeMaskを、sizeメンバーには好みのフォントサイズを代入しておきます。この時、flagsメンバーに渡すMaskパラメータの値を変えることで、Tabタイトルのフォントタイプ、スタイル、カラーなども変更することが可能です。各コントロールタイプに定義されているTagの種類やControlFontStyleRec構造体の詳しい内容については、Universal HeadersのControls.hを参照してください。


 図9-Tabタイトルのフォントサイズを変更する例とControlFontStyleRec構造体

copyright 2001 Ottimo, Inc. All rights reserved
無断転載・引用禁止
Contact Us: koike@ottimo.co.jp