17. Windows Presentation Foundation 4.5 入門
16
コンストラクタで呼び出されている InitializeComponent メソッドは、XAML で定義された情報を使用するために必
須のメソッドです。このメソッドの呼び出しを忘れると、XAML で定義した情報が使用できなくなるので気を付けて
ください。
2.2.3. デザイナによる画面の設計
Visual Studio 2012 には、WPF アプリケーションの画面デザインを行うためのデザイナがついています。主にツー
ルボックスとデザイナとドキュメントアウトラインとプロパティを使用します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace HelloWorld
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
19. Windows Presentation Foundation 4.5 入門
18
helloWorldButton と設定しています。
Content 表示文字列。Hello world と設定しています。
その他の HorizontalAlignment プロパティなどはデザイナ上などで自動的に設定されたものなので、ここでは割愛
します。
2.2.4. イベントハンドラの追加とコードの記述
作成したボタンをダブルクリックすると、ボタンのクリックイベントが作成されます。プロパティのイベントからも
Windows Form アプリと同じ要領でイベントを作成できます。helloWorldButton_Click というメソッドが作成され
るので以下のようにメッセージボックスを表示するコードを追加してください。
2.2.5. コンパイルして実行
アプリケーションを実行すると、以下のようにボタンの置いてある画面が表示されます。ボタンを押すとメッセージ
ボックスが表示されます。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void helloWorldButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello world");
}
}
20. Windows Presentation Foundation 4.5 入門
19
2.2.6. Main メソッドはどこにいった?
Hello world を作る手順の中で App クラスが WPF における Main メソッドを持つエントリポイントのようなクラス
であるという説明を行いましたが、これについてもう少し詳しく説明したいと思います。App.xaml と App.xaml.cs
がコンパイルされる際に、以下のようなコードがコンパイラによって生成されます。このコードを見るには、ソリュ
ーションエクスプローラですべてのファイルを表示するように設定して「obj→Debug→App.g.cs」というコードを
開きます。
111. Windows Presentation Foundation 4.5 入門
110
画面に、Person クラスを表示するための TreeView コントロールを置きます。
画面のコンストラクタで、この TreeView コントロールの ItemsSource プロパティに Person クラスの List を設定
します。
using System.Collections.Generic;
namespace TreeViewSample03
{
public class Person
{
public string Name { get; set; }
public List<Person> Children { get; set; }
}
}
<TreeView Name="treeView">
</TreeView>
112. Windows Presentation Foundation 4.5 入門
111
この状態で画面を表示すると、以下のように Person クラスを ToString した結果が 2 つ TreeView コントロールに
表示されます。
public MainWindow()
{
InitializeComponent();
this.treeView.ItemsSource = new List<Person>
{
new Person
{
Name = "田中 太郎",
Children = new List<Person>
{
new Person { Name = "田中 花子" },
new Person { Name = "田中 一郎" },
new Person
{
Name = "木村 貫太郎",
Children = new List<Person>
{
new Person { Name = "木村 はな" },
new Person { Name = "木村 梅" },
}
}
}
},
new Person
{
Name = "田中 次郎",
Children = new List<Person>
{
new Person { Name = "田中 三郎" }
}
}
};
}
113. Windows Presentation Foundation 4.5 入門
112
Person クラスの Name プロパティが表示されるように TreeView コントロールの ItemTemplate プロパティを設
定します。ここでは、HierarchicalDataTemplate を使用していますが、ItemsSource プロパティにを設定していな
いので単純に TreeView コントロールに名前が表示されるだけになります。
MainWindow の属性で xmlns を使って Person クラスのある名前空間を local という名前で参照できるようにして
HierarchicalDataTemplate の DataType プロパティで、扱う型が Person クラスであることを指定しています。実
行すると以下のような表示になります。
この状態では、ルートの要素しか表示されないので HierarchicalDataTemplate の ItemsSource プロパティに
Person クラスの Children プロパティをバインドします。XAML を以下に示します。
xmlns:local="clr-namespace:TreeViewSample03"
<TreeView Name="treeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="local:Person">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
167. Windows Presentation Foundation 4.5 入門
166
Register メソッドを使い DependencyProperty クラスのインスタンスを作成します。作成したインスタンスは
public static readonly のフィールドに「プロパティ名 Property」の命名規約で格納します。DependencyProperty
の値は、DependencyObject クラスに定義されている GetValue、SetValue メソッドで取得と設定が可能です。上
記クラスを使って Name 依存関係プロパティの値の取得と設定をするコード例を以下に示します。
実行すると、以下のような出力になります。
GetValue メソッドと SetValue メソッドを使って依存関係プロパティの値の取得と設定が出来ますが、通常のプロ
パティの使用方法とかけ離れているため、通常は、以下のような CLR のプロパティのラッパーを作成します。
public class Person : DependencyObject
{
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register(
"Name", // プロパティ名を指定
typeof(string), // プロパティの型を指定
typeof(Person), // プロパティを所有する型を指定
new PropertyMetadata("default name")); // メタデータを指定。ここではデフォルト値を設定して
る
}
// GetValue, SetValueの使用例
var p = new Person();
// 値を取得
Console.WriteLine(p.GetValue(Person.NameProperty));
// 値を設定
p.SetValue(Person.NameProperty, "おおた");
// 値を取得
Console.WriteLine(p.GetValue(Person.NameProperty));
default name
おおた
168. Windows Presentation Foundation 4.5 入門
167
上記のプロパティを使うと使用する側のコードは自然な C#によるクラスを利用したコードになります。
5.2.1.2. デフォルト値の設定
Person クラスの例で示したように、依存関係プロパティは、メタデータを使ってでデフォルト値の設定が出来ま
す。デフォルト値は、全てのクラスで同じインスタンスが使われます。このようにして、大量のインスタンスが生成
されたときにメモリをデフォルト値によって無駄に使うことがないようになっています。その反面、List 型などのよ
うな参照型の値の場合同じインスタンスを使うと不都合があるケースがあります。
例えば先ほどの Person クラスに Children という List<Person>型の依存関係プロパティを追加してデフォルト値に
List<Person>型のインスタンスを指定したとします。
public class Person : DependencyObject
{
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register(
"Name", // プロパティ名を指定
typeof(string), // プロパティの型を指定
typeof(Person), // プロパティを所有する型を指定
new PropertyMetadata("default name")); // メタデータを指定。ここではデフォルト値を設定して
る
// 依存関係プロパティのCLRのプロパティのラッパー
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
var p = new Person();
Console.WriteLine(p.Name);
p.Name = "おおた";
Console.WriteLine(p.Name);
169. Windows Presentation Foundation 4.5 入門
168
このようにすると、2 つの Person クラスのインスタンスを作った時に、Children プロパティの値が共有されて不都
合がおきてしまいます。
このプログラムの実行結果はどちらも 2 が表示されてしまいます。このような問題を避けるためには、通常のプロパ
ティと同じように、デフォルト値をコンストラクタで行う必要があります。
public class Person : DependencyObject
{
// Nameプロパティは省略
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register(
"Children",
typeof(List<Person>),
typeof(Person),
new PropertyMetadata(new List<Person>())); // デフォルト値は共有される
public List<Person> Children
{
get { return (List<Person>)GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
}
// Childrenプロパティの使用
var p1 = new Person();
var p2 = new Person();
p1.Children.Add(new Person());
p2.Children.Add(new Person());
Console.WriteLine("p1.Children.Count = {0}", p1.Children.Count);
Console.WriteLine("p2.Children.Count = {0}", p2.Children.Count);
225. Windows Presentation Foundation 4.5 入門
224
このコントロールは、ControlTemplate をサポートした完全なコントロールです。以下のように定義することで見
た目のカスタマイズが使用者側で出来るようになっています。
実行結果を以下に示します。テンプレートが置き換わって Up ボタンと Down ボタンの位置が変わっていることが確
認できます。
最後に、コントロールがどんな PART と、VisualState をクラスの属性として定義します。これは、必須ではありま
せんが、Blend や Visual Studio が使用したり、ドキュメントとして役立つのでカスタムコントロールを作った時に
はつけておくことをおすすめします。
<StackPanel>
<!-- 通常の見た目 -->
<local:NumericUpDown />
<!-- コントロールテンプレートの差し替え -->
<local:NumericUpDown>
<local:NumericUpDown.Template>
<ControlTemplate TargetType="{x:Type local:NumericUpDown}">
<StackPanel>
<RepeatButton x:Name="PART_UpButton" Content="Up" />
<TextBlock Text="{Binding Value, RelativeSource={RelativeSource
AncestorType=local:NumericUpDown}}"
HorizontalAlignment="Center"/>
<RepeatButton x:Name="PART_DownButton" Content="Down" />
</StackPanel>
</ControlTemplate>
</local:NumericUpDown.Template>
</local:NumericUpDown>
</StackPanel>
226. Windows Presentation Foundation 4.5 入門
225
TemplatePart 属性で、どの型がどのような名前で ControlTemplate 内に存在することを期待しているか表しま
す。TemplateVisualState 属性で、どんな VisualStateGroup 内に VisualState が必要なのか表します。今回作成し
た NumericUpDown コントロールでは、以下のようになります。
5.10. Binding
WPF には、見た目とデータを分離して管理するための強力なデータバインディングの機能があります。WPF のデー
タバインディングは、依存関係プロパティとプロパティの間の同期をとる機能にないります。
5.10.1. 単純な Binding
データバインディングは、ソースに設定されたオブジェクトのプロパティとターゲットに設定された依存関係プロパ
ティ(添付プロパティも可)の間の同期をとります。例えば、以下のような Person クラスがあるとします。
[TemplatePart(Type = typeof(RepeatButton), Name = "PART_UpButton")]
[TemplatePart(Type = typeof(RepeatButton), Name = "PART_DownButton")]
[TemplateVisualState(GroupName = "PositiveNegative", Name = "Negative")]
[TemplateVisualState(GroupName = "PositiveNegative", Name = "Positive")]
public class NumericUpDown : Control
227. Windows Presentation Foundation 4.5 入門
226
このオブジェクトを Window のリソースに登録します。
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace DataBindingSample01
{
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName =
null)
{
field = value;
var h = this.PropertyChanged;
if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
}
private int age;
public int Age
{
get { return this.age; }
set { this.SetProperty(ref this.age, value); }
}
private string name;
public string Name
{
get { return this.name; }
set { this.SetProperty(ref this.name, value); }
}
}
}
<Window.Resources>
<local:Person x:Key="Person" Name="tanaka" Age="34" />
</Window.Resources>
231. Windows Presentation Foundation 4.5 入門
230
ルトで有効になっていて、もっとも柔軟な指定が可能な INotifyDataErrorInfo インターフェースを実装する方法に
ついて解説します。
INotifyDataErrorInfo インターフェースは同期、非同期の値の検証を実装するためのインターフェースで以下のよう
に定義されています。
HasErrors プロパティでオブジェクトにエラーが有るか無いかを返します。ErrorsChanged イベントでプロパティ
のエラーの状態に変化があったことを外部に通知します。GetErrors メソッドでプロパティのエラーを返します。
Name プロパティが必須入力で、Age プロパティに 0 以上を設定しないといけない Person クラスの実装例を以下に
示します。まず、必須のインターフェースの INotifyPropertyChanged と、INotifyDataErrorInfo のイベントやメ
ソッドなどを実装します。
public interface INotifyDataErrorInfo
{
bool HasErrors { get; }
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
IEnumerable GetErrors(string propertyName);
}
232. Windows Presentation Foundation 4.5 入門
231
これらのメソッドを使って Name プロパティと Age プロパティを実装します。エラーがあれば errors にエラーの内
容を追加します。エラーが無い場合はエラーの情報を null にします。そして最後にエラーに変化があったことを通知
する ErrorsChanged イベントを発行します。
public class Person : INotifyPropertyChanged, INotifyDataErrorInfo
{
// INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName =
null)
{
field = value;
var h = this.PropertyChanged;
if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
}
// INotifyErrorsInfo
private Dictionary<string, IEnumerable> errors = new Dictionary<string, IEnumerable>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private void OnErrorsChanged([CallerMemberName] string propertyName = null)
{
var h = this.ErrorsChanged;
if (h != null) { h(this, new DataErrorsChangedEventArgs(propertyName)); }
}
public IEnumerable GetErrors(string propertyName)
{
IEnumerable error = null;
this.errors.TryGetValue(propertyName, out error);
return error;
}
public bool HasErrors
{
get { return this.errors.Values.Any(e => e != null); }
}
}
233. Windows Presentation Foundation 4.5 入門
232
public class Person : INotifyPropertyChanged, INotifyDataErrorInfo
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.SetProperty(ref this.name, value);
if (string.IsNullOrEmpty(value))
{
this.errors["Name"] = new[] {"名前を入力してください" };
}
else
{
this.errors["Name"] = null;
}
this.OnErrorsChanged();
}
}
private int age;
public int Age
{
get { return this.age; }
set
{
this.SetProperty(ref this.age, value);
if (value < 0)
{
this.errors["Age"] = new[] { "年齢は0以上を入力してください" };
}
else
{
this.errors["Age"] = null;
}
this.OnErrorsChanged();
}
}
}
245. Windows Presentation Foundation 4.5 入門
244
5.10.6.5. 現在の選択項目をバインディングする方法
CollectionView は、現在の選択項目を管理しているので、同じ CollectionView をバインドしているもの同士では、
選択項目を同期することが可能です。選択項目を同期するには、Selector コントロール(ListBox などの親クラス)
の IsSynchronizedWithCurrentItem を True にする必要があります。選択中の項目をバインドするには、”/”を使っ
てコレクションとバインドします。たとえば、Categories というコレクションにバインドしていて、それの選択中
の項目の Name プロパティをバインドするためには”Categories/Name”のようにバインディングの Path を指定し
ます。この”/”を使ったバインディングは、親子だけでなく孫やその先までコレクションがある限り指定できます。
例えば、People コレクションの選択中の項目の Children プロパティ(これもコレクション)の選択中の項目の
Name プロパティは“People/Children/Name”のように指定できます。
例として以下のような Person クラスをバインドしたケースを示します。
246. Windows Presentation Foundation 4.5 入門
245
このクラスのコレクションを持った DataContext に格納するクラスを以下に示します。
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace CollectionBindingSample04
{
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName
= null)
{
field = value;
var h = this.PropertyChanged;
if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
}
private string name;
public string Name
{
get { return this.name; }
set { this.SetProperty(ref this.name, value); }
}
private int age;
public int Age
{
get { return this.age; }
set { this.SetProperty(ref this.age, value); }
}
}
}
247. Windows Presentation Foundation 4.5 入門
246
MainWindow のコンストラクタで DataContext に設定します。
この MainWindowViewModel クラスの People を DataGrid にバインドします。バインド時に、
IsSynchronizedWithCurrentItem を True に設定して、選択項目の同期を有効にします。
using System.Collections.ObjectModel;
namespace CollectionBindingSample04
{
public class MainWindowViewModel
{
private ObservableCollection<Person> people;
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; }
}
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel
{
People = new ObservableCollection<Person>(
Enumerable.Range(1, 100)
.Select(x => new Person
{
Name = "tanaka" + x,
Age = (30 + x) % 50
}))
};
}
<!-- 選択項目を同期するように設定してバインドする -->
<DataGrid IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding People}"/>
268. Windows Presentation Foundation 4.5 入門
267
6.2.データバインディングを前提としたプログラミングモデル
WPF では、強力なデータバインディングを活かした設計パターンとして Model View ViewModel パターンというア
プリケーションを設計するうえでの定石となる設計パターンがあります。Model View ViewModel パターンは
MVVM パターンと略されます。MVVM パターンは、WPF だけでなく Web アプリ開発や、その他のアプリ開発にも
波及していて、それぞれの状況に応じて形を変えて存在しています。
MVVM パターンは、MSDN マガジンの以下の記事をきっかけに世間に認知されるようになりました。
Model-View-ViewModel デザイン パターンによる WPF アプリケーション
http://msdn.microsoft.com/ja-jp/magazine/dd419663.aspx
また、Microsoft はオープンソースで Prism という MVVM パターンをサポートするライブラリを提供しています。
Prism
http://compositewpf.codeplex.com/
ここでは、簡単に MVVM パターンの考えについて説明したあと、Prism の一部の機能を使って実際に MVVM パター
ンのサンプルプログラムを作成していきたいと思います。
6.2.1. MVVM パターンとは
MVVM パターンは、View(XAML + コードビハインド)と ViewModel と呼ばれる Model を View に適したインタ
ーフェースに変換するレイヤと、アプリケーションを記述する Model のレイヤからなります。View と ViewModel
間は、基本的にデータバインディングによって連携を行います。ViewModel は Model の変更を監視したり、必要に
応じて Model のメソッドの呼び出しを行います。この関係を図で表すと以下のようになります。
269. Windows Presentation Foundation 4.5 入門
268
6.2.2. 変更通知の仕組み
MVVM パターンの、変更通知や双方向データバインディングの ViewModel から View 方向の変更通知には
INotifyPropertyChanged インターフェースを実装したクラスを使用します。INotifyPropertyChanged インターフ
ェースは PropertyChanged イベントのみをもつシンプルなインターフェースです。このイベントを通じて Model か
ら ViewModel、ViewModel から View への変更通知が行われます。
INotifyPropertyChanged インターフェースの実装をすべてのプロパティに実装するのは負荷が高いため、一般的に
以下のようなヘルパークラスが作成されます。
View ViewModel Model
双方向データバイ
ンディング
メソッド呼び出し
変更通知
270. Windows Presentation Foundation 4.5 入門
269
このクラスを継承すると、プロパティの変更通知機能を持ったクラスが以下のように簡単に作成できます。
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MVVMSample01
{
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetProperty<T>(ref T field, T value,
[CallerMemberName]string propertyName = null)
{
if (Equals(field, value)) { return false; }
field = value;
var h = this.PropertyChanged;
if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
return true;
}
}
}
271. Windows Presentation Foundation 4.5 入門
270
単一のクラスの変更通知は INotifyPropertyChanged インターフェースで行いますが、コレクションの変更通知に
は、これまでも使ってきた ObservableCollection<T>クラスを使用します。基本的に、この 2 通りの変更通知を通
して Model から ViewModel と ViewModel から View の間のやり取りを行います。
6.2.3. ユーザーからの入力の処理
ボタンをクリックするなどのユーザーの処理を View から ViewModel に伝えるには、Command を使用します。こ
の時使用する Command は RoutedCommand ではなく、デリゲートに Execute と CanExecute 処理を移譲する実
装の DelegateCommand(RelayCommand という名前で作られることも多いです)クラスを使用します。
DelegateCommand を ViewModel クラスのプロパティとして定義して、View の Button や MenuItem などの
Command プロパティとバインドして使用します。
6.2.4. 最初のアプリケーションの作成
各レイヤの連携方法がわかったので、簡単なアプリケーションを作成します。このアプリケーションは、入力した文
字列を、ボタンを押したタイミングで大文字に変換して出力するものです。ボタンは、入力が空の場合は押すことが
namespace MVVMSample01
{
public class Person : BindableBase
{
private int age;
public int Age
{
get { return this.age; }
set { this.SetProperty(ref this.age, value); }
}
private string name;
public string Name
{
get { return this.name; }
set { this.SetProperty(ref this.name, value); }
}
}
}
272. Windows Presentation Foundation 4.5 入門
271
できません。また、このサンプルプログラムは、処理が単純すぎるため Model に該当する部分は存在しません。あ
くまで View と ViewModel が連携した場合の動きを示すものです。
WPF アプリケーションを作成して NuGet で Prism.Mvvm のパッケージを追加します。Prism.Mvvm は
BindableBase クラスや DelegateCommand クラスなどの MVVM パターンに必須のクラスだけを持ったシンプルな
ライブラリです。
ライブラリを追加したら、ViewModel を作成します。MainWindowViewModel という名前でクラスを作って以下の
ようなコードを作成します。入力用のプロパティと出力用のプロパティと変換用のコマンドを定義しています。コマ
ンドの実行可否は、入力値が変化するたびに評価が必要なので DelegateCommand の CanExecute を再評価するた
めのメソッドを呼び出しています。
クラスを定義します。
入力、出力を受け取るプロパティを定義します。
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Mvvm;
namespace MVVMSample01
{
public class MainWindowViewModel : BindableBase
{
}
}
273. Windows Presentation Foundation 4.5 入門
272
そして、Command を定義します。
private string input;
/// <summary>
/// 入力値
/// </summary>
public string Input
{
get { return this.input; }
set
{
this.SetProperty(ref this.input, value);
// 入力値に変かがある度にコマンドのCanExecuteの状態が変わったことを通知する
this.ConvertCommand.RaiseCanExecuteChanged();
}
}
private string output;
/// <summary>
/// 出力値
/// </summary>
public string Output
{
get { return this.output; }
set { this.SetProperty(ref this.output, value); }
}