ブログトップ 記事一覧 ログイン 無料ブログ開設

子持ちししゃもといっしょ RSSフィード

2008-10-06

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

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

今回から5章のイベントです。


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

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


5.1 マウス入力

GUIを持つWindowsアプリケーションは、UIに関するすべての情報交換をWindowsメッセージを介して行っています。

そのため、以前はWndProcというメッセージを受けたとった時に実行される関数を使用し、どのメッセージを受け取ったのかを確認してから対応する処理を呼び出して実行するという作業が必要でした。

これに対して現在主流となっているのは、コントロールごとにイベントを定義し、ユーザからのアクションに応じて対応するイベントが呼び出され、そのイベントが委譲された処理を実行するというものです。イベントドリブンなどと言います。


WPFは今までの.NETアプリケーションと同様に、後者のイベントドリブンで動作します。

まずはマウスイベントについてまとめます。


最初にマウスで使用できるイベント一覧です(P.228から抜粋)


イベント名発生条件MSDN
MouseDownマウスボタンが押された場合
MouseUpマウスボタンが離された場合
MouseLeftButtonDownマウスの左ボタンが押された場合
MouseLeftButtonUpマウスの左ボタンが離された場合
MouseRightButtonDownマウスの右ボタンが押された場合
MouseRightButtonUpマウスの右ボタンが離された場合
MouseWheelマウスのホイールが回転した場合
MouseEnterマウスカーソルが領域に侵入した場合
MouseMoveマウスカーソルが領域内で移動した場合
MouseLeaveマウスカーソルが領域から外に出た場合

これらをイベントを使って簡単なプログラムを作成してみます。


using System;
using System.Windows;
using System.Windows.Controls;

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

class Test : Window {
	[STAThread]
	public static void Main(){
		Window wnd = new Test();
		Application app = new Application();
		app.Run(wnd);
	}

	// 楕円クラス
	private Ellipse elp;

	public Test() {
		elp = new Ellipse();
		elp.Fill  = Brushes.Blue;
		elp.Width  = 200;
		elp.Height = 200;
		elp.MouseDown    += ellipseMouseDown ;
		elp.MouseUp      += ellipseMouseUp   ;

		Content = elp;
	}

	private void ellipseMouseDown(object sender, MouseButtonEventArgs e){
		if (e.LeftButton == MouseButtonState.Pressed)
			elp.Fill = Brushes.Pink;

		else if (e.RightButton == MouseButtonState.Pressed)
			elp.Fill = Brushes.Orange;

		else if (e.MiddleButton == MouseButtonState.Pressed)
			elp.Fill = Brushes.Red;

		Title    = e.ChangedButton.ToString();
	}

	private void ellipseMouseUp(object sender, MouseButtonEventArgs e){
		elp.Fill = Brushes.Blue;
		Title    = "";
	}

}

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

中心に青い円が表示されます。


f:id:itotto:20081005155419p:image


この円をクリック(左ボタン)します。

# マウスのボタンを押したままにしてください


f:id:itotto:20081005155420p:image


円がピンクになりました。

また、WindowのタイトルがLeftになっています。


押しっぱなしにしていたボタンを離します。



f:id:itotto:20081005155419p:image


円の色は青に戻り、タイトルの文字もなくなりました。

次に円を右クリック(これも押しっぱなし)してみます。


f:id:itotto:20081005172336p:image


円はオレンジ色になり、タイトルはRightと表示されました。

押しっぱなしにしていたボタンを離すと画面は青に戻ってタイトルが消されます。


f:id:itotto:20081005155419p:image


最後に円をホイールや真ん中のボタンでクリック(くどいようですがこれも押しっぱなしです)して見ます。


f:id:itotto:20081005172337p:image


円は赤になり、タイトルはMiddleとなります。

で、ボタンを離すと元に戻ります。


MouseDownイベントでどのボタンが押されたのかを判断して色を塗り替え、タイトルにはイベントハンドラに引数として渡されているMouseButtonEventArgsクラスのインスタンスに格納されているChangedButtonを表示しています。

また、MouseUpイベントハンドラには円の塗りつぶしを青にしてタイトルを空文字にするという処理を入れることで画面を初期化しています。


こんな感じでマウスの動作に対するイベントを使うことが出来ます。

その他のイベントの詳細(ハンドラの引数や)についてはMSDNとかMSDNとかMSDNをみてください。


5.2 キーボード入力

パソコンについている入力用インターフェースといえば、マウスとキーボード。

という訳で次はキーボードからの入力についてまとめます。


イベント名発生条件MSDN
KeyDownキーが押された場合
KeyUpキーが離された場合

ではさっそくキーボードイベントを使ったプログラムを作ってみます。


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

using System.Collections;

class Test : Window {
	[STAThread]
	public static void Main(){
		Window wnd = new Test();
		Application app = new Application();
		app.Run(wnd);
	}
	
	private Ellipse   elp;
	private Hashtable ColorKeyTable;

	public Test() {
		elp = new Ellipse();
		elp.Fill   = Brushes.Gray;
		elp.Width  = 100;
		elp.Height = 100;

		InitHash();

		Content = elp;
		KeyDown += thisKeyDown;
		KeyUp   += thisKeyUp  ;
	}

	// KeyDownイベントハンドラ
	private void thisKeyDown(object sender, KeyEventArgs e) {
		elp.Fill = (Brush)ColorKeyTable[e.Key.ToString()];
		Title    += e.Key.ToString();
	}

	// KeyUpイベントハンドラ
	private void thisKeyUp(object sender, KeyEventArgs e) {
		elp.Fill = Brushes.Gray;
	}

	// Hashtable初期化
	private void InitHash() {
		ColorKeyTable = new Hashtable();

		ColorKeyTable.Add("A",Brushes.Aqua);
		ColorKeyTable.Add("B",Brushes.Blue);
		ColorKeyTable.Add("C",Brushes.Chocolate);
		ColorKeyTable.Add("D",Brushes.DeepPink);
		ColorKeyTable.Add("E",Brushes.Azure);
		ColorKeyTable.Add("F",Brushes.Firebrick);
		ColorKeyTable.Add("G",Brushes.Green);
		ColorKeyTable.Add("H",Brushes.Honeydew);
		ColorKeyTable.Add("I",Brushes.Indigo);
		ColorKeyTable.Add("J",Brushes.Ivory);
		ColorKeyTable.Add("K",Brushes.Khaki);
		ColorKeyTable.Add("L",Brushes.LightGreen);
		ColorKeyTable.Add("M",Brushes.Maroon);
		ColorKeyTable.Add("N",Brushes.Navy);
		ColorKeyTable.Add("O",Brushes.Olive);
		ColorKeyTable.Add("P",Brushes.PaleGreen);
		ColorKeyTable.Add("Q",Brushes.Plum);
		ColorKeyTable.Add("R",Brushes.Red);
		ColorKeyTable.Add("S",Brushes.Salmon);
		ColorKeyTable.Add("T",Brushes.Tomato);
		ColorKeyTable.Add("U",Brushes.Teal);
		ColorKeyTable.Add("V",Brushes.Violet);
		ColorKeyTable.Add("W",Brushes.Wheat);
		ColorKeyTable.Add("X",Brushes.Orange);
		ColorKeyTable.Add("Y",Brushes.Yellow);
		ColorKeyTable.Add("Z",Brushes.YellowGreen);
	}
}

コンパイルして実行します。


実行すると画面が表示され、中央に円が表示されます。


f:id:itotto:20081006003542p:image


このフォームをアクティブにした状態でキーボードでA〜Zのいずれかを入力してみてください。

円が押されたボタンに対応した色に塗りつぶしなおされます。

また、押したボタンのキーがタイトルバーに表示されます。

# 今回は画面イメージを取るのが面倒なので画面展開のサンプルは省略します


5章は一度で終わらせようと思っていましたが、まだこれで半分くらいなので詳しくは次にします。

次回はイベントルーティングというイベントが発生して親/子オブジェクトに派生する仕組みと、実行箇所が異なるだけで内容は同じ処理をうまく扱う方法(CommandBinding)についてまとめます。

2008-09-29

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);
	}
}

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


f:id:itotto:20080928173200p:image


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

左上のボタンを押すたびに楕円の色が変わります。

f:id:itotto:20080928173201p:image

f:id:itotto:20080928173202p:image


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


f:id:itotto:20080928173203p:image


f:id:itotto:20080928173204p:image


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


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

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


CombinedGeometryクラスにはGeometry1,Geometry2プロパティが存在し、それぞれが結合する対象の図形となります。

またCombinedGeometryクラスには、GeometryCombineModeという列挙型のプロパティが存在し、2つの図形の結合演算を指定します。


メンバ説明
ExcludeGeometry1に含まれていてGeometry2に含まれていない部分を塗りつぶす
IntersectGeometry1,Geometry2の重なっている部分のみを塗りつぶす
UnionGeometry1,Geometry2両領域を塗りつぶす
XorGeometry1,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);
	}
}

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


f:id:itotto:20080928220714p:image


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

一度ボタンを押します。


f:id:itotto:20080928220715p:image


Intersectが設定されるので、重ね合わさった部分のみ塗りつぶされます。

さらにもう一度ボタンを押します。


f:id:itotto:20080928220716p:image


Unionに設定されるので全部塗りつぶされます。

最後にもう一度ボタンを押します。


f:id:itotto:20080928220717p:image


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);
	}
}

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


f:id:itotto:20080928220718p:image


直線で指定した部分とベジェ曲線が描画されています。

LineSegmentクラスとBezerSegmentクラスは各々与えられるプロパティは異なります。

例えば、LineSegmentクラスは直線は始点/終点を指定する必要があるのに対して、BezerSegmentは制御点4つのうち3つを指定する必要があります*1

ただし、両者共にPathGeometryに追加することが出来るなど描画に関する扱いはまったく同等です。



4.10 座標変換

4.11 直接描画

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


かなり強引ですがこれで4章はひとまず終了です。

次回は5章のイベントについてまとめます。

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

2008-09-21

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

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

今回も4章の描画オブジェクトをまとめます。


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

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


4.6 イメージ

イメージの表示/編集については以下のクラスあたりを使うと簡単に出来ます。


    1. System.Windows.Controls.Imageクラス
      1. ImageSourceプロパティ
    2. System.Windows.Media.Imaging.BitmapImageクラス

ここでの説明は省略。詳しくはMSDNのこのあたりとかこのあたりをご覧ください。


4.7 ブラシ

塗りつぶししたいときにはブラシを使います。ブラシとはSystem.Windows.Media.Brushクラスであり、その塗りつぶし方によって使用するクラスは異なりますが、使用するクラスはすべてSystem.Windows.Media.Brushクラスの派生クラスです。


塗りつぶしに関するクラスの一覧をまとめました。

# 名前空間はすべて「System.Windows.Media」です


クラス塗りつぶし方法MSDN
SolidColorBrush  単一固定色の塗りつぶし 
GradientBrush  塗りつぶしをグラデーションにするスーパークラス 
 LinearGradientBrush  特定の方向に徐々に色を変化させる塗りつぶし 
 RadialGradientBrush  楕円状に徐々に色を変化させる塗りつぶし 
TileBrush  特定パターンでの塗りつぶしをするスーパークラス 
 ImageBrush  特定のイメージを用いて内部の領域を塗りつぶしする 


(1) SolidColorBrushクラスの使用方法

楕円を塗りつぶし、それらを重ね合わせたり色を変えたりするサンプルを作成しました。


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

class Test {
	[STAThread]
	public static void Main(){
	        byte el1Color = 0xFF;
        	byte el2Color = 0xFF;

	        Ellipse elp1 = new Ellipse();
	        elp1.Fill    = new SolidColorBrush(Color.FromArgb(0x80, el1Color, 0, 0   ));
	        elp1.Width   = 400;
        	elp1.Height  = 200;
	        Canvas.SetLeft(elp1, 10);
        	Canvas.SetTop(elp1, 10);

	        Ellipse elp2 = new Ellipse();
        	elp2.Fill    = new SolidColorBrush(Color.FromArgb(0x80, 0   , 0, el2Color));
	        elp2.Width   = 400;
        	elp2.Height  = 200;
	        Canvas.SetLeft(elp2, 200);
        	Canvas.SetTop(elp2, 10);

	        Button btnChange  = new Button();
        	btnChange.Content = "楕円の色を変えます";
	        Canvas.SetLeft(btnChange, 10);
        	Canvas.SetTop(btnChange, 230);

	        btnChange.Click += (sender, e) => {
        	    el1Color -= 0x20;
	            el2Color -= 0x30;

        	    if (el1Color < 0) el1Color += 0xFF;
	            if (el2Color < 0) el2Color += 0xFF;

        	    elp1.Fill = new SolidColorBrush(Color.FromArgb(0x80, el1Color, 0, 0   ));
	            elp2.Fill = new SolidColorBrush(Color.FromArgb(0x80, 0   , 0, el2Color));
		};

	       	Canvas canvas = new Canvas();
        	canvas.Children.Add(elp1);
	       	canvas.Children.Add(elp2);
        	canvas.Children.Add(btnChange);

		Window wnd  = new Window();
       		wnd.Content = canvas;
		Application app = new Application();
		app.Run(wnd);
	}
}

コンパイルして実行すると画面が表示されます。


f:id:itotto:20080921152720p:image


楕円が2つ重なり合って表示されていて、その下にボタンが配置されています。

まず最初に注目すべきは楕円が重なり合っている部分です。重なっている部分の色は両者の色の重ね合わせとなっています。


続いてボタンを何度か繰り返し押してみます。


f:id:itotto:20080921152721p:image


楕円の色が少しずつ暗くなり、最も黒に近づいた後に再度最初の色に戻ります。

また楕円の色の変化に伴って、重ね合わせの部分の色も変化するのがわかります。これが単色の塗りつぶしに使用されるSolidColorBurshクラスの使い方の例です。


(2) GradientBrushクラスの使用方法

次はグラデーションを使用した塗りつぶしの例です。

まずは単純なグラデーションの塗りつぶしをするサンプルを書いてみます。


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

class Test {
	[STAThread]
	public static void Main(){
	        Color  cl1 = Color.FromRgb(0xFF, 0x00 , 0x00);
        	Color  cl2 = Color.FromRgb(0x00, 0xFF , 0x00);
	        Color  cl3 = Color.FromRgb(0x00, 0x00 , 0xFF);

        	double redPos = 0.0;
	        double grnPos = 0.5;
        	double bluPos = 1.0;

	        LinearGradientBrush br1 = new LinearGradientBrush();
        	br1.GradientStops.Add(new GradientStop(cl1, redPos));
	        br1.GradientStops.Add(new GradientStop(cl2, grnPos));
        	br1.GradientStops.Add(new GradientStop(cl3, bluPos));

	        Button btnChange  = new Button();
	        btnChange.Content = "グラデーションの位置変更";
        	btnChange.Width   = 220;
	        btnChange.Click += (sender, e) => {
        		br1.GradientStops.Clear();
	        	redPos = redPos > 1.0 ? 0.0 : redPos + 0.05;
			grnPos = grnPos > 1.0 ? 0.0 : grnPos + 0.05;
			bluPos = bluPos > 1.0 ? 0.0 : bluPos + 0.05;
			br1.GradientStops.Add(new GradientStop(cl1, redPos));
			br1.GradientStops.Add(new GradientStop(cl2, grnPos));
			br1.GradientStops.Add(new GradientStop(cl3, bluPos));
		};

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

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

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

コンパイルして実行するとグラデーションのかかった背景の画面とボタンが表示されます。


f:id:itotto:20080921152722p:image


ボタンを押すたびにStopPointが全体的に右側にずれるのでグラデーションの様相が変わっていきます。


f:id:itotto:20080921152723p:image

f:id:itotto:20080921152724p:image


btnChangeのClickイベントハンドラでGradientStopを以前の値に+0.05するよう変更しているので、この変化する値の幅を変えてみると面白いので試してみてください。

以上でLinearGradientBrushクラスで直線的なグラデーションを描く説明は終わり。

次にRadialGradientBrushクラスを使って、楕円状のグラデーションを描くサンプルを作成します。



sing System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

class Test {
	[STAThread]
	public static void Main(){
	        Color  cl1 = Color.FromRgb(0xFF, 0x11 , 0x00);
        	Color  cl2 = Color.FromRgb(0x00, 0x11 , 0xFF);

	        double centerX = 0.3;
        	double centerY = 0.8;

	        RadialGradientBrush br1 = new RadialGradientBrush();
        	br1.Center = new Point(centerX, centerY);
	        br1.GradientStops.Add(new GradientStop(cl1, 0.0));
        	br1.GradientStops.Add(new GradientStop(cl2, 1.0));

	        Button btnChange  = new Button();
	        btnChange.Content = "グラデーションの位置変更";
        	btnChange.Width   = 220;
        	btnChange.Click += (sender, e) => {
	        	br1.GradientStops.Clear();
        	        centerX = centerX > 1.0 ? 0.0 : centerX + 0.1;
	                centerY = centerY > 1.0 ? 0.0 : centerY + 0.1;
                	br1.Center = new Point(centerX, centerY);
        	        br1.GradientStops.Add(new GradientStop(cl1, 0.0));
	                br1.GradientStops.Add(new GradientStop(cl2, 1.0));
		};

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

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

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

これまたコンパイルして実行すると画面が表示されます。


f:id:itotto:20080921152725p:image


楕円上にグラデーションがかかった模様が表示されます。

続いてボタンを何度か繰り返し押してみます。


f:id:itotto:20080921152726p:image

f:id:itotto:20080921152727p:image

f:id:itotto:20080921152728p:image


塗りつぶしの中心を変えているので楕円が少しずつ移動します。

ここではRadialGradientBrushクラスはCenterプロパティを変更していますが、これとは別に焦点を変更することも可能です。この場合はGradientOriginプロパティを使用します。

以上でRadialGradientBrushクラスでグラデーションを描く説明は終わり。


ブラシを使った塗りつぶしの説明はここまで。


なかなか4章が終わりませんが、長くなったのでここまで。

次はより複雑な図形を描くためのパス(4.8)から4章の最後までまとめます。

2008-09-15

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


これからは該当の記事よりも過去分の記事にはインデックスを付けることにします。


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


今回はテキストの前回からの続きで、4章の描画オブジェクトをまとめます。


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

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


4.4 複数線と多角形

Polygonクラスを使用して閉じた図形を描画してみます。


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

class Test {
	[STAThread]
	public static void Main(){
        Polygon pgn = new Polygon();
        pgn.Stroke = Brushes.Black;

        pgn.Points.Add(new Point( 10, 10));
        pgn.Points.Add(new Point(110, 10));
        pgn.Points.Add(new Point(110,210));
        pgn.Points.Add(new Point( 60,210));
        pgn.Points.Add(new Point( 60, 60));
        pgn.Points.Add(new Point(210, 60));
        pgn.Points.Add(new Point(210,160));
        pgn.Points.Add(new Point( 10,160));
        pgn.Points.Add(new Point( 10,130));
        pgn.Points.Add(new Point(130,130));
        pgn.Points.Add(new Point(130, 90));
        pgn.Points.Add(new Point( 10, 90));

        Polygon pgn2  = new Polygon();
        pgn2.Stroke   = Brushes.Gray;
        pgn2.Fill     = Brushes.Pink;
        pgn2.FillRule = FillRule.Nonzero;

        foreach (Point pt in pgn.Points){
            pgn2.Points.Add(new Point(pt.X + 30 , pt.Y));
        }

        Button btnCChange = new Button();
        btnCChange.Width = 200; 
        btnCChange.Content = "押すと赤で塗りつぶします";
		btnCChange.Click += (sender, e) => {
            if (pgn.Fill == null) {
                pgn.Fill  = Brushes.Red;
                pgn2.Fill = Brushes.Red;
                btnCChange.Content = "押すと青で塗りつぶします";
            } else if (pgn.Fill == Brushes.Red) {
                pgn.Fill  = Brushes.Blue;
                pgn2.Fill = Brushes.Blue;
                btnCChange.Content = "押すと塗りつぶしません";
            } else if (pgn.Fill == Brushes.Blue) {
                pgn.Fill  = null;
                pgn2.Fill = null;
                btnCChange.Content = "押すと赤で塗りつぶします";
            }
        };

        StackPanel stpnl = new StackPanel();
        stpnl.Orientation = Orientation.Horizontal;

        stpnl.Children.Add(pgn);
        stpnl.Children.Add(pgn2);
        stpnl.Children.Add(btnCChange);

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

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

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


f:id:itotto:20080914222628p:image


画面上に重なり合った図形が2組、表示されます。

細かいことは考えずに、まずは右側に配置されているボタンを一度押してください。


f:id:itotto:20080914222629p:image



両方赤く塗りつぶされましたが、塗りつぶし方が左右で異なります。

もう一度ボタンを押してみます。


f:id:itotto:20080914222630p:image


今度は塗りつぶしが青に変わりました。

さらにもう一度ボタンを押してみます。


f:id:itotto:20080914222631p:image


これで最初の状態に戻りました。あとはこの繰り返しなので気の済むまでボタンを押してみてください。

ポイントはPolygonクラスのインスタンスのFillプロパティには塗りつぶしの色を指定できるということ(nullの場合は塗りつぶしなし)と、塗りつぶしのパターンを帰られるということです。塗りつぶしのパターンはPolygonクラスのプロパティであるFillRule(列挙型)で指定できます。

この列挙型についてはMSDNに詳しく図解されているのでこちらをご覧ください。



4.5 テキスト

単純な文字列はContentControlのContentプロパティに設定すれば表示出来ますが、この場合は細かい制御がまったくできず不便です。

表示している文字列を制御する場合にはTextBlockクラスを利用します。


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

class Test {
	[STAThread]
	public static void Main(){
                TextBlock txtblk = new TextBlock();
                txtblk.Width     = 500;
                txtblk.FontSize  = 20;
                txtblk.Text      = "テスト!!";

                Button btnSetPos = new Button();
                btnSetPos.Width  = 500;
                txtblk.TextAlignment = TextAlignment.Center;
                btnSetPos.Content = "文字を左に配置します";

                btnSetPos.Click += (sender , e) => {
                    if (txtblk.TextAlignment == TextAlignment.Center){
                        txtblk.TextAlignment = TextAlignment.Left;
                        btnSetPos.Content = "文字を右に配置します";

                    } else if (txtblk.TextAlignment == TextAlignment.Left){
                        txtblk.TextAlignment = TextAlignment.Right;
                        btnSetPos.Content = "文字を両端揃えに配置します";

                    } else if (txtblk.TextAlignment == TextAlignment.Right){
                        txtblk.TextAlignment = TextAlignment.Justify;
                        btnSetPos.Content = "文字を中央揃えに配置します";

                    } else if (txtblk.TextAlignment == TextAlignment.Justify){
                        txtblk.TextAlignment = TextAlignment.Center;
                        btnSetPos.Content = "文字を左に配置します";
                    }
                };


                Button btnInline = new Button();
                btnInline.Width  = 500;
                btnInline.Content = "文字の一括追加";

                btnInline.Click += (sender , e) => {
                    txtblk.Inlines.Clear();
                    txtblk.Inlines.Add(              new Run("テスト!!"));
                    txtblk.Inlines.Add(new Bold(     new Run("テスト!!")));
                    txtblk.Inlines.Add(new Italic(   new Run("テスト!!")));
                    txtblk.Inlines.Add(new Underline(new Run("テスト!!")));
                    txtblk.Inlines.Add(new Hyperlink(new Run("テスト!!")));
                };

        	Window wnd = new Window();

	        Canvas cnvs = new Canvas();
                Canvas.SetLeft(txtblk   , 20);
	        Canvas.SetLeft(btnSetPos, 20);
        	Canvas.SetLeft(btnInline, 20);
	        Canvas.SetTop(txtblk   , 20);
        	Canvas.SetTop(btnSetPos, 120);
	        Canvas.SetTop(btnInline, 220);
        	cnvs.Children.Add(txtblk);
	        cnvs.Children.Add(btnSetPos);
        	cnvs.Children.Add(btnInline);

	        wnd.Content = cnvs;

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

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


f:id:itotto:20080914222632p:image


テストという文字が表示されており、その下にボタンが2つ表示されています。

まずは上のボタンを押してみます。


f:id:itotto:20080914222633p:image


テストという文字が中央揃えから左寄せになりました。

もう一度同じボタンを押してみます。


f:id:itotto:20080914222634p:image



文字が右寄せに変更されました。

さらにもう一度ボタンを押してみます。


f:id:itotto:20080914222635p:image


うーん...。

これは左寄せとの違いがちょっと分かりにくいです。とりあえず両端揃えとするとこんな感じになるようです。

最後にもう一度同じボタンを押してみます。


f:id:itotto:20080914222636p:image


これで最初の状態に戻りました。

次に一番下のボタンを押してみます。


f:id:itotto:20080914222637p:image


なにやら「テスト」という文字がさまざまなスタイルで追加されました。

TextBlockを使うと、表示する文字の場所(レイアウト)やスタイルを変えるのも簡単に実施できます。


それにしても4章長いなあ。

techedのBOFで聴いた限りでは、xamlの直接記述とデータバインドがWPFの肝らしいのですが、どちらもまったく手をつけていません。

次回から少し飛ばしながらやっていこうと思います。

2008-07-22

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


私が勉強で使っているテキストはこちらです。

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

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


今回は4章の「描画オブジェクト」についてまとめます。


分類私の環境
OSWindows Vista SP1
Framework.NET Framework3.5
editorgvim 7.1

第4章 描画オブジェクト

UIのあるプログラムを作っていると画面上に線を引いたり、図形を描いたりということをしたくなるはずです。

この章ではWPFで代表的な図形の描画/設定をする方法についてまとめます。


線を引く

線を引くのはLineクラスを使用します。


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


class Test {
	[STAThread]
	public static void Main(){
		Window wnd = new Window();

		// 線を引く //
		Line ln1 = new Line();
		ln1.Stroke = Brushes.Blue;
		ln1.X1 = 10;
		ln1.Y1 = 10;
		ln1.X2 = 410;
		ln1.Y2 = 210;

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

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

		// ボタンを追加 //
		Button btn1 = new Button();
		btn1.Content = "線を太くする";
		Canvas.SetLeft(btn1, 50);
		Canvas.SetTop(btn1, 90);
		btn1.Height = 50;
		btn1.Width  = 100;

		btn1.Click += (sender, e) => {
			ln1.StrokeThickness +=  5;
			ln2.StrokeThickness += 10;
			ln3.StrokeThickness += 15;
		};

		Button btn2 = new Button();
		btn2.Content = "線の端部を変更";
		Canvas.SetLeft(btn2, 180);
		Canvas.SetTop(btn2,180);
		btn2.Height = 50;
		btn2.Width  = 100;

		btn2.Click += (sender, e) => {
			PenLineCap plc = ln1.StrokeStartLineCap;
			switch ( plc ) {
				case PenLineCap.Square  :
					plc = PenLineCap.Flat;
					break;
				case PenLineCap.Flat    :
					plc = PenLineCap.Round;
					break;
				case PenLineCap.Round   :
					plc = PenLineCap.Triangle;
					break;
				case PenLineCap.Triangle:
					plc = PenLineCap.Square;
					break;
			}
			ln1.StrokeStartLineCap = plc;
			ln2.StrokeStartLineCap = plc;
			ln3.StrokeStartLineCap = plc;
			ln1.StrokeEndLineCap   = plc;
			ln2.StrokeEndLineCap   = plc;
			ln3.StrokeEndLineCap   = plc;
		};

		Button btn3 = new Button();
		btn3.Content = "線を点線に変更";
		Canvas.SetLeft(btn3, 50);
		Canvas.SetTop(btn3,180);
		btn3.Height = 50;
		btn3.Width  = 100;

		btn3.Click += (sender, e) => {
			DoubleCollection dbl = new DoubleCollection();
			dbl.Add(1);
			dbl.Add(0.5);
			dbl.Add(2);
			dbl.Add(0.5);

			ln1.StrokeDashArray = dbl;
			ln2.StrokeDashArray = dbl;
			ln3.StrokeDashArray = dbl;
		};

		Canvas cnvs = new Canvas();
		cnvs.Children.Add(ln1);
		cnvs.Children.Add(ln2);
		cnvs.Children.Add(ln3);
		cnvs.Children.Add(btn1);
		cnvs.Children.Add(btn2);
		cnvs.Children.Add(btn3);

		wnd.Content = cnvs;

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

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

実行すると線が3本とボタンが3つの画面が表示されます。


f:id:itotto:20080702003857p:image


「線を太くする」ボタンを押すたびに3本の線が少しずつ太くなっていきます。


f:id:itotto:20080702003858p:image


次に「線の端部を変更」ボタンを押すと、押すたびに線の端の形が変わります。


f:id:itotto:20080702003859p:image

f:id:itotto:20080702003900p:image


最後に「線を点線に変更」を押すと線が点線になります。


f:id:itotto:20080702003901p:image


このようにLineクラスのプロパティを変更することでさまざまな線を描画することが出来ます。



長方形と円


単線ではなく閉じた図形を描くことも可能です。


まずは長方形を描画してみます。そのままだとつまらないので、角を丸くするためのボタンや色を変えるためのボタンも追加します。


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

class Test {
	[STAThread]
	public static void Main(){
		Rectangle rect1 = new Rectangle();
		rect1.Fill   = Brushes.Red;
		rect1.Width  = 400;
		rect1.Height = 200;
		Canvas.SetTop(rect1, 10);
		Canvas.SetLeft(rect1, 10);

		// ボタンを追加 //
		Button btn1 = new Button();
		btn1.Content = "角を丸くします";
		Canvas.SetLeft(btn1, 70);
		Canvas.SetTop(btn1, 80);
		btn1.Height = 50;
		btn1.Width  = 120;

		btn1.Click += (sender, e) => {
			if ( (string)btn1.Content == "角を丸くします" ){
				btn1.Content  = "角を戻します";
				rect1.RadiusX = 30;
				rect1.RadiusY = 30;
			} else {
				btn1.Content  = "角を丸くします";
				rect1.RadiusX = 0;
				rect1.RadiusY = 0;
			}
		};

		// ボタンを追加 //
		Button btn2 = new Button();
		btn2.Content = "青で塗りつぶします";
		Canvas.SetLeft(btn2, 210);
		Canvas.SetTop(btn2, 80);
		btn2.Height = 50;
		btn2.Width  = 120;

		btn2.Click += (sender, e) => {
			if ( (string)btn2.Content == "青で塗りつぶします" ){
				rect1.Fill = Brushes.Blue;
				btn2.Content  = "赤で塗りつぶします";
			} else {
				btn2.Content  = "青で塗りつぶします";
				rect1.Fill = Brushes.Red;
			}
		};

		Canvas cnvs1 = new Canvas();

		// 矩形を追加
		cnvs1.Children.Add(rect1)  ;

		// ボタン1を追加
		cnvs1.Children.Add(btn1)   ;

		// ボタン2を追加
		cnvs1.Children.Add(btn2)   ;

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

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




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


実行すると画面上に赤い四角とその中にボタンが2つ表示されます。


f:id:itotto:20080715002708p:image


左側のボタンを押してみると四角の角が丸くなります。


f:id:itotto:20080715002709p:image


さらに右側のボタンを押してみると四角の色が青に変わります。


f:id:itotto:20080715002710p:image


ボタンを押すたびに動きが変わるので気が済むまで触ってください。


次回は描画オブジェクトの続きをまとめます。

2008-06-16

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


私が勉強で使っているテキストはこちらです。

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

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


今回は勉強するための環境作成と3章の「パネルとレイアウト」についてまとめます。


分類私の環境
OSWindows Vista SP1
Framework.NET Framework3.5
editorgvim 7.1


テスト環境作成

いつもIDEを使って開発をしていたので、今回のようにIDE無しの環境でプログラムを作るのが面倒に感じられます。ちょっと手抜きをするために、ソースのテンプレートを準備してそれをコピーするバッチファイルを作成しました。


今回、開発をするにあたってフォルダ階層はこのようにしました。

d:\pg\WPF>tree /F
フォルダ パスの一覧
ボリューム シリアル番号は 0006F240 2862:
D:.
│  c.bat
│
├─exe
│
├─source
│
└─template
        template.cs

で、テンプレートファイルをコピーするバッチファイルを作成しました。ファイル(D:\pg\WPF\c.bat)の中はこんな感じです。

@ECHO OFF

CLS

:SELECTPROCESS
ECHO テンプレートファイルを作成しますか?
SET /PMKFILE=[Y:作る/N:作らない/D:一覧表示]

DEL .\*.*~ > NUL
MOVE *.exe .\exe > NUL
MOVE *.cs .\source > NUL

IF /I "%MKFILE%" EQU "D" GOTO SOURCEVIEW
IF /I "%MKFILE%" EQU "Y" GOTO MAKEFILE
IF /I "%MKFILE%" EQU "N" GOTO FINISH

:SOURCEVIEW
	CLS
	ECHO ● ソースファイル一覧
	DIR /ON "%~dp0source\*.cs" | FIND /I ".cs"
	GOTO SELECTPROCESS


:MAKEFILE
CLS
  SET NEWFILENAME=sample.cs
  ECHO ファイル名を入力してください[止める場合は.を入力]
  SET /PNEWFILENAME=[初期値:%NEWFILENAME%]?

  IF /I "%NEWFILENAME%" EQU "." GOTO FINISH

  COPY /Y "%~dp0template\template.cs" .\%NEWFILENAME%
  VI.EXE "%~dp0%NEWFILENAME%"


:FINISH
CLS


チョーシンプル。Simple is Bestです。

で。テンプレートファイル(D:\pg\WPF\template\template.cs)はこんな感じです。


using System;
using System.Windows;
using System.Windows.Controls;

class Test {
        [STAThread]
        public static void Main(){


                Window wnd = new Window();


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

今後、パターンが増えてきたらもう少しいいテンプレートを考案します。

というわけで開発環境の作成は終わり。


第3章 パネルとレイアウト

キャンバス

画面上にボタンなどを配置するためにCanvasクラスを利用します。


    1. 画面上の位置を指定する場合(左) → Canvas.SetLeft(UIElement,int)
    2. 画面上の位置を指定する場合(上) → Canvas.SetTop(UIElement,int)
    3. 重ね合わせ順を指定する場合   → Canvas.SetZIndex(UIElement,int)

では画面上にボタンを重ね合わせて表示して、押されたボタンが最上位に移動するようなプログラムを作成してみます。


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


class Test {
	[STAThread]
	public static void Main(){
		Button button1 = new Button();
		button1.Content = "button1";
		button1.FontSize = 50;
		Canvas.SetLeft(button1,10);
		Canvas.SetTop(button1,10);
		Canvas.SetZIndex(button1,0);


		Button button2 = new Button();
		button2.Content = "BUTTON2";
		button2.FontSize = 50;
		Canvas.SetLeft(button2,100);
		Canvas.SetTop(button2,30);
		Canvas.SetZIndex(button2,2);


		Button button3 = new Button();
		button3.Content = "ボタン3";
		button3.FontSize = 50;
		Canvas.SetLeft(button3,200);
		Canvas.SetTop(button3,50);
		Canvas.SetZIndex(button2,2);

		button1.Click += (sender, e) => {
			Canvas.SetZIndex(button1,2);
			Canvas.SetZIndex(button2,1);
			Canvas.SetZIndex(button3,0);
		};

		button2.Click += (sender, e) => {
			Canvas.SetZIndex(button2,2);
			Canvas.SetZIndex(button3,1);
			Canvas.SetZIndex(button1,0);
		};

		button3.Click += (sender, e) => {
			Canvas.SetZIndex(button3,2);
			Canvas.SetZIndex(button1,1);
			Canvas.SetZIndex(button2,0);
		};

		Canvas cnvs1 = new Canvas();
		cnvs1.Children.Add(button1);
		cnvs1.Children.Add(button2);
		cnvs1.Children.Add(button3);

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

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

}

SetLeft,SetTop,SetZIndexは前述のアタッチプロパティを介して各オブジェクトの位置/表示順を指定しています。

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


    • まず起動するとボタンが3つ重なり合って表示
      • f:id:itotto:20080611230527p:image

    • で、隠れているボタンを押してみると...

    • 押されたボタンが最前面になりました
      • f:id:itotto:20080611230528p:image

イベントハンドラがそのまま書けるのはとても便利でいいと思います。短い処理で汎用性がなければかなり重宝しそうです。


一応こんな感じでCanpasを利用してオブジェクトを配置出来るのですが、実際にはあまり使わない方法だとか。なんじゃそりゃ...。


スタックパネル

キャンバスの次はスタックパネル。名前からしてスタックのようにしてオブジェクトを管理するパネルでしょうか。

ひとまずスタックパネルでボタンを管理するサンプルを書いてみました。


using System;
using System.Windows;
using System.Windows.Controls;

class Test {
	[STAThread]
	public static void Main(){
		StackPanel stkpnl1 = new StackPanel();
		stkpnl1.Orientation = Orientation.Horizontal;

		string[] texts = {"ほげほげ1", "もげもげ", "ふがふが3", "フー4", "バー5"};

		foreach (string text in texts){
			Button btn  = new Button();
			btn.FontSize = 30;
			btn.Content = text;

			btn.Click += (sender, e) => {
				stkpnl1.Children.Remove(sender as Button);
			};

			stkpnl1.Children.Add(btn);
		}

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


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

押したボタンから消えていきますが、よく考えたらもっとスタックらしいプログラムを書けばよかった...。

ま、いいや。とりあえずこんなことも出来るよってことで。


ドックパネル

ドックパネルは自身の子要素を上下左右に配置するためのパネルです。


using System;
using System.Windows;
using System.Windows.Controls;

class Test {
	[STAThread]
	public static void Main(){
		DockPanel dkPanel1 = new DockPanel();

		Button[] btns   = new Button[5];
		string[] texts  = {"Top", "Bottom", "Left", "Right", "Center"};
		Dock[] DockType = {Dock.Top, Dock.Bottom, Dock.Left, Dock.Right}; 

		for (int i = 0 ;  i < btns.Length ; i++){
			btns[i]          = new Button();
			btns[i].Content  = texts[i];
			btns[i].FontSize =  30;
			if (i == 4) {
				btns[i].Click += (sender, e) => {
					dkPanel1.LastChildFill = !dkPanel1.LastChildFill;
				};
			} else {
				DockPanel.SetDock(btns[i], DockType[i]);
			}
			dkPanel1.Children.Add(btns[i]);
		}

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

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

ここで出てきているDockPanel.SetDock()はアタッチプロパティを利用しています。少しずつ見慣れてきたとはいえ、やはりなじめない書き方です。


とりあえず動かして見ます。


    • 実行するとボタンが表示されて...
      • f:id:itotto:20080611230529p:image
    • 押すと中央部分の大きさが変わります
      • f:id:itotto:20080612005915p:image

これは中央部分のボタン(Centerとテキストが書かれたボタン)のクリックイベントにDockPanelのLastChildFillプロパティをtrue/falseを切り替えるようにしたためです。このプロパティ次第で最後に追加されたCenter用のボタンが空き部分を作るのかどうか挙動が変わります。


このレイアウトは結構使いそうなので(テキストにもよく使うと書いてあります)覚えておいた方がよさそうです。


グリッド

表のようなレイアウトを実現するのがグリッドです。

グリッドで表を表現するためには列と行を設定する必要があります。


大まかな流れは以下のとおりです

    1. Gridクラスを作成する(grdと命名)
    2. grdに追加したい列数分、ColumnDefinitionsにColumnDefinitionクラスのインスタンスを追加する
    3. grdに追加したい行数分、RowDefinitionsにRowDefinitionクラスのインスタンスを追加する
    4. 表示するためのWindowクラスを作成する
    5. 上記で作成したWindowクラスのContentプロパティにgrdを設定
    6. Windowクラスのインスタンスを表示
using System;
using System.Windows;
using System.Windows.Controls;

class Test {
	[STAThread]
	public static void Main(){
		string[,] texts = {
			{"1-1","2-1","3-1"},
			{"1-2","2-2","3-2"},
			{"1-3","2-3","3-3"}
		};

		Grid grdPnl = new Grid();
		
		for (int i = 0 ; i < texts.GetLength(0) ; i++){
			grdPnl.ColumnDefinitions.Add(new ColumnDefinition());
			grdPnl.RowDefinitions.Add(new RowDefinition());
			for (int j = 0 ; j < texts.GetLength(1) ; j++){
				Button btn = new Button();
				btn.Content = texts[i,j];

				btn.Click += (sender, e) => {
					MessageBox.Show((sender as Button).Content.ToString());
				};

				Grid.SetRow(btn,i);
				Grid.SetColumn(btn,j);

				grdPnl.Children.Add(btn);
			}
		}

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


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

実行すると3x3のボタンがある画面が表示されます。

f:id:itotto:20080616224202p:image


ボタンを押すと押したボタンのテキストをメッセージボックスに表示します。

f:id:itotto:20080616224203p:image


ちなみにここで使用しているMessageBoxクラスは通常のWindowsフォームで使用している名前空間(System.Windows.Forms)ではなく、System.WindowsのMessageBoxクラスです。


次にグリッドの線を変更してみます。

グリッドに線を引くのはGridクラスのShowGridLinesプロパティを使用します。上のソースを少し手直しして再利用します。


using System;
using System.Windows;
using System.Windows.Controls;

class Test {
	[STAThread]
	public static void Main(){
		string[,] texts = {
			{"1-1","2-1","3-1"},
			{"1-2","2-2","3-2"},
			{"1-3","2-3","3-3"}
		};

		Grid grdPnl = new Grid();
		Window wnd  = new Window();
		
		for (int i = 0 ; i < texts.GetLength(0) ; i++){
			grdPnl.ColumnDefinitions.Add(new ColumnDefinition());
			grdPnl.RowDefinitions.Add(new RowDefinition());
			for (int j = 0 ; j < texts.GetLength(1) ; j++){
				Button btn = new Button();
				btn.Content = texts[i,j];

				btn.Click += (sender, e) => {
					grdPnl.ShowGridLines = !grdPnl.ShowGridLines ; 
					wnd.Title = (sender as Button).Content.ToString();
				};

				Grid.SetRow(btn,i);
				Grid.SetColumn(btn,j);

				grdPnl.Children.Add(btn);
			}
		}

		wnd.Content = grdPnl;

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

これを実行してみます。

初期表示される画面は修正前のソースで作ったプログラムと一緒です。


f:id:itotto:20080616224412p:image


で、何かボタンを押してみるとボタンとボタンの間に点線が表示されます。

f:id:itotto:20080616224413p:image


さらに今回はMessageBoxではなく、WindowのTitleを利用してどのボタンが押されたのかをわかるようにしてみました。


グリッドのまとめとして複数グリッドにまたがったボタンを作って最後にします。

using System;
using System.Windows;
using System.Windows.Controls;

class Test {
	[STAThread]
	public static void Main(){
		Window wnd  = new Window();
		Grid grdPnl = new Grid();
		grdPnl.ShowGridLines = true;
		
		for (int i = 0 ; i < 3 ; i++){
			grdPnl.ColumnDefinitions.Add(new ColumnDefinition());
			grdPnl.RowDefinitions.Add(new RowDefinition());
		}

		Button btn1 = new Button();

		btn1.Content = "1-1...3-1";
		Grid.SetRow(btn1,0);
		Grid.SetColumn(btn1,0);
		Grid.SetColumnSpan(btn1,3);

		btn1.Click += (sender, e) => {
			wnd.Title = (sender as Button).Content.ToString();
			grdPnl.ShowGridLines = !grdPnl.ShowGridLines ; 
		};

		Button btn2 = new Button();

		btn2.Content = "2-2...2-3";
		Grid.SetRow(btn2,1);
		Grid.SetColumn(btn2,1);
		Grid.SetRowSpan(btn2,2);

		btn2.Click += (sender, e) => {
			wnd.Title = (sender as Button).Content.ToString();
			grdPnl.ShowGridLines = !grdPnl.ShowGridLines ; 
		};

		grdPnl.Children.Add(btn1);
		grdPnl.Children.Add(btn2);

		wnd.Content = grdPnl;


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

実行するとたしかに複数グリッドにまたがったボタンのある画面が表示されます。

f:id:itotto:20080616224414p:image


「2-1...2-3」ボタンを押すとグリッドが消えてタイトルが変わります。

f:id:itotto:20080616224415p:image


今度は「1-1...3-1」ボタンを押すとグリッドがまた出てきてタイトルが変わります。

f:id:itotto:20080616224416p:image


複数のグリッドを超えてボタンが設置出来るのは非常に面白いと感じます。



以上で3章はおしまい。

実際に作ってみてやっとWindowsフォームとの違いが何となく感じられるようになってきた感じがします。積み木を作っているようなそんな印象を受けました。

テキストと全く同じものを作っても得るものが少ないと思い、イベントハンドラの追加やMessageBoxを使用してみましたが、このあたりの作りやすさはさすがだと思います。特にイベントハンドラに匿名メソッドを割り当てられるのは便利だと感じています。

多用する機会はなさそうですが、あって悪い機能ではないしそのうち好きになりそうです。


次は4章の描画オブジェクトについてまとめます。

2008-06-07

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


久しぶりに新しいことに手を出してみようと思います。今回はテーマをWPFに絞って3回くらいに分けて記事をまとめます。

使うテキストはこちらです。


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

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


これ一冊をしっかりと勉強して、WPFで何かアプリケーションを作ってみることを最終目的に掲げます。

何を作るのかは、やっていきながら考えます。


分類私の環境
OSWindows Vista SP1
Framework.NET Framework3.5
editorgvim 7.1


第1章 .NET FrameworkとWPF

WPFの概要についてと開発環境の作成方法について書いてあります。

WPFは.NET Framework3.0からの機能であることや元々WinFXというコードネームで開発されていたということが簡単に説明してあります。

開発環境の作成方法を見て、初めてIDEを使わない前提の本であることを知りました。

敷居を低くするのが狙いのようですが、どうせExpress Editionはただなんだからそれを入れさせればいいのに...。

開発環境の作成については、私のPCには既にVS.NET 2008 Standardが入っているので、そのツールのひとつとしてインストールされているVS.NET用のコマンドプロンプトを私用することにしました。これであればわざわざPATHを通したりする必要はありません。


f:id:itotto:20080606004155p:image


ただ、参照するモジュールについての設定だけは必要なので行いました。方法は以下の通りです。


    • explorerを開く
    • フォルダを開く (C:\Windows\Microsoft.NET\Framework\v3.5)
    • このフォルダ内にあるcsc.rspをメモ帳で開く
    • 以下のように追記する

  f:id:itotto:20080606004156p:image

    • csc.rspを保存する(読み取り専用やアクセス権で保存できない場合はそれを解消)

これでコンパイラのオプションにWPF用の参照を追記しなくても済みます。便利。


第2章 ウィンドウアプリケーション

この章から実際にサンプルを書いてコンパイルして動かしてみます。

とは言っても全部試す必要もなくて適当なサンプルを作って試してみます。


ウィンドウを表示するだけの簡単なサンプル
using System;
using System.Windows;
using System.Windows.Media;

class Test {
	[STAThread]
	public static void Main(){
		Window wnd   = new Window();

		wnd.Topmost  = true;

		wnd.Width    = 200;
		wnd.Height   = 200;

		wnd.Content  = "ほげほげ";

		wnd.FontSize = 30;
		wnd.Background = Brushes.Red;
		wnd.FontFamily = new FontFamily("HG行書体");
		wnd.FontStyle  = FontStyles.Italic;

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

これを保存してコンパイルしてみます。

f:id:itotto:20080607011502p:image


で、出来たプログラムを実行すると赤いフォームが表示されます。


f:id:itotto:20080607011503p:image


正直、この程度であればWPFを使わなくてもWindowsアプリケーションで作れるし、書く内容だってほとんど一緒。うーん....。


統合されたUI要素
    1. 可視化されるものは全部System.Windows.UIElementクラスを継承してる
    2. 線とか図形は描画するんじゃなくてオブジェクトとして利用する

サンプルが載ってたのでちょっといじって動かしてみました。


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

class Test {
	[STAThread]
	public static void Main(){
		CheckBox chkBox1 = new CheckBox();
		chkBox1.Content  = "チェックボックス";
		chkBox1.FontSize = 20;

		Ellipse elp1     = new Ellipse();
		elp1.Width       = 200;
		elp1.Height      = 100;
		elp1.Fill        = Brushes.Red;

		Label lbl1       = new Label();
		lbl1.FontSize    = 30;
		lbl1.Content     = "This is Label";

		StackPanel pnl1  = new StackPanel();
		pnl1.Children.Add(chkBox1);
		pnl1.Children.Add(elp1);
		pnl1.Children.Add(lbl1);

		Button btn1      = new Button();
		btn1.Content     = pnl1;

		Canvas cnvs      = new Canvas();
		cnvs.Children.Add(btn1);

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

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

	}
}

で、コンパイルして実行。

f:id:itotto:20080607015615p:image


ウィンドウの中にボタンがあってその中に楕円やチェックボックスが描画されています。

何となく今までのWindowsアプリケーションとの違いが見えてきました。


依存プロパティ

オブジェクトが動的(実行時)に状況に応じて使用するためのプロパティを依存プロパティと呼ぶそうです。


using System;
using System.Windows;

class Test {

	public static DependencyProperty LactProperty;
	static Test(){
		LactProperty = DependencyProperty.Register (
				"Lact",typeof(String),typeof(Test)
		);
	}

	[STAThread]
	public static void Main() {
		Window wnd = new Window();

		wnd.FontSize = 30;
		wnd.SetValue(LactProperty,"after change");
		wnd.Content  = wnd.GetValue(LactProperty);

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

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

f:id:itotto:20080607161214p:image


とりあえず使い方は分かったので終わり。使いどころはこれからかな...。



アタッチプロパティ

依存プロパティの一種で、使うクラスに情報を付加するといった用途などで持たせるプロパティをアタッチプロパティと呼びます。

とりあえずサンプルを書いてみます。


using System;
using System.Windows;

class TagManager {
	public static readonly DependencyProperty TagProperty ;
	static TagManager(){
		TagProperty = DependencyProperty.RegisterAttached(
				"Tag", typeof(String), typeof(TagManager)
				);
	}

	public static void SetTag(UIElement element, string value){
		element.SetValue(TagProperty, value);
	}

	public static String GetTag(UIElement element){
		return (string)element.GetValue(TagProperty);
	}
}

class Test {
	[STAThread]
	public static void Main(){
		Window wnd = new Window();
		TagManager.SetTag(wnd, "タイトル");
		wnd.Title = TagManager.GetTag(wnd);

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

コンパイルして実行するとこんな感じ。

f:id:itotto:20080607163054p:image


使い方は分かりましたが、説明を読んだ感じと印象がちょっと異なっているように感じました。これだとTagManagerクラスが単に属性の管理クラスになっているだけみたいです。

これも使っていきながら感覚をつかみます。



これで2章は終わり。明日からは3章のパネルとレイアウトと4章の描画オブジェクトを勉強します。