WisdomSoft - for your serial experiences.

4.2 線を引く

最も基本的な図形オブジェクトである線の描画について説明します。

4.2.1 Line クラス

単純な一本の線を描画するには System.Windows.Shapes.Line クラスを利用します。Line クラスのオブジェクトが、画面上に表示される 1 本の線に対応します。

System.Windows.Shapes.Line クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Media.Visual 
         System.Windows.UIElement 
           System.Windows.FrameworkElement 
             System.Windows.Shapes.Shape 
              System.Windows.Shapes.Line
public sealed class Line : Shape

Line クラスのコンストラクタは、パラメータを何も受け取りません。

Line クラスのコンストラクタ
public Line ()

線は、既定の状態では始点と終点が共に 0 で初期化されているため、適切な座標を設定しなければ表示されません。線の始点を設定するには X1 プロパティに X 座標を、Y1 プロパティに Y 座標を設定します。それぞれ、ピクセル単位の値です。

Line クラス X1 プロパティ
[TypeConverterAttribute(typeof(LengthConverter))] 
public double X1 { get; set; }
Line クラス Y1 プロパティ
[TypeConverterAttribute(typeof(LengthConverter))] 
public double Y1 { get; set; }

同様に、線の終点は X2 プロパティに X 座標を、Y2 プロパティに Y 座標を設定します。

Line クラス X2 プロパティ
[TypeConverterAttribute(typeof(LengthConverter))] 
public double X2 { get; set; }
Line クラス Y2 プロパティ
[TypeConverterAttribute(typeof(LengthConverter))] 
public double Y2 { get; set; }

始点、終点は共に、Line オブジェクトが配置される親コントロールの相対座標となります。

線の始点と終点の他に、線を何色で描画するかを設定しなければなりません。線の色は、Shape クラスの図形の輪郭線の塗りつぶし方法を表す Stroke プロパティから設定します。

Shape クラス Stroke プロパティ
public Brush Stroke { get; set; }

このプロパティは、Line クラスの基底クラスである Shape 抽象クラスで定義されているプロパティで、図形の輪郭線を描画する時に利用する論理ブラシを設定または取得します。線の場合は「内部」という考え方が存在しないので、Stroke は、すなわち線の塗りつぶし方法を表します。

コード1
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main() {
		Line line = new Line();
		line.Stroke = Brushes.Black;
		line.X1 = 10;
		line.Y1 = 10;
		line.X2 = 400;
		line.Y2 = 200;

		Window wnd = new Window();
		wnd.Content = line;

		Application app = new Application();
		app.Run(wnd);
	}
}
実行結果
コード1 実行結果

コード1は、単純な 1 本の線を描画するプログラムです。従来の GDI とは異なり、関数やメソッドを使って出力デバイスに直接描画するのではなく、図形そのものがオブジェクトとして表現され、図形オブジェクトの情報に基づいて WPF が最終的に描画してくれるという仕組みを採用しているところに注目してください。例えば、従来の Windows Forms プログラミングであれば Form クラスを継承して OnPaint() メソッドをオーバーライドするか、Paint イベントに描画処理を行うメソッドのデリゲートを登録する作業が必要でした。しかし、WPF では再描画用のイベントハンドラを定義することなく、こうした図形を出力するプログラムを記述できてしまうのです。

このプログラムでは、Line オブジェクトを表示する Window オブジェクトのコンテンツとして表示しています。よって、Line オブジェクトは親コンテンツである Window コントロールの左上隅からの相対座標で描画されます。この Line オブジェクトの場合、始点座標 (10, 10) から、斜め右下に向かって終点 (400, 200) まで線が引かれます。線の色は Stroke プロパティに黒色で塗りつぶすことを表す Black ブラシを設定しているため、黒で表示されます。

複数の図形を組み合わせるには、パネルを利用する必要があります。図形はコントロールとは役割が異なるため、自動的にサイズ調整が行われては不都合かもしれません。そのような場合、自動的な配置やサイズ調整が行われない Canvas パネルに貼り付けることになるでしょう。

コード2
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main() {
		Line line1 = new Line();
		line1.Stroke = Brushes.Blue;
		line1.X1 = 10;
		line1.Y1 = 10;
		line1.X2 = 410;
		line1.Y2 = 210;

		Line line2 = new Line();
		line2.Stroke = Brushes.Red;
		line2.X1 = 10;
		line2.Y1 = 210;
		line2.X2 = 410;
		line2.Y2 = 10;

		Line line3 = new Line();
		line3.Stroke = Brushes.Green;
		line3.X1 = 10;
		line3.Y1 = 110;
		line3.X2 = 410;
		line3.Y2 = 110;

		Canvas canvas = new Canvas();
		canvas.Children.Add(line1);
		canvas.Children.Add(line2);
		canvas.Children.Add(line3);

		Window wnd = new Window();
		wnd.Content = canvas;

		Application app = new Application();
		app.Run(wnd);
	}
}
実行結果
コード2 実行結果

コード2は、複数の Line オブジェクトを表示するプログラムです。このプログラムの場合は Canvas オブジェクトを生成し、Canvas の子要素としてそれぞれの Line オブジェクトを追加しています。

4.2.2 線の太さ

線の幅は Shape クラスの StrokeThickness プロパティから設定することができます。このプロパティも Shape クラスで定義されているものなので Line クラスに限らず、他の形を表すクラスでも、輪郭を描画する線の太さとして利用することができます。

Shape クラス StrokeThickness プロパティ
[TypeConverterAttribute(typeof(LengthConverter))] 
public double StrokeThickness { get; set; }

このプロパティには、線の幅をピクセル単位で指定します。既定では 1 ピクセルの幅の線が描画されますが、より大きい値を設定することで太い幅の線を描画することができます。

コード3
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main() {
		Line line1 = new Line();
		line1.Stroke = Brushes.Blue;
		line1.StrokeThickness = 5;
		line1.X1 = 10;
		line1.Y1 = 10;
		line1.X2 = 400;
		line1.Y2 = 10;

		Line line2 = new Line();
		line2.Stroke = Brushes.Red;
		line2.StrokeThickness = 10;
		line2.X1 = 10;
		line2.Y1 = 60;
		line2.X2 = 400;
		line2.Y2 = 60;

		Line line3 = new Line();
		line3.Stroke = Brushes.Green;
		line3.StrokeThickness = 20;
		line3.X1 = 10;
		line3.Y1 = 110;
		line3.X2 = 400;
		line3.Y2 = 110;

		Canvas canvas = new Canvas();
		canvas.Children.Add(line1);
		canvas.Children.Add(line2);
		canvas.Children.Add(line3);

		Window wnd = new Window();
		wnd.Content = canvas;

		Application app = new Application();
		app.Run(wnd);
	}
}
実行結果
コード3 実行結果

コード3は、3 色のそれぞれ異なる Line オブジェクトの StrokeThickness に異なる値を設定したものです。実行結果を見ると、描画された線の太さが異なっていることが確認できます。

4.2.3 キャップ

線の始点と終点の先端部分をどのように描画するかは、キャップと呼ばれる情報で決定されます。始点側の先端は StrokeStartLineCap プロパティで、終点側の先端は StrokeEndLineCap プロパティで設定します。

Shape クラス StrokeStartLineCap プロパティ
public PenLineCap StrokeStartLineCap { get; set; }
Shape クラス StrokeEndLineCap プロパティ
public PenLineCap StrokeEndLineCap { get; set; }

これらのプロパティには、キャップの種類を表す System.Windows.Media.PenLineCap 列挙体のメンバのいずれかを指定します。

System.Windows.Media.PenLineCap 列挙体
public enum PenLineCap

この列挙体には、以下のようなメンバが定義されています。StrokeStartLineCap と StrokeEndLineCap プロパティには、既定で Flat が設定されています。

  • Flat : キャップを使わない。線は指定座標上に四角形で終わる。
  • Round : 半円系の丸いキャップ。
  • Square : 四角形のキャップ
  • Triangle: 三角形の先端が尖ったキャップ。

例えば、Line オブジェクトに対して Round を設定することで、線の先端が丸く描画されます。

コード4
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main() {
		Line line1 = new Line();
		line1.Stroke = Brushes.Blue;
		line1.StrokeThickness = 20;
		line1.StrokeStartLineCap = PenLineCap.Flat;
		line1.StrokeEndLineCap = PenLineCap.Flat;
		line1.X1 = 20;  line1.Y1 = 30;
		line1.X2 = 400; line1.Y2 = 30;

		Line line2 = new Line();
		line2.Stroke = Brushes.Red;
		line2.StrokeThickness = 20;
		line2.StrokeStartLineCap = PenLineCap.Square;
		line2.StrokeEndLineCap = PenLineCap.Square;
		line2.X1 = 20;  line2.Y1 = 80;
		line2.X2 = 400; line2.Y2 = 80;

		Line line3 = new Line();
		line3.Stroke = Brushes.Green;
		line3.StrokeThickness = 20;
		line3.StrokeStartLineCap = PenLineCap.Round;
		line3.StrokeEndLineCap = PenLineCap.Round;
		line3.X1 = 20;  line3.Y1 = 130;
		line3.X2 = 400; line3.Y2 = 130;

		Line line4 = new Line();
		line4.Stroke = Brushes.Black;
		line4.StrokeThickness = 20;
		line4.StrokeStartLineCap = PenLineCap.Triangle;
		line4.StrokeEndLineCap = PenLineCap.Triangle;
		line4.X1 = 20;  line4.Y1 = 180;
		line4.X2 = 400; line4.Y2 = 180;

		Canvas canvas = new Canvas();
		canvas.Children.Add(line1);
		canvas.Children.Add(line2);
		canvas.Children.Add(line3);
		canvas.Children.Add(line4);

		Window wnd = new Window();
		wnd.Content = canvas;

		Application app = new Application();
		app.Run(wnd);
	}
}
実行結果
コード4 実行結果

コード4は、線の始点と終点の先端に PenLineCap 列挙体の各メンバを指定し、表示するプログラムです。一番上の青い線は既定の先端である Flat です。2 番目の赤い線は Square、3 番目は Round、一番下は Triangle です。先端は Line オブジェクトの StrokeThickness に設定されている幅に従って描画されます。Flat と Square を比較すれば、Square は線の幅の長さだけ四角形の先端で埋められていることが確認できます。

4.2.4 点線

輪郭線を一本の線ではなく点線で描画したい場合、StrokeDashArray プロパティを利用します。点線は、一定間隔で線と空白を Line オブジェクトで定義されている始点と終点まで繰り返しながら描画されます。このとき、点線の描画される小さな線をダッシュ、空白部分をギャップとも呼びます。

Shape クラス StrokeDashArray プロパティ
public DoubleCollection StrokeDashArray { get; set; }

StrokeDashArray プロパティは、ダッシュとギャップの間隔を定義する System.Windows.Media.DoubleCollection クラスのオブジェクトを要求します。このクラスは、double 型の値のコレクションを表すもので、ダッシュの長さとギャップの長さは、double 型の値で表されます。

System.Windows.Media.DoubleCollection クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
        System.Windows.Media.DoubleCollection
[TypeConverterAttribute(typeof(DoubleCollectionConverter))] 
public sealed class DoubleCollection : Freezable,
	IFormattable, IList, ICollection, 
	IList<double>, ICollection<double>, IEnumerable<double>, 
	IEnumerable

DoubleCollection クラスは、IList や ICollection インターフェイスを実装する double 型のコレクションです。Add() メソッドや Remove() メソッドを用いて double 型の値をコレクションに追加したり、削除することができます。

点線を定義する double 型の配列には、ダッシュとギャップの長さを繰り返しで指定します。最初のインデックス 0 番の要素はダッシュの長さを表す値であり、次のインデックス 1 番の要素はギャップの長さを表します。次に、再びダッシュの長さ、というように繰り返します。

長さを表す値はピクセル単位ではなく、線の太さから相対的な値になります。double 型の値 1 が線の太さ(この場合は StrokeThickness)に相当します。

コード5
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main() {
		DoubleCollection doubles = new DoubleCollection();
		doubles.Add(1);
		doubles.Add(0.5);
		doubles.Add(2);
		doubles.Add(0.5);

		Line line = new Line();
		line.Stroke = Brushes.Black;
		line.StrokeThickness = 20;
		line.StrokeDashArray = doubles;
		line.X1 = 20;  line.Y1 = 30;
		line.X2 = 400; line.Y2 = 30;

		Canvas canvas = new Canvas();
		canvas.Children.Add(line);

		Window wnd = new Window();
		wnd.Content = canvas;

		Application app = new Application();
		app.Run(wnd);
	}
}
実行結果
コード5 実行結果

コード5は、短い線、空白、長い線、空白を繰り返す点線を定義し、それを表示しています。DoubleCollection オブジェクトには、先頭インデックスから順番に 1、0.5、2、0.5 という値を与えています。実行結果の短いダッシュは最初の 1 という値、その次の長いダッシュは 2 という値が対応しています。空白部分のギャップは全て 0.5 という長さが与えられています。

実行結果の線の幅は 20 に設定されているため、短い線を表す 1 は 20 ピクセルで描画され、2 は、その倍の 40 ピクセルで描画されています。また、ギャップの長さ 0.5 は 20 の半分である 10 ピクセルの長さを持ちます。このように、点線を定義する DoubleCollection に設定されている値は、線の幅に依存します。

4.2.5 座標を保存する

これまで、線を描画するには Line オブジェクトに対して X 座標と Y 座標からなる 2 次元空間の点を指定しました。Line オブジェクトは、始点と終点を表す座標と、それぞれ X1、Y1 と X2、Y2 という個別のプロパティで提供しています。

しかし、アプリケーション側でこうした座標を保持する時、個別の int 型や double 型の変数を用意するのは非効率です。2 次元空間の点を表す X 座標と Y 座標の値は関連付けられるべきです。そこで、通常は座標を表す System.Windows.Point 構造体を利用して X 座標と Y 座標を保存します。

System.Windows.Point 構造体
[SerializableAttribute] 
[TypeConverterAttribute(typeof(PointConverter))] 
public struct Point : IFormattable

Point 構造体は、X 座標と Y 座標の組み合わせを提供する値です。2次元空間上の1点は、この構造体の値で表現することができます。この構造体のコンストラクタは、X 座標と Y 座標を初期化するための値を要求しています。 

Point 構造体のコンストラクタ
public Point (double x, double y)

x には X 座標を、y には Y 座標を指定します。コンストラクタに指定した X 座標と Y 座標は、X プロパティY プロパティで設定または取得できます。

Point 構造体 X プロパティ
public double X { get; set; }
Point 構造体 Y プロパティ
public double Y { get; set; }

今後、様々な場面で Point 構造体を利用することになります。

4.2.6 線のスタイルを保存する

線に適用するブラシや線の太さ、キャップなど、線のスタイルを決定する様々な情報も個別のプロパティで設定することができました。Shape クラスは、図形の線を描画するためにこれらの情報を保有しますが、例えば複数の Shape オブジェクトが同じスタイルの線を共有する構造になっている場合、アプリケーション側の変数で線のスタイルを保存しなければなりません。

このような場合、Point 構造体を使った理由と同じで、ブラシや線の太さを表す値を個別に管理するのは合理的ではありません。線のスタイルに関する情報をまとめて提供してくれる System.Windows.Media.Pen クラスを使うべきです。このクラスが提供する情報は、古くは論理ペンと呼ばれていた GDI オブジェクトに相当するものです。

System.Windows.Media.Pen クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
         System.Windows.Media.Animation.Animatable 
          System.Windows.Media.Pen
[LocalizabilityAttribute(LocalizationCategory.None, Readability=Readability.Unreadable)] 
public sealed class Pen : Animatable

このクラスのコンストラクタには、パラメータから線が保有するブラシと、線の太さを指定することができます。

Pen クラスのコンストラクタ
public Pen ()
Bpublic Pen (Brush brush, double thickness)

コンストラクタで指定するブラシと太さは、プロパティから設定・取得することもできます。ブラシを設定・取得するには Brush プロパティを使います。

Pen クラス Brush プロパティ
public Brush Brush { get; set; }

このプロパティの既定の値は null になっています。

線の太さは Thickness プロパティから設定・取得できます。

Pen クラス Thickness プロパティ
public double Thickness { get; set; }

このプロパティの既定の値は 1 になっています。

この場では割愛しますが、Pen クラスはこのほかにも線のスタイルを決定する様々なプロパティを提供しています。例えば、キャップに関する情報を提供する StartLineCap や EndLineCap プロパティ、点線のスタイルを決定する DashStyle プロパティなどがあります。