WisdomSoft - for your serial experiences.

4.8 パス

パスを用いることで図形を純粋なデータとして保持し、複数の図形の組み合わせを 1 つの Shape オブジェクトとして描画できます。

4.8.1 より複雑な図形のために

パスは Shape クラスを継承するクラスの中でも、最も複雑な図形を扱うことができます。単純な長方形や楕円などでは表現できない、不葛生の図形を組み合わせた複雑な図形を描画したい場合にパスを用います。

パスを描画するには System.Windows.Shapes.Path クラスを利用します。Rectangle クラスや Ellipse クラスと同様に、Path クラスも Shape クラスを継承しています。

System.Windows.Shapes.Path クラス
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.Path
public sealed class Path : Shape

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

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

Path クラスは、Line や Rectangle、Ellipse というようなクラスとは異なり、Path 自体が何らかの形を直接表現するわけではありません。Path クラスは、抽象的に表現された形を内部で保存し、その形を最終的な Path オブジェクトの形として描画します。Rectangle や Ellipse オブジェクトを使わなくても Path クラスを利用することで同じように長方形や楕円を描画することも可能です。

Path オブジェクトが表現する形は Data プロパティに設定されているオブジェクトで決定されます。

Path クラス Data プロパティ
public Geometry Data { get; set; }

Data プロパティは、任意の形を表す System.Windows.Media.Geometry クラスのオブジェクトを設定または取得します。

System.Windows.Media.Geometry クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
         System.Windows.Media.Animation.Animatable 
          System.Windows.Media.Geometry
[LocalizabilityAttribute(LocalizationCategory.None, Readability=Readability.Unreadable)] 
[TypeConverterAttribute(typeof(GeometryConverter))] 
public abstract class Geometry : Animatable, IFormattable

Geometry クラスは、何らかの幾何学模様を表す抽象的なクラスです。描画要素を表す UIElement クラスの関係における Shape クラスのような位置づけになりますが、Geometry クラスは UIElement を継承していないので、描画要素ではありません。あくまで、Geometry クラスは形をデータとして表現するオブジェクトです。

Geometry クラスと Shape クラスの設計は非常に良く似ています。形を表す描画要素である Shape の派生クラスで具体的な形を表す Rectangle や Ellipse クラスを実装しているのと同じように、Geometry もまた、派生クラスで具体的な形を表すクラスを提供しています。

例えば、長方形をパスから描画したい場合は System.Windows.Media.RectangleGeometry クラスを利用します。このクラスは Shape クラスの階層における Rectangle クラスに相当し、パス上で長方形を表します。 

System.Windows.Media.RectangleGeometry クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
         System.Windows.Media.Animation.Animatable 
           System.Windows.Media.Geometry 
            System.Windows.Media.RectangleGeometry
public sealed class RectangleGeometry : Geometry

RectangleGeometry のコンストラクタは、いくつかにオーバーロードされています。この場では、長方形の位置とサイズを保存する Rect オブジェクトを渡して、RectangleGeometry を初期化するコンストラクタを利用します。

RectangleGeometry クラスのコンストラクタ
public RectangleGeometry ()
public RectangleGeometry (Rect rect)

コンストラクタにパラメータを渡さずに、インスタンス生成後に Rect プロパティから、長方形の座標とサイズを設定・取得することも可能です。

RectangleGeometry クラス Rect プロパティ
public Rect Rect { get; set; }

Path に追加される Geometry は、Shape オブジェクトとは異なり座標を保持しています。Shape は、描画要素である UIElement のサブクラスなので、Shape を配置するのはパネルの役割でしたが、Geometry は、Path オブジェクト内に内包される形なので座標を保持します。この座標は、Path オブジェクトが配置されている位置からの相対座標となります。

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

class Test {
	[STAThread]
	public static void Main() {
		Rect rect = new Rect(10, 10, 400, 200);

		Path path = new Path();
		path.Data = new RectangleGeometry(rect);
		path.Fill = Brushes.Black;

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

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

コード1は、Path オブジェクトを用いて長方形を描画しています。この作業は Rectangle オブジェクトを用いても同じことができるため、なぜ Path オブジェクトでこのようなことをする必要があるのか、不思議に思うかもしれません。後ほど解説しますが、Path は抽象的な Geometry クラスで表現された幾何学模様を内部で保有し、それを描画してるのです。よって、複数の図形を組み合わせることで、より複雑な図形を生成することができます。

複雑な図形を生成する前に、まずは RectangleGeometry クラスのような基本的な図形をパスを出表示させる方法から学習しましょう。楕円形をパスを用いて描画するには System.Windows.Media.EllipseGeometry クラスを利用します。

System.Windows.Media.EllipseGeometry クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
         System.Windows.Media.Animation.Animatable 
           System.Windows.Media.Geometry 
            System.Windows.Media.EllipseGeometry
public sealed class EllipseGeometry : Geometry

EllipseGeometry クラスの利用方法も、ほとんど RectangleGeometry クラスと同じです。このクラスのコンストラクタには、楕円の座標と幅と高さを表す Rect オブジェクトを渡すことができます。

EllipseGeometry クラスのコンストラクタ
public EllipseGeometry ()
public EllipseGeometry (Rect rect)
public EllipseGeometry (Point center, double radiusX, double radiusY)

EllipseGeometry クラスのコンストラクタに Rect オブジェクトを渡すことによって、指定した長方形に収まるように楕円を配置することができますが、EllipseGeometry クラスの内部表現では、楕円形の中心の座標と半径によって楕円の位置とサイズが決定されています。3番目のコンストラクタでは、center パラメータに楕円形の中央を表す座標を指定子、radiusX に、center から水平方向の半径、radiusY に center から垂直方向の半径を指定します。

コンストラクタで指定したこれらの値は、プロパティからも操作することができます。楕円形の中心は Center プロパティから、半径は RadiusX プロパティRadiusY プロパティから設定・取得できます。

EllipseGeometry クラス Center プロパティ
public Point Center { get; set; }
EllipseGeometry クラス RadiusX プロパティ
public double RadiusX { get; set; }
EllipseGeometry クラス RadiusY プロパティ
public double RadiusY { get; set; }

長方形の場合とは異なり、Center プロパティは左上隅ではなく円の中央の座標を表すことに注意してください。また、RadiusX プロパティと RadiusY プロパティは幅や高さではなく、水平方向、及び垂直方向の半径です。

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

class Test {
	[STAThread]
	public static void Main() {
		EllipseGeometry ellipse = new EllipseGeometry();
		ellipse.Center = new Point(210, 110);
		ellipse.RadiusX = 200;
		ellipse.RadiusY = 100;

		Path path = new Path();
		path.Data = ellipse;		
		path.Fill = Brushes.Black;

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

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

コード2は、EllipseGeometry クラスのコンストラクタにパラメータを渡さず、生成したインスタンスのプロパティから座標とサイズを設定しています。コード1のように、Rect オブジェクトをコンストラクタに渡して楕円形の位置とサイズを指定した方がプログラムとしては簡単ですが、このような方法でもオブジェクトを設定することができます。コンストラクタ呼び出し時点で、図形の座標やサイズを決定できない場合はこの方法を用いなければなりません。

パスに線を設定するには System.Windows.Media.LineGeometry クラスを利用します。

System.Windows.Media.LineGeometry クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
         System.Windows.Media.Animation.Animatable 
           System.Windows.Media.Geometry 
            System.Windows.Media.LineGeometry
public sealed class LineGeometry : Geometry

LineGeometry クラスのコンストラクタには、線の始点と終点を表す座標を指定することができます。

LineGeometry クラスのコンストラクタ
public LineGeometry ()
public LineGeometry (Point startPoint, Point endPoint)

startPoint パラメータに、線の始点となる座標を、endPoint パラメータに終点となる座標を指定します。これらの値は StartPoint プロパティEndPoint プロパティから設定・取得することも可能です。

LineGeometry クラス StartPoint プロパティ
public Point StartPoint { get; set; }
LineGeometry クラス EndPoint プロパティ
public Point EndPoint { get; set; }

当然、線は内部を保有していないので、LineGeometry を保有する Path オブジェクトには Fill ではなく Stroke に適切なブラシを設定しなければなりません。

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

class Test {
	[STAThread]
	public static void Main() {
		Point pt1 = new Point(10, 10);
		Point pt2 = new Point(400, 200);

		Path path = new Path();
		path.Data = new LineGeometry(pt1, pt2);
		path.Stroke = Brushes.Black;
		path.StrokeThickness = 10;

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

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

コード3は、1 本の単純な線を保有するパスを描画しています。前述したように、パスは本来、複数の Geometry オブジェクトを組み合わせて利用するものなので多角形を表す Polygon や Polyline に相当する Geometry オブジェクトは存在していません。それらは LineGeometry を組み合わせることで表現可能です。

4.8.2 複数の模様を組み合わせる

これまでのパスは単純な図形ばかりでしたが、Geometry オブジェクトは System.Windows.Media.GeometryGroup クラスを利用することで組み合わせて利用することができます。GeometryGroup を使うことで、1 つの Path オブジェクトに複数の Geometry を描画させることができます。

System.Windows.Media.GeometryGroup クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
         System.Windows.Media.Animation.Animatable 
           System.Windows.Media.Geometry 
            System.Windows.Media.GeometryGroup
[ContentPropertyAttribute("Children")]
public sealed class GeometryGroup : Geometry

GeometryGroup の継承関係を見ると、GeometryGroup もまた Geometry クラスを継承していることが分かります。このクラスは内部で複数の子 Geometry を保有することができる Geometry のコンテナとして作用します。UIElement の階層における、パネルとコントロールのような関係だと考えてください。

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

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

生成した GeometryGroup クラスのオブジェクトに子 Geometry オブジェクトを追加するには Children プロパティを使います。

GeometryGroup クラス Children プロパティ
public GeometryCollection Children { get; set; }

Children プロパティは、Geometry オブジェクトの動的は配列を管理する GeometryCollection クラスのオブジェクトを返してくれます。GeometryCollection は、任意の数の Geometry オブジェクトを保有するコレクションオブジェクトなので、このオブジェクトの Add() メソッドから Geometry を追加することができます。

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

class Test {
	[STAThread]
	public static void Main() {
		Rect rect1 = new Rect(10, 10, 300, 200);
		Rect rect2 = new Rect(210, 10, 300, 200);
		
		EllipseGeometry ellipse1 = new EllipseGeometry(rect1);
		EllipseGeometry ellipse2 = new EllipseGeometry(rect2);
		
		GeometryGroup gg = new GeometryGroup();
		gg.Children.Add(ellipse1);
		gg.Children.Add(ellipse2);

		Path path = new Path();
		path.Data = gg;
		path.Fill = Brushes.Black;

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

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

コード4は、2 つの EllipseGeometry オブジェクトを GeometryGroup に追加し、この GeometryGroup オブジェクトを最終的に Path オブジェクトに設定しています。実行結果を見ると、2 つの楕円が描画されていることが確認できます。

楕円の重なり合っている領域が塗りつぶされていませんが、これは GeometryGroup オブジェクトに設定されている既定の塗りつぶしルールによる影響です。塗りつぶしルールは、多角形と同様に、図形が重なり合う領域を内部とするかどうかを判断するためのルールです。GeometryGroup の塗りつぶしルールは FillRule プロパティから変更することができます。

GeometryGroup クラス FillRule プロパティ
public FillRule FillRule { get; set; }

このプロパティは Polygon クラスの FillRule プロパティと同様に既定で EvenOdd が設定されています。この値を Nonzero に切り替えれば、重なり合う内部を塗りつぶすことができます。

図1 FillRule が Nonzero の場合
FillRule が Nonzero の場合

図1は、コード4の GeometryGroup オブジェクトに対して Nonzero の FillRule を採用した場合の実行結果となる図です。楕円が重なり合っている部分も図形の内部として認識され、塗りつぶされています。  

4.8.3 模様を結合する

GeometryGroup クラスは、複数の Geometry を追加する機能を提供しましたが、これとは別に、Geometry を結合して新しい模様を構築する機能を提供する System.Windows.Media.CombinedGeometry クラスが存在します。 

System.Windows.Media.CombinedGeometry クラス
System.Object 
   System.Windows.Threading.DispatcherObject 
     System.Windows.DependencyObject 
       System.Windows.Freezable 
         System.Windows.Media.Animation.Animatable 
           System.Windows.Media.Geometry 
            System.Windows.Media.CombinedGeometry
public sealed class CombinedGeometry : Geometry

CombinedGeometry は、Geometry を追加するのではなく、2 つの Geometry オブジェクトを結合して新しい 1 つの図を提供します。例えば、内部に穴が空いている図形などは、2 つの Geometry を組み合わせて作成することができます。

CombinedGeometry クラスのコンストラクタは、オーバーロードされています。この場では、組み合わせる 2 つの Geometry オブジェクトを渡すコンストラクタを使ってみましょう。

CombinedGeometry クラスのコンストラクタ
public CombinedGeometry ()
public CombinedGeometry (Geometry geometry1, Geometry geometry2)

geometry1 パラメータと geometry2 パラメータには、それぞれ結合させる Geometry オブジェクトを渡します。結合する 2 つの Geometry オブジェクトは、Geometry1 プロパティGeometry2 プロパティから設定することも可能です。

CombinedGeometry クラス Geometry1 プロパティ
public Geometry Geometry1 { get; set; }
CombinedGeometry クラス Geometry2 プロパティ
public Geometry Geometry2 { get; set; }

2 つの Geometry オブジェクトをどのように結合するかという方法は GeometryCombineMode プロパティから設定することができます。

CombinedGeometry クラス GeometryCombineMode プロパティ
public GeometryCombineMode GeometryCombineMode { get; set; }

このプロパティには、System.Windows.Media.GeometryCombineMode 列挙体のいずれかのメンバを設定します。このメンバが表す方法で Geometry の結合結果が決定します。

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

この列挙体のメンバには、表1のようなものがあります。単純に 2 つの Geometry を結合させる場合は Union を、図形から特定の領域を削除して新しい図形を作成したい場合には Exclude を使うなど、作成したい目的の図形に応じて適切なメンバを選択してください。

表1 GeometryCombineMode 列挙体のメンバ
メンバ 意味
Exclude 2 つの Geometry のうち、2 つ目の Geometry に含まれていない領域。
Geometry2 は Geometry1 の領域を削除する効果を持つ。
Intersect 2 つの Geometry が重なり合っている部分のみを領域とする。
Union 2 つの Geometry を単純に加算した結果を領域とする。
Xor 2 つの Geometry が重なり合っていない部分を領域とする。重なり合っている部分は領域としない

以下の図2は CombinedGeometry で結合させる 2 つの EllipseGeometry オブジェクトであると仮定します。

図2 結合する2つの Geometry オブジェクト
図2 結合する2つの Geometry オブジェクト

左の EllipseGeometry を Geometry1 に、右の EllipseGeometry を Geometry2 に設定して、各 GeometryCombineMode 列挙体のメンバで結合した場合、図3のような結果が得られます。

図3 結合結果
図3 結合結果

複雑な図形も、こうした単純な図形の組み合わせるよって作ることができます。これは、Path 以外の単一の Shape 系オブジェクトでは作れない形です。

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

class Test {
	[STAThread]
	public static void Main() {
		Rect rect1 = new Rect(10, 10, 400, 200);
		Rect rect2 = new Rect(110, 60, 200, 100);
		
		EllipseGeometry ellipse1 = new EllipseGeometry(rect1);
		EllipseGeometry ellipse2 = new EllipseGeometry(rect2);
		
		CombinedGeometry gg = new CombinedGeometry(ellipse1, ellipse2);
		gg.GeometryCombineMode = GeometryCombineMode.Exclude;

		Path path = new Path();
		path.Data = gg;
		path.Fill = Brushes.Black;

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

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

コード5では、大きな楕円と小さな楕円の 2 つの Geometry オブジェクトを用意し、これを CombinedGeometry オブジェクトで結合しています。結合方法には Exclude を指定しているため、Geometry1 から Geometry2 に指定した領域を削除した領域が結合結果となります。Geometry1 に大きな楕円を指定し、Geometry2 に小さな楕円を指定しているため、大きな楕円の中央を、小さな楕円の形で切り取った形が結果となっています。