Windows Presentation Foundation4.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();
}
}
}
Windows Presentation Foundation4.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 Foundation4.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 Foundation4.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>
Windows Presentation Foundation4.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 Foundation4.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 Foundation4.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);
Windows Presentation Foundation4.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 Foundation4.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>
Windows Presentation Foundation4.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 Foundation4.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 Foundation4.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}"/>
Windows Presentation Foundation4.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 Foundation4.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 Foundation4.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 Foundation4.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); }
}