WPFでプログラムしてみよう(6)

    1. WPFでプログラムしてみよう(1)-依存プロパティ等のWPF概要説明-
    2. WPFでプログラムしてみよう(2)-環境作成方法とパネルの使い方-
    3. WPFでプログラムしてみよう(3)描画オブジェクト(その1)-
    4. WPFでプログラムしてみよう(4)描画オブジェクト(その2)-
    5. WPFでプログラムしてみよう(5)描画オブジェクト(その3)-


今回で4章の描画オブジェクトは最後にします。長かった...。


Windows Presentation Foundation プログラミング入門

Windows Presentation Foundation プログラミング入門

4.8 パス

単純な形状では表現出来ない(複数の図形の組み合わせなど)ケースにはPathクラスを利用します。
PathクラスのDataプロパティを使用します。

GeometryGroupクラスを使った図形の組み合わせ
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main(){
		int iFlag = 0;
		Rect rct1 = new Rect( 10, 10, 300, 200);
		Rect rct2 = new Rect(210, 10, 300, 200);

		EllipseGeometry elp1 = new EllipseGeometry(rct1);
		EllipseGeometry elp2 = new EllipseGeometry(rct2);

		Button btnChange   = new Button();
		btnChange.Content  = "色を変更";
		btnChange.FontSize = 12;

		GeometryGroup gg = new GeometryGroup();
		gg.Children.Add(elp1);
		gg.Children.Add(elp2);

		Path pt = new Path();
		pt.Data = gg;
		pt.Fill = Brushes.Red;

		btnChange.Click += (sender, e) => {
			if (++iFlag % 3 == 0){
				if (gg.FillRule == FillRule.Nonzero)
					gg.FillRule = FillRule.EvenOdd;
				else if (gg.FillRule == FillRule.EvenOdd)
					gg.FillRule = FillRule.Nonzero;
			}

			if (pt.Fill == Brushes.Red)
				pt.Fill = Brushes.Blue;
			else if (pt.Fill == Brushes.Blue)
				pt.Fill = Brushes.Pink;
			else if (pt.Fill == Brushes.Pink)
				pt.Fill = Brushes.Red;
		};

		Canvas cnvs = new Canvas();
		cnvs.Children.Add(pt);
		cnvs.Children.Add(btnChange);

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

		Application app = new Application();
		app.Run(wnd);
	}
}


これまたコンパイルして実行してみます。



画面に楕円が重なり合った赤い図形が表示されます。楕円の重なり合っている部分は塗りつぶされていませんが、これはGeometryGroupクラスのFillRuleプロパティにEvenOddを指定しているためです。
左上のボタンを押すたびに楕円の色が変わります。


さらにボタンを押すと、塗りつぶしの色が赤に戻り、さらにFillRuleプロパティの値がNonzeroに変わるために塗りつぶしパターンが変わります。具体的にはEvenOddの時には塗りつぶされなかった重ね合わせの部分が塗りつぶされているという点です。




こんな感じで図形の重ねあわせが可能です。

CombinedGeometryクラスを使った図形の結合

GemetryGroupクラスが複数の図形を追加して組み合わせたの対して、CombinedGeometryクラスは2つのGeometryを結合して図形を作成します。


CombinedGeometryクラスにはGeometry1,Geometry2プロパティが存在し、それぞれが結合する対象の図形となります。
またCombinedGeometryクラスには、GeometryCombineModeという列挙型のプロパティが存在し、2つの図形の結合演算を指定します。

メンバ 説明
Exclude Geometry1に含まれていてGeometry2に含まれていない部分を塗りつぶす
Intersect Geometry1,Geometry2の重なっている部分のみを塗りつぶす
Union Geometry1,Geometry2両領域を塗りつぶす
Xor Geometry1,Geometry2両領域を塗りつぶす(ただし重なっている部分は塗りつぶさない)


このプロパティの違いを簡単なプログラムで説明します。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

class Test {
	[STAThread]
	public static void Main(){
		Rect rct1 = new Rect( 10, 10, 300, 200);
		Rect rct2 = new Rect(210, 10, 300, 200);

		EllipseGeometry elp1 = new EllipseGeometry(rct1);
		EllipseGeometry elp2 = new EllipseGeometry(rct2);

		Button btnChange   = new Button();
		btnChange.Content  = "Intersectに変更";
		btnChange.FontSize = 12;

		CombinedGeometry gg = new CombinedGeometry(elp1,elp2);
		gg.GeometryCombineMode = GeometryCombineMode.Exclude;

		Path pt = new Path();
		pt.Data = gg;
		pt.Fill = Brushes.Red;

		btnChange.Click += (sender, e) => {
			if (gg.GeometryCombineMode == GeometryCombineMode.Exclude){
				gg.GeometryCombineMode = GeometryCombineMode.Intersect;
				btnChange.Content  = "Unionに変更";
			} else if (gg.GeometryCombineMode == GeometryCombineMode.Intersect){
				gg.GeometryCombineMode = GeometryCombineMode.Union;
				btnChange.Content  = "Xorに変更";
			} else if (gg.GeometryCombineMode == GeometryCombineMode.Union){
				gg.GeometryCombineMode = GeometryCombineMode.Xor;
				btnChange.Content  = "Excludeに変更";
			} else if (gg.GeometryCombineMode == GeometryCombineMode.Xor){
				gg.GeometryCombineMode = GeometryCombineMode.Exclude;
				btnChange.Content  = "Intersectに変更";
			}
		};

		Canvas cnvs = new Canvas();
		cnvs.Children.Add(pt);
		cnvs.Children.Add(btnChange);

		Window wnd  = new Window();
		wnd.Background = Brushes.Pink;
		wnd.Content = cnvs;

		Application app = new Application();
		app.Run(wnd);
	}
}


で、これも早速コンパイルして実行。



最初はExcludeが設定されているためにGeometry2の分が抜けた塗りつぶしとなっています。
一度ボタンを押します。



Intersectが設定されるので、重ね合わさった部分のみ塗りつぶされます。
さらにもう一度ボタンを押します。



Unionに設定されるので全部塗りつぶされます。
最後にもう一度ボタンを押します。



Xorに設定されるので重ねあった部分以外が塗りつぶされます。
CombinedGeomeotryを使うとこのような描画が可能となります。


4.9 曲線

曲線/直線を描画するためにはPathGeometryクラスを使用します。
これまた簡単なサンプルを作ってみます。

using System;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;

class Test {
	[STAThread]
	public static void Main(){
		// 直線 //
		LineSegment ln1 = new LineSegment();
		LineSegment ln2 = new LineSegment();
		ln1.Point = new Point(410, 210);
		ln2.Point = new Point( 10, 210);

		// ベジェ曲線 //
		BezierSegment bz = new BezierSegment();
		bz.Point1 = new Point(360 , 10);
		bz.Point2 = new Point(480 , 310);
		bz.Point3 = new Point(640 , 150);

		// PathFigureにセグメントを追加 //
		PathFigure pf = new PathFigure();
		pf.StartPoint = new Point(210, 10);
		pf.Segments.Add(ln1);
		pf.Segments.Add(ln2);
		pf.Segments.Add(bz);

		PathGeometry pg = new PathGeometry();
		pg.Figures.Add(pf);

		Path pt = new Path();
		pt.Data = pg;
		pt.Stroke = Brushes.Black;

		Window wnd     = new Window();
		wnd.Content    = pt;
		wnd.Background = Brushes.Pink;

		Application app = new Application();
		app.Run(wnd);
	}
}


さっそくコンパイルして実行します。



直線で指定した部分とベジェ曲線が描画されています。
LineSegmentクラスとBezerSegmentクラスは各々与えられるプロパティは異なります。
例えば、LineSegmentクラスは直線は始点/終点を指定する必要があるのに対して、BezerSegmentは制御点4つのうち3つを指定する必要があります*1
ただし、両者共にPathGeometryに追加することが出来るなど描画に関する扱いはまったく同等です。


4.10 座標変換

4.11 直接描画

上記は結構ボリュームがある上にすぐに使う部分でもなさそうだし、正直あまりおもしろくなさそうなので、今後必要になったタイミングで勉強します。


かなり強引ですがこれで4章はひとまず終了です。
次回は5章のイベントについてまとめます。

*1:最初の制御点は前のセグメントの終点が自動で割り当てられます