コントロールのサブクラスを作成する / Graphicsクラスにより図形を描画する

C#.net

コントロールのサブクラスを作成する

Windows 10 / Visual Stadio Community 2019 / .NET Framework 4.7.2
[control_create1]

概要

コントロールクラスのサブクラスを作成して、デフォルトのボタンとやや異なる動きをするボタンを作成してみる。
本稿の目的は、コントロールクラスのサブクラス化により、ボタンのデフォルトの動作をいかに変えることができるかを確かめ、その過程でコントロールクラスの規定の要素や動作の仕組みを理解し、コントロールをカスタマイズする一般的な手法を大まかに理解することである。
ボタンは、コントロールに対する操作に応じてその外観を変える。具体的にはマウスのカーソルがコントロール上に乗ったとき外れたとき、マウスボタンをダウンしたときアップしたとき、それぞれの動作に合わせてボタンの枠線の色や太さ、背景色、テキストの色を変えているが、本プログラムではこれらを若干変えてみる。
なお、本プログラムが行っていることは、ボタンクラスのサブクラス化によっても可能であるが、コントロールクラスの一般的な働きを理解するという狙いから、ひとつクラスの構造を遡って、コントロールクラスをベースとしてみた。

実装のポイント

コントロールの外観を変えるということは具体的には、コントロールクラスの OnPaint関数の中で Graphicsクラスのメソッドによりテキストや図形イメージの描画を行うことである。

文字列の描画

文字列の描画は DrawStringメソッドを使用する。フォント情報から表示文字列のサイズを求め、それを元に表示位置の起点を決める。

矩形の描画

コントロールの枠線の表示は、DrawRectangleメソッドを使った矩形の描画により実現する。次のコードは、マウスのカーソルがコントロールの中に入ったとき、外側がライトブルーで内側が黒色の二重の枠線を表示するものである。
やり方は、まず最外枠から幅10ミリの黒い枠線を描き、その上から重ねて幅6ミリのライトブルーの枠線を描く。これにより、6ミリのライトブルーの枠線の内側に4ミリの黒い枠線があるように見える。
注意すべきは、線の属性を指定するPenクラスでは、線の幅はミリ単位である一方、図形を描く Graphicsクラスの世界では、長さの単位はピクセルである。この例のように二つの枠線を隣接して表示しようとするとき、DrawRectangleメソッドで、ミリ単位のPenの幅とピクセル単位の矩形の位置を組み合わせて描画すると、単位が違うので、どうしてもずが生じてしまう。そこでここでは、描画の開始位置をゼロポイントに揃えることで、二つの枠線の相対位置に違いが生じないようにしている。
[control_create2]
コントロール上でマウスをクリック(左ボタンをダウン)したとき、タイトルの文字色を赤に、コントロールの背景色を青にする。
[control_create3]

OnPaintメソッドの起動方法

コントロールに描画する Controlクラスの OnPaintメソッドは、描画が必要になったとシステムが判断したとき、自動的に起動されるものである。OnPaintメソッドをプログラムから明示的に起動したい場合は Invalidateメソッドを呼ぶ。
次の例は、マウスイベントの種類に応じた描画処理を行うために、イベントハンドラをオーバーライドし、Invalidateメソッドにより OnPaintメソッドを呼びだしている。
なお、このクラスのオブジェクトを所有するクラスが、そのオブジェクトに対しイベントハンドラを定義することもあり得るので、サブクラスでオーバーライドするメソッドは必ず基底クラスのハンドラを呼んでおくこと。そうしないと所有クラスで実装したハンドラが起動しない。

ソースコード

コントロールクラスのサブクラスを作る理由

提供されたクラスをそのまま使わず、サブクラスを作成するという選択は、どのような理由によるのだろうか?
ひとつはコントロールの部品化であろう。具体的には、外観に関わる属性であるコントロール自身のサイズ、文字のフォントや色、背景色といったものを規格化することである。
通常、設計段階では、統一的なデザインの考え方のもと、コントロールの外観等は標準化されているはずだ。しかしドキュメントレベルの規約だけでは、プログラマはすべてのコントロールに対して、決められた属性の値を繰り返し記述する必要があり、これは非常に効率の悪い作業である。またミスや勘違いによって生じる規約からの逸脱を完全に防ぐことはできない。
コントロールのサブクラス化は、このようなことを防ぐための技術的な対応である。サブクラスは、標準化の対象となる属性に規定値を設定し、属性をカプセル化する。開発者はこれを利用することにより、必要のない属性の設定を行うことなく、サブクラスのデフォルト値だけで完全に標準化に則ったな実装を効率よく実現することができる。