2.5 Canvasここでは、Canvas プロパティ の型としておなじみの TCanvas 型について解説します。TCanvas およびその一群の派生クラスは GDI の Device Context に対応するもので、Device Context を直接扱うよりもはるかにやさしくビットマップやスクリーンに絵を描くことが出来ます。ここでは Canvas の内部動作、及び、Device Context の機能と TCanvas の プロパティ/メソッド との関係を解説します。 2.5.1 TCanvas とその派生クラスTCanvas は TBitmapCanvas 等、いくつかの Canvas 用クラスが派生するベースクラスですが、TCanvas は抽象クラスではありません。ベースクラスだからといって、それ自体は直接使えないだろうと思ったら大間違いで、TCanvas は Device Context を扱うのに十分な機能を備えた一人前のクラスです。事実、TBitBtn は Owner Draw のための Canvas として TCanvas を直接使っています。 TCanvas の使い方は簡単です。TCanvas のインスタンスの Handle プロパティ に適当な Device Context のハンドルを代入すれば、その Device Context は TCanvas のインスタンスを介して簡単に操作できるようになります。TCanvas 型のインスタンスを破棄しても、Device Context は破壊されません。TCanvas は Device Context を包んで使いやすくする「だけ」のクラスなのです。 以上の様に TCanvas は便利なものですが、TCanvas は Device Context を作成したり、破棄したりする機能は持っていません。VCL 内ではほとんどの場合 TCanvas の派生クラス が利用されます。実際、各種クラスの Canvas プロパティ は TCanvas型 ではなく、TCanvas の派生クラスのインスタンスです。 VCL で定義されている TCanvas の派生クラスは以下の4種です。
以上の4種は Device Context の作り方、破壊のしかたを知っている専用化されたより便利なクラスです。これらの詳細に関しては後述します。また、以後 これらをまとめて、単に Canvas と総称することにします。 2.5.2 Canvas の提供する機能Canvas は Device Context の取り扱いを大幅にやさしくします。Canvas の Font, Brush, Pen プロパティ の実体は TFont, TBrush, TPen 型のインスタンスでリソースマネージャを使って GDI オブジェクト(論理フォント、論理ブラシ、論理ペン)の生成/廃棄を管理しています。また、Canvas は、Device Context の状態を管理します。Canvas は描画用メソッドの要求に応じて、Device Context を作成します(TCanvas はこれができないので、予め Handle プロパティ に Device Context を入れておく必要があります)。また、必要に応じて Font/Brush/Pen プロパティ から GDI オブジェクトを取得し、Device Context に選択(SelectObject)します。これらは自動的に行われるため、Canvas の利用者からは見えません。 例えば Canvas の FillRect メソッドが呼ばれると、ブラシが未選択なら Brush プロパティ の GetHandle メソッド呼び出して 論理ブラシを取得し、Device Context に選択してから描画します。 Device Context のペン、ブラシ、フォント以外のいくつかの属性は、面白いことに、Canvas の Pen, Brush, Font プロパティ の プロパティ から設定されます。以下に主な Device Context の属性がどのようなにセットされるかを示します。
お気づきかと思いますが、背景色や背景モードがブラシスタイルとブラシの色から決まったり、パレットが固定だったり、リージョンのサポートが無いなど、結構制限がきついのがお判りかと思います。これらをもっと柔軟に設定するには Canvas.Handle(つまり Device Context ハンドル)を利用すればよいのですが、使い方が難しい点があります。注意点は後述します。また、Brush.Color プロパティ と Brush.Style プロパティ は「背景色」と「背景モード」を決定するという重要な役割が有るにもかかわらず、2.4 TBrush で説明したように取り扱いがややこしい点に注意してください。 Font.Color がテキストカラーなのはまあ理解できますが、ブラシの色と背景色を一緒にしてしまったことや、ブラシスタイルと背景モードが関連しているのはの設計上の失敗だと思います。 2.5.3 Canvas の管理TCanvas には DeviceContext を破壊するような Public なメソッドは有りませんが。コントロールや TBitmap の Canvas プロパティ は、コントロールや TBitmap を破壊しない限り無くなりません。しかし、Windows の参考書は Device Context を メッセージの処理毎に作成し破壊しなさいとうるさいくらい書いてあります。Device Context は貴重な GDI リソース(Windows 9X/MEの場合)を消費する上、BeginPaint API, GetDC API, GetWindowDC API 等で取得する Device Context はシステムが予め用意している Device Context なので数に限りがあるからです。そこで VCL は Device Context の数が増えないように工夫しています。 TCanvasのリストを保持しているTThreadList型の変数が VCL の中に3個有ります。Graphics Unit 内の CanvasList, BitmapCanvasList とControls Unit 内の CanvasList です。 CanvasListは同じ名前のものが2ヶ所に有るので注意してください。 Graphics Unit 内の CanvasList は、生成された全ての TCanvas 及び、その派生クラスのインスタンスが登録されるリストです。このリストは、システムカラーの変更(WM_SYSCOLORCHANGE)を全てのCanvasに通知するのに使われます。ペンとブラシはシステムカラーが変更されたとき、全てのデバイスコンテキストから外し、破壊して作り直さなければなりませんが、Delphi はこれを自動的に行ってくれます。 Controls Unit 内の CanvasList は CreateHandle メソッドで Device Context を生成済の TControlCanvas型 のインスタンスのリストです。TControlCanvas は GetDC API か BeginPaint API で作成された スクリーン用の Device Context を保持する Canvas です。CanvasList は TControlCanvas のインスタンスが Createhandle メソッドで作成した Device Contextを最大4個に制限するのに使います。5個目のDevice Context が CreateHandle メソッドで作成されようとすると、リスト内のもっとも古い Device Context が解放(Release)され、Windows が確保している貴重なスクリーン用 Device Context が使い切られてしまうのを防ぎます。 しつこく、「Createhandle メソッドで作成した Device Context」と書いているのは、CreateHandle メソッドは GetDC(または GetDCEx)で Device Context を作成し、BeginPaint API は使わないからです。WM_PAINT メッセージの処理時、VCL は BeginPaint で取得した Device Context を TControlCanvas の Handle プロパティ に 代入します。この Device Context は上記の最大4個の制限の対象にはなりません。 Graphics Unit内のBitmapCanvasListは Device Contextが生成済みの TBitmapCanvas型のCanvas(つまりメモリDC)のリストです。 VCL では メッセージの処理が終わるとプログラムの制御が TWinControl.MainWndProc内に返ってきます。ここで、VCL は Controls Unit の CanvasList 中の 全ての Canvas、及び Graphics Unit 内の BitmapCanvasList 中の 全ての Canvas(メモリDC)の Device Context を破壊/解放します。つまり、ほとんどの Device Context はメッセージの処理毎に破壊/解放されてしまうのです。 尚、上記の Device Context の自動破棄では、ロックされた Canvas は対象外です。上記のDevice Context の 4個の制限や、メッセージ処理による Device Context の破棄が処理に都合が悪い場合、TCanvas の Lock メソッドで Canvas をロックしておけば問題を避けることができます。但し、使いすぎに注意してください。Windows 9X/ME では、ひとつのアプリケーションが6個以上のスクリーン用のデバイスコンテキストを GetDC/BeginPaint で取得するとプログラムが正常に動作しなくなります。ですから ロックはより多くの Device Context を取得するためではなく、デバイスコンテキストが破棄されては困るときにお使いください。 2.5.4 Canvas の Handle プロパティ 使用時の注意事項VCL は Device Context の属性をすべて使い切っていないため(特にパレット)、Device Context を直接触りたくなることがあります。Device Context のハンドルは Canvas の Handle プロパティ で得られますが、これを使う際には細心の注意が必要です。 2.5.3 で説明しましたように、TControlCanvas の Device Context は、他の TControlCanvas が Device Context を生成する時や、メッセージ処理の最後で破壊されてしまいます。従って、Device Context のハンドルを取得しても、いつのまにかそのハンドルが無効になってしまう可能性があります。 ハンドルが無効になっていても、Canvas.Handle から新しいハンドルを取得できますが、Device Context に Handle プロパティ を使って設定した属性は、当然消えてしまいます。注意が必要です。特に難しいのは Windows API を使ううちにいつの間にかメッセージが発生して、Device Context のハンドルが無効になる場合です。 例えば、論理パレットを作成してコントロールの Device Context に選択/実体化すると、システムパレットに変更がおき、WM_PALETTECHANGE メッセージが発生することが有ります。従ってパレットを使う場合は Canvas.Handle に対し、2回パレットを選択/実体化しなければなりません。ただし、OnPaint ハンドラー内での Canvas.Handle は BeginPaint で取得された Device Context なので、上述したように、ハンドラーが終了するまで破壊されず、安心して使えます。 この他にも、メッセージを生成する Windows API は腐るほど有り、かつ、どの API がメッセージを発生するかは マイクロソフトのドキュメントには明確には書いてありませんから、とにかく慎重に使うことが肝要です。どうしてもこの問題が避けられない場合のみ、Canvas をロックして問題を回避してください。 2.5.5 TPrinterCanvas と Font プロパティTPrinterCanvas は TPrinter の Canvas で、プリンターデバイスの Canvas です。この Canvas が他と大きく違うのは、TPrinter の状態によって Canvas が Infomation Context を持ったり、Device Context を持ったりすることです。 TPrinter の BeginDoc メソッドが呼ばれてから、EndDoc/Abort メソッドが呼ばれるまでは、TPrinter は プリンターの Device Context を持ちます。それ以外では Information Context を持ちます。 TPrinter の Canvas.Handle プロパティ は Device Context 専用で BeginDoc 以前に使おうとすると例外が発生します。Infomation Context のハンドルを取得するには TPrinter の Handle Preoperty を使用します。こちらを使えば、 BeginDoc メソッド実行前は Information Context、 実行後は Device Context が得られ便利です。 もう一つの重要な違いは、TFont の PixelsPerInch プロパティの動きです。TFont の PixelsPerInch はデバイスの論理インチ当たりのピクセル数で、GetDeviceCaps API で LOGPIXELSY を指定して取得されます。 TPrinterCanvas 以外の Canvas では、この値はスクリーンデバイスの値が使われます。このため、TFont のコンストラクターはインスタンス生成時、PixelsPerInch に取り敢えず、Screen 変数の持つ PixelsPerInch の値を入れてしまいます。つまり、TPrinterCanvas の Font.PexelsPerInch の値は最初は間違った値が入っているのです。 TPrinterCanvas は、Information Context 又は Device Context を作成するとき、正しい値を TFont の PixelsPerInch にセットします。この時、TPrinter Canvas は Font.Size にあわせて Font.Height を計算し直しなおします。従って、正しい PixelsPerInch がセットされる前に、Font プロパティ の属性をアプリケーション側でセットしていると、思わぬ大きさで文字が印刷されてしまいます。注意しましょう。もっとも簡単な対処法は、ダミーで Canvas.TextHeight を使い、強制的に PixelsPerInch を更新させることです。 2.5.6 Device Context の管理の問題点2.5.3 で述べましたように、VCL は TControlCanvas と TBitmapCanvas の Device Context をメッセージ処理の最後で自動的に破棄します。この機構自体は便利なものですが、Device Context の種類が上の2種類に限定されているのは大きな問題です。 例えば、ダウンロードエリア に拙作の DIB 専用の TNkDIB という TGraphic の派生クラスがあり、DIB に描画するために専用の Canvas クラス TNkDIBCanvas が用意されています。この Canvas は本当は TBitmap の Canvas と同様 TNkDIB クラスの Canvas プロパティ にしたいのですが、自動破棄ができないため、TNkDIB とは独立のクラスになってしまっています。つまり、現在の VCL の設計では Canvas プロパティ になりうる型は TBitmapCanvas か TControlCanvas の2つしかないのです(TPrinterCanvas は例外。この Canvas はアプリケーション毎に一つしかいらないので問題にならない)。CanvasList を公開し、任意の Canvas が登録出来るようにすることを Borland に強く望みます。 2.5.7 まとめ
|