プログラミング モデルの変更
ここでは、GDI+ でのプログラミングと GDI でのプログラミングの違いについて説明します。
デバイス コンテキスト、ハンドル、および Graphics オブジェクト
Windows の以前のバージョンに組み込まれていたグラフィックス デバイス インターフェイスである GDI を使用してプログラムを記述するには、デバイス コンテキストの概念を把握しておく必要があります。デバイス コンテキストは Windows で使用される構造体であり、特定のディスプレイ デバイスの機能に関する情報や、そのデバイスでアイテムがどのように描画されるかを指定する属性を格納します。ビデオ ディスプレイのデバイス コンテキストは、ディスプレイ上の特定のウィンドウにも関連付けられます。最初にデバイス コンテキストを識別するハンドル (HDC) を取得し、次に実際に描画を実行する GDI 関数にそのハンドルを引数として渡します。また、デバイス コンテキストの属性を取得または設定する GDI 関数にも、このハンドルを引数として渡します。
GDI+ では、ハンドルやデバイス コンテキストを使用する必要はありません。代わりに、Graphics オブジェクトを作成し、そのメソッドを一般的なオブジェクト指向の形式 (myGraphicsObject.DrawLine(parameters)) で呼び出すだけです。デバイス コンテキストが GDI の中核を担っていたのと同様に、Graphics オブジェクトは GDI+ の中核として機能します。デバイス コンテキストの役割と Graphics オブジェクトの役割は似ていますが、デバイス コンテキストを使用するハンドル ベースのプログラミング モデル (GDI) と Graphics オブジェクトを使用するオブジェクト指向のモデル (GDI+) の間には、基本的な違いがいくつかあります。
Graphics オブジェクトもデバイス コンテキストと同様に、画面上の特定のウィンドウに関連付けられ、アイテムの描画方法を指定するプロパティ (SmoothingMode、TextRenderingHint など) を備えています。しかし、Graphics オブジェクトは、ペン、ブラシ、パス、イメージ、またはフォントには関連付けられず、この点はデバイス コンテキストとは異なります。たとえば、デバイス コンテキストを使用して直線を描画する前には、SelectObject を呼び出してペン オブジェクトとデバイス コンテキストを関連付ける必要があります。この操作をデバイス コンテキストで使用するペンの選択と呼びます。別のペンを選択するまで、デバイス コンテキストでのすべての直線の描画には、選択したペンが使用されます。GDI+ では、Pen オブジェクトを引数として Graphics クラスの DrawLine メソッドに渡します。特定の Pen オブジェクトを Graphics オブジェクトに関連付ける必要はなく、一連の DrawLine 呼び出しのそれぞれで異なる Pen オブジェクトを使用できます。
直線を描画する 2 つの方法
(20, 10) の位置から (200, 100) の位置まで幅 3 の赤い直線を描画する 2 つの例を次に示します。最初の例では GDI を呼び出し、2 番目の例ではマネージ クラス インターフェイスを使用して GDI+ を呼び出します。
GDI を使用した直線の描画
GDI を使用して直線を描画するには、デバイス コンテキストとペンの 2 つのオブジェクトが必要です。BeginPaint を呼び出してデバイス コンテキストのハンドルを取得し、CreatePen を呼び出してペンのハンドルを取得します。次に、SelectObject を呼び出して、デバイス コンテキストで使用するペンを選択します。MoveToEx を呼び出してペンの位置を (20, 10) に設定し、LineTo を呼び出して、最初に設定した位置から (200, 100) まで直線を描画します。MoveToEx と LineTo は両方とも hdc (デバイス コンテキストを識別するハンドル) を引数として受け取ります。
HDC hdc;
PAINTSTRUCT ps;
HPEN hPen;
...
hdc = BeginPaint(hWnd, &ps);
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(hdc, hPen);
MoveToEx(hdc, 20, 10, NULL);
LineTo(hdc, 200, 100);
EndPaint(hWnd, &ps);
GDI+ とマネージ クラス インターフェイスを使用した直線の描画
C# と Visual Basic で記述した例を次に示しますが、このコードは、マネージ コードを生成できる任意の言語で記述できます。GDI+ とマネージ クラス インターフェイスを使用して直線を描画するには、Graphics オブジェクトと Pen オブジェクトが必要です。Graphics オブジェクトへの参照を取得する方法の 1 つは、フォームの OnPaint メソッドを使用することです。OnPaint メソッドの唯一のパラメータは PaintEventArgs 型の構造体ですが、この構造体のメンバの 1 つに Graphics オブジェクトが含まれています。
直線を描画するには、Graphics クラスの DrawLine メソッドを呼び出す必要があります。DrawLine メソッドの最初のパラメータは Pen オブジェクトです。この方法は、上の GDI の例で示した (デバイス コンテキストで使用するペンを選択する) 方法よりも簡単で柔軟性を備えています。
Class PlainForm
Inherits Form
Protected Overrides Sub OnPaint(e As PaintEventArgs)
Dim myPen As New Pen(Color.Red, 3)
Dim myGraphics As Graphics = e.Graphics
myGraphics.DrawLine(myPen, 20, 10, 200, 100)
End Sub 'OnPaint
End Class 'PlainForm
[C#]
class PlainForm : Form
{
protected override void OnPaint(PaintEventArgs e)
{
Pen myPen = new Pen(Color.Red, 3);
Graphics myGraphics = e.Graphics;
myGraphics.DrawLine(myPen, 20, 10, 200, 100);
}
}
パラメータとして渡されるペン、ブラシ、パス、イメージ、およびフォント
上の例は、描画メソッドを提供する Graphics オブジェクトとは別に Pen オブジェクトを作成および維持できることを示しています。Brush、GraphicsPath、Image、Font の各オブジェクトも、Graphics オブジェクトとは別に作成および維持できます。Graphics クラスが提供する描画メソッドの多くは、Brush、GraphicsPath、Image、または Font の各オブジェクトを引数として受け取ります。たとえば、Brush オブジェクトは FillRectangle メソッドに引数として渡され、GraphicsPath オブジェクトは DrawPath メソッドに引数として渡されます。同様に、Image オブジェクトと Font オブジェクトは、それぞれ DrawImage メソッドと DrawString メソッドに渡されます。この点で、デバイス コンテキストで使用するブラシ、パス、イメージ、またはフォントを選択してから、デバイス コンテキストを識別するハンドルを引数として描画関数に渡す GDI での処理とは異なっています。
メソッドのオーバーロード
GDI+ メソッドの多くはオーバーロードされています。つまり、複数のメソッドが同じ名前を共有していますが、それぞれのパラメータ リストは異なります。たとえば、Graphics クラスの DrawLine メソッドは次のように指定されます。
Overloads Sub DrawLine( _
pen As Pen, _
x1 As Single, _
y1 As Single, _
x2 As Single, _
y2 As Single)
End Sub 'DrawLine
Overloads Sub DrawLine( _
pen As Pen, _
pt1 As PointF, _
pt2 As PointF)
End Sub 'DrawLine
Overloads Sub DrawLine( _
pen As Pen, _
x1 As Integer, _
y1 As Integer, _
x2 As Integer, _
y2 As Integer)
End Sub 'DrawLine
Overloads Sub DrawLine( _
pen As Pen, _
pt1 As Point, _
pt2 As Point)
End Sub 'DrawLine
[C#]
void DrawLine(
Pen pen,
float x1,
float y1,
float x2,
float y2) {}
void DrawLine(
Pen pen,
PointF pt1,
PointF pt2) {}
void DrawLine(
Pen pen,
int x1,
int y1,
int x2,
int y2) {}
void DrawLine(
Pen pen,
Point pt1,
Point pt2) {}
上の 4 つの DrawLine メソッドは、Pen オブジェクト、開始点の座標、および終了点の座標を受け取ります。最初の 2 つのメソッドは座標を浮動小数点数として受け取り、最後の 2 つのメソッドは座標を整数として受け取ります。1 番目と 3 番目のメソッドは座標を 4 個の数値から成るリストとして受け取り、2 番目と 4 番目のメソッドは座標を Point (または PointF) オブジェクトのペアとして受け取ります。
現在の位置という概念の排除
上の DrawLine メソッドは、直線の開始点と終了点の両方を引数として受け取ります。これは、MoveToEx(hdc, x1, y1, NULL) を呼び出して現在のペンの位置を設定してから、LineTo(hdc, x2, y2) を呼び出して (x1, y1) から (x2, y2) まで直線を描画する GDI のスキームとは異なります。GDI+ では、現在の位置という概念が一切使用されなくなっています。
描画メソッドと塗りつぶしメソッドの分離
図形の輪郭を描画したり図形の内部を塗りつぶしたりする場合、GDI+ の方が GDI に比べてより柔軟性があります。GDI では、四角形の輪郭の描画と四角形内部の塗りつぶしをまとめて Rectangle 関数が実行します。輪郭は現在選択されているペンを使用して描画され、内部は現在選択されているブラシを使用して塗りつぶされます。
hBrush = CreateHatchBrush(HS_CROSS, RGB(0, 0, 255));
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(hdc, hBrush);
SelectObject(hdc, hPen);
Rectangle(hdc, 100, 50, 200, 80);
GDI+ では、四角形の輪郭を描画するメソッドと内部を塗りつぶすメソッドが異なっています。Graphics クラスの DrawRectangle メソッドにパラメータの 1 つとして Pen オブジェクトを渡し、FillRectangle メソッドにパラメータの 1 つとして Brush オブジェクトを渡します。
Dim myHatchBrush As New HatchBrush( _
HatchStyle.Cross, _
Color.FromArgb(255, 0, 255, 0), _
Color.FromArgb(255, 0, 0, 255))
Dim myPen As New Pen(Color.FromArgb(255, 255, 0, 0), 3)
myGraphics.FillRectangle(myHatchBrush, 100, 50, 100, 30)
myGraphics.DrawRectangle(myPen, 100, 50, 100, 30)
[C#]
HatchBrush myHatchBrush = new HatchBrush(
HatchStyle.Cross,
Color.FromArgb(255, 0, 255, 0),
Color.FromArgb(255, 0, 0, 255));
Pen myPen = new Pen(Color.FromArgb(255, 255, 0, 0), 3);
myGraphics.FillRectangle(myHatchBrush, 100, 50, 100, 30);
myGraphics.DrawRectangle(myPen, 100, 50, 100, 30);
GDI+ の FillRectangle メソッドと DrawRectangle メソッドは、四角形の左端、上端、幅、および高さをそれぞれ指定する引数を受け取ります。四角形の上下左右の端をそれぞれ指定する引数を受け取る GDI の Rectangle 関数とは、この点が異なります。さらに、GDI+ の Color クラスの FromArgb メソッドが 4 つのパラメータを持っていることに注意してください。後ろの 3 つのパラメータは、通常の赤、緑、および青の値です。最初のパラメータは、描画する色と背景色のブレンドの程度を指定するアルファ値です。
領域の構築
GDI は、領域を作成するための関数として CreateRectRgn、CreateEllpticRgn、CreateRoundRectRgn、CreatePolygonRgn、および CreatePolyPolygonRgn を提供します。同様に、GDI+ の Region クラスには四角形、楕円、角の丸い四角形、および多角形を引数として受け取るコンストラクタがあると想像できますが、実際にはそのようなコンストラクタはありません。GDI+ の Region クラスは、Rectangle オブジェクトを受け取るコンストラクタと、もう 1 つ、GraphicsPath オブジェクトを受け取るコンストラクタを提供します。楕円、角の丸い四角形、または多角形に基づいて領域を構築する場合は、楕円などを格納する GraphicsPath オブジェクトを作成し、その GraphicsPath オブジェクトを Region コンストラクタに渡すだけで簡単に構築できます。
GDI+ では、図形とパスを組み合わせることによって、複雑な領域を簡単に構築できます。Region クラスには、既存の領域をパスまたは別の領域を使用して拡張するために使用できる Union メソッドと Intersect メソッドがあります。GDI+ のスキームの利点の 1 つは、GraphicsPath オブジェクトが引数として Region コンストラクタに渡されるときに、このオブジェクトが破棄されないことです。GDI では、PathToRegion 関数を使用してパスを領域に変換できますが、このパスは処理中に破棄されてしまいます。また、GraphicsPath オブジェクトは引数として Union メソッドまたは Intersect メソッドに渡されるときにも破棄されないため、指定したパスをビルド ブロックとして使用して複数の異なる領域を構築できます。この例を次に示します。onePath は既に初期化されている単純または複雑な GraphicsPath オブジェクトであると想定します。
Dim region1 As New [Region](rect1)
Dim region2 As New [Region](rect2)
region1.Union(onePath)
region2.Intersect(onePath)
[C#]
Region region1 = new Region(rect1);
Region region2 = new Region(rect2);
region1.Union(onePath);
region2.Intersect(onePath);