-
WPF
-
バージョン3.0以降の.NET Frameworkに標準搭載
-
Windowsフォーム
-
それより前のGUI開発ライブラリ
- 単にWin32 APIをマネージ・コードでラップしたもの
- WPFはマネージ・コードで新たに実装されたGUI開発ライブラリ
-
WPFの特徴と利点
-
グラフィックス・ハードウェア
- コアの部分にグラフィックス・ハードウェアを活用したベクター・ベースのレンダリング・エンジンを採用
- ベクター・ベースであるため、UI要素にスムーズな拡大・縮小/回転を掛けることができる
-
コントロール、メディア、文書の統合
- コントロール、3D描画、メディア、リッチテキストなどの整形済み文書に対して、統一的な開発機能を提供
- ボタンの中に動画を表示するといった組み合わせも簡単
-
UIカスタマイズの柔軟性
- 任意の形状のボタンを作成
- 背景に動画
-
見た目(=外観デザイン)とロジックの分離
-
XAML(Extensible Application Markup Language)
- XML形式の宣言的言語を用いてユーザー・インターフェイスを記述
-
プログラミング・モデル
-
XAMLコード+分離コード
- ロジックが必要な場合には、C#などで記述する(「分離コード」と呼ぶ)
- ビルド時に1つのクラスに合成
-
BAML(Binary Application Markup Language)
- XAMLコードをバイナリ化したもの
-
ツリー構造のUI要素
-
WPFのUI要素はツリー構造により構築
- ボタンの中に動画を配置したり、コンボボックスの中にボタンを並べたりすることも可能
-
特徴
- 1.平行移動や回転などの変形は、親要素からの相対位置に基づいて、子要素もろとも
- 2.添付プロパティという仕組みを用いて、要素自身ではなく、親要素が使う情報を持てる
- 3.FontSizeなど、一部のプロパティは親要素から値を継承する(包含継承)
-
データ・バインディング
- ビュー側には「ここにこのデータを表示したい」というような目印だけを入れ、 実際のデータは外部から与える
- XAMLコード内で、属性値に「{Binding}」という記述を行う
- 実際に表示したいデータは、DataContextというプロパティを介して渡す
- ビューとモデルの接点はDataContextプロパティのただ1点のみ
-
UI要素
-
分類
- コントロール
- コンテナ
- シェイプ
- ドキュメント
- 標準コントロール
-
WPF Toolkit
- 開発途中のコントロールをいち早く開発者が試せるように、マイクロソフトがCodePlexというサイトにおいてオープン・ソースとして提供している
-
コンテナ
- StackPanel: 要素を縦または横に並べて配置
- WrapPanel: HTMLのインライン要素と同じように、画面の幅に合わせて自動的に折り返す
- DockPanel: UI要素を上下左右に貼り付ける形で配置
- Grid: UI要素を格子状に配置
- Canvas: UI要素を、座標や幅・高さの値を明示的に指定して配置
- シェイプ
-
メディア
- 静止画を表示するためのImageコントロール
- 動画や音声を再生するためのMediaElementコントロール
- ほかのUI要素ときっちり統合
- ドキュメント
-
WPFとXAMLの関係性
-
XAML=「CLRオブジェクトのインスタンス生成」
- WF(Windows Workflow Foundation)でも、ワークフローの記述にXAMLを利用
-
CLRオブジェクトとXAMLコードの双方向変換
- XAML形式でシリアライズ/デシリアライズすることが可能
-
依存関係プロパティ(dependency property)
-
用途
- 包含継承: 親要素で設定した値をそのまま継承して使う
- リソース: 1カ所で定義したオブジェクトを複数カ所から参照
- スタイル: HTMLでいうところのCSSのようなスタイル設定
- データ・バインディング: モデルとビューの間など、異なるオブジェクト間で値を結び付ける
-
意義
- パフォーマンス
- メタデータの保持
- 値の優先順位
-
ルーティング・イベント(routed event)
- 依存関係プロパティのイベント版
- 発生したイベントが要素ツリーをたどって“ルーティング”される
-
種類
- 直接(Direct): イベント発生源となる要素自身のイベント・ハンドラのみが呼び出し
- トンネル(Tunnel): 要素ツリーのルートからイベント発生源となる要素に向かって、要素ツリーを掘り進むようにイベント・ハンドラが呼び出される
- バブル(Bubble): イベント発生源となる要素からルートに向かって、要素ツリーをたどりながら、浮かび上がるようにイベント・ハンドラが呼び出される
-
利用例
- 子要素で発生したイベントを親要素自身で一括して処理したい場合など
- <UserControl x:Class="MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ListBox Name="list" Button.Click="Button_Click">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
-
データバインディング
- データ・ソース(=モデルなどの、データの提供元)をビュー(=WPFの場合はXAMLコード)上のUI要素と簡単に結び付ける
- 値を一度だけ代入するのではなく、「1カ所で値が変化するたびに、ほかの個所にもその変化が即座に伝搬される」
- 利用法
-
Bindingマークアップ拡張
-
向き(Modeプロパティ)
- OneTime: UI要素生成時に一度だけ、ソース・プロパティの値を読み出してターゲット・プロパティに与える
- OneWay: ソース・プロパティが変更された際に、ターゲット・プロパティに変更を反映させる
- OneWayToSource: ターゲット・プロパティが変更された際に、ソース・プロパティに変更を反映
- TwoWay:ソース・プロパティおよびターゲット・プロパティのいずれの変更も、他方に反映
-
タイミング(UpdateSourceTriggerプロパティ)
- Default: バインディング・ターゲットの依存関係プロパティのメタデータに基づいてタイミングを決定
- PropertyChanged: バインディング・ターゲットの値が変化するたびに(例えば、テキストボックスの場合、1文字入力されるたびに)変更を通知
- LostFocus: バインディング・ターゲットの要素がフォーカスを失うたびに(例えば、テキストボックスからフォーカスを外した際に)変更を通知
- Explicit: 明示的にUpdateSourceメソッドを呼び出した場合にのみ変更を通知
-
ソースのパスの書き方
-
Pathプロパティは省略形で書くことができる
- {Binding X} -> {Binding Path=X}
- 「{Binding X.Y}」のように、「.」でつなぐことで階層的なパス指定が可能
- 「{Binding X[0]}」のように、角カッコを用いてインデクサを利用可能
-
「コマンド」と「MVVMパターン」
-
コマンド
-
意味論的なイベント処理(コマンド)
- WPFでは、この意味論的なイベントを「コマンド(command)」と呼んでいる
-
コマンドの実行可否
- コマンドの実行可否はモデルの状態に応じて随時変化し、これに応じてボタンやメニューなどの有効/無効を切り替えなければならない
- データが変化したことを(各ビューに)通知する仕組み(=コマンド)が必要
-
2つの機能を提供する「コマンド」(command)という仕組みを持っている
- 意味的なイベント処理
- モデルの状態に応じてボタンを押せなくするなどして、コマンドを実行の可否を切り替え
-
コマンドの実体
- コマンドの実体はICommandインターフェイス(System.Windows.Input名前空間)を実装したクラス
- CanExecuteメソッド: コマンドが実行可能な状態にあるかどうかを判定
- CanExecuteChangedイベント: コマンド実行の可否が変化したことを通知
-
データ・バインディングでコマンド処理
- ビューの外部から表示したいデータを与えることができる
- 外部から与えるデータは、後述する「ビューモデル(ViewModel)」というものになる場合が多い
-
利用手順
- ICommandインターフェイスを実装したクラスを作る(=コマンドの実装)
- 外部から与えるデータ(=ビューモデル)により、実装したコマンドをプロパティとして公開
- 公開したプロパティを、<Button>要素や<MenuItem>要素などのCommandプロパティにデータ・バインディング
-
ルーティング・コマンドを使ったコマンド処理
- ICommandインターフェイスの1実装として、RoutedCommandクラスというものがある
-
利用目的別
- ApplicationCommands: ファイルを開く、保存、ウィンドウを閉じるなど
- NavigationCommands: ブラウザの戻る、ホームを開く、前項/次項を開くなど
- ComponentCommands: リスト・アイテムやセルの移動、スクロール、フォーカスの移動など
- EditingCommands: テキスト編集用のコマンド
- MediaCommands: メディアの再生、停止、次のトラックへ移動など
-
コマンド・ソース
-
まずはコマンドの発生源(=コマンド・ソース)の設定が必要
- ICommandSourceインターフェイス(System.Windows.Input名前空間)があり、コマンド・ソースとなるUI要素を作る場合には、このインターフェイスを実装
- コントロール
- インプット・バインディング
- サブトピック 1キーボード・ショートカットや特殊なマウス・ジェスチャーなどの入力系のイベントを拾ってコマンド実行するため
- Executeメソッド: コマンドを実行
-
MVVMパターン
-
ビューモデルの役割
-
モデルをWPF向けにラッピング
- データ・ソースがINotifyPropertyChangedインターフェイス(System.ComponentModel名前空間)を実装している必要
- ユーザーの操作に応じて何らかの処理を行う場合には、ICommandインターフェイスを実装したコマンドを利用する
- ユーザーからの入力に不整合がないかなどのデータ検証を行うためには、IDataErrorInfoインターフェイス(System.ComponentModel名前空間)を実装
- すべてのモデルがこれらを実装しているわけではない
-
ビューから状態を分離
- 一般に、GUI部分(=ビュー)のテストは手間がかかりがち
-
ビューモデルの作成方法
-
データ・バインディングするための前提条件
- ビュー側のコンストラクタ
- this.DataContext = new ViewModel();
- XAMLコード
- <StackPanel>
<TextBox Text="{Binding X, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
ToolTip="{Binding RelativeSource ={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Button Content="OK" Command="{Binding OkCommand}" />
</StackPanel>
-
INotifyPropertyChangedインターフェイスの実装
- PropertyChangedイベントを持っていて、プロパティの値が変化した際には、このイベントを起こす
- using System.ComponentModel;
public class ViewModel : INotifyPropertyChanged
{
private int _x;
public int X
{
get { return _x; }
set
{
if (_x != value)
{
_x = value;
RaisePropertyChanged("X");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var d = PropertyChanged;
if (d != null)
d(this, new PropertyChangedEventArgs(propertyName));
}
}
-
ICommandインターフェイス(=コマンド)の実装
- ICommandインターフェイスを実装したクラスを作成
- コマンド1つ1つに対して毎度クラスを作成するのは手間
- ExecuteメソッドやCanExecuteメソッド内ではデリゲートを呼び出すだけというクラス
- using System;
using System.Windows.Input;
public class DelegateCommand : ICommand
{
public Action<object> ExecuteHandler { get; set; }
public Func<object, bool> CanExecuteHandler { get; set; }
#region ICommand メンバー
public bool CanExecute(object parameter)
{
var d = CanExecuteHandler;
return d == null ? true : d(parameter);
}
public void Execute(object parameter)
{
var d = ExecuteHandler;
if (d != null)
d(parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var d = CanExecuteChanged;
if (d != null)
d(this, null);
}
#endregion
}
-
ビューモデルのコード
- using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
public class ViewModel : INotifyPropertyChanged
{
private int _x;
public int X
{
get { return _x; }
set
{
if (_x != value)
{
_x = value;
RaisePropertyChanged("X");
((DelegateCommand)OkCommand).
RaiseCanExecuteChanged();
}
}
}
private void OkCommandExecute(object parameter)
{
MessageBox.Show("コマンドが実行されました。");
}
private bool OkCommandCanExecute(object parameter)
{
return X > 0;
}
private ICommand _okCommand;
public ICommand OkCommand
{
get
{
if (_okCommand == null)
_okCommand = new DelegateCommand
{
ExecuteHandler = OkCommandExecute,
CanExecuteHandler = OkCommandCanExecute,
};
return _okCommand;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var d = PropertyChanged;
if (d != null)
d(this, new PropertyChangedEventArgs(propertyName));
}
}
-
IDataErrorInfoインターフェイス(=データ検証)の実装
- ValidatesOnDataErrorsプロパティに「True」を設定で有効
-
UI要素の基礎とレイアウト用のパネル
- WPFのレイアウトの基礎概念
-
親子間の協調
- DesiredSizeプロパティ: 要素が希望するサイズ
- Measureメソッド: 親要素から利用可能な領域サイズを渡し、DesiredSizeプロパティの値を更新
- Arrangeメソッド: 実際に要素のサイズや配置を決定
-
希望サイズの決定
- Width, Height: 幅、高さを具体的に指定。ただし、親要素によって希望サイズが拒否される可能性もある
- MinWidth, MaxWidth, MinHeight, MaxHeight: 幅、高さの最小値、最大値を指定
- Margin: 要素の外側の余白幅を指定
- HorizontalAlignment, VerticalAlignment: 配置される空間よりも要素のサイズの方が小さい場合に、要素を上下左右のどちらに寄せるかを指定
- レイアウト決定後の値を取得したい場合には、ActualWidthプロパティとActualHeightプロパティを利用
-
標準提供されるパネル
-
StackPanel
- 子要素を縦または横一列に整列
-
WrapPanel
- 子要素を左から右に順番に並べ、幅を超えた分は右端で折り返すようにレイアウト
-
DockPanel
- パネルの上下左右に子要素を貼り付ける(ドッキング)ようにレイアウト
- ドッキングする位置はDockPanel.Dock属性(実体は添付プロパティ)によって行う
-
Canvas
- どうしても固定レイアウトが必要なときに利用
-
Grid
- 形式(列位置と行位置の指定)でレイアウトを行う
-
XAML
-
XAML構文
-
XML名前空間
-
xmlns:ns="clr-namespace:CLR名前空間名;assembly=アセンブリ名"
- 以後、<ns:クラス名>というようなXML要素を書くことで、対応するCLRオブジェクトのインスタンスを生成できる
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- XAML固有の機能を使うために併せて記述
-
属性構文(attribute syntax)
- XML属性は、CLRオブジェクトのプロパティもしくはイベントへの値の代入として解釈
-
XML属性値(文字列)から所望の型への変換
- CLRオブジェクトのプロパティにTypeConverter属性が付いているかどうか
- 付いている場合はこの属性情報に基づいた型コンバータを使って、ConvertFromメソッドにより値を生成
- プロパティの型に対してTypeConverter属性が付いているかどうか
- <Button Background="Blue" />
-
プロパティ要素構文(property element syntax)
- <型名.プロパティ名>という形のXML要素を記述
- <Button>
<Button.Background>Blue</Button.Background >
</Button>
-
マークアップ拡張(markup extension)
- Bindingマークアップ拡張がその代表例
- XML属性中に { } で囲った記述を書くと、対応するマークアップ拡張クラスのインスタンス生成と、そのProvideValueメソッド呼び出しを通してプロパティの値が設定
- <StackPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Width="100">
<Slider Name="slider" />
<TextBox Text="{Binding ElementName=slider, Path=Value}" />
</StackPanel>
-
コンテンツ・プロパティとXML要素の省略
- 可読性を向上させるため、XML要素の省略機構がいくつか存在
-
コンテンツ・プロパティ(content property)
- クラスに対してContentProperty属性が付いている場合、その情報に基づいてXML要素の省略が可能
- 親クラスであるContentControlクラスに[ContentProperty("Content")]属性が付いているため、<Button.Content>要素を省略できる。
- <Button>
<Button.Content>ボタン</Button.Content>
</Button>
- <Button>
ボタン
</Button>
-
コレクション型の省略
- プロパティの値がコレクションの場合、コレクションに相当するXML要素を省略することができる
- StackPanelクラスなどのChildrenプロパティ(=コンテンツ・プロパティに設定されている)の型はUIElementCollectionというコレクション・クラスであるため、省略可能である
- <StackPanel>
<StackPanel.Children>
<UIElementCollection>
<Button Content="ボタン1" />
<Button Content="ボタン2" />
</UIElementCollection>
</StackPanel.Children>
</StackPanel>
- <StackPanel>
<Button Content="ボタン1" />
<Button Content="ボタン2" />
</StackPanel>
-
添付プロパティ(attached property)
- 自分自身ではなく親要素で用いる値を保持するための機構
-
XML属性名に「型名.添付プロパティ名」を指定
- <Button Canvas.Left="10" />
- 「Canvas.SetLeft(button, 10)」というようなメソッド呼び出しになる
-
XML形式
- XML形式を使ううえでの注意点がそのままXAMLにも当てはまる
-
リソース
- 複数のUI要素で1つのオブジェクトを共有する
-
種類
- アセンブリ・リソース: アセンブリの中にバイナリ・ファイルを埋め込むためのリソース機構
- オブジェクト・リソース: 本稿で説明する、.NETオブジェクトを複数のUI要素から参照するためのリソース機構
-
リソース定義
- リソースは<Window>などの要素(正確には、FrameworkElement型を継承する要素)のResourcesプロパティ内に定義
-
外部リソースの取り込み
- ルート要素がResourceDictionary型のXAMLファイルを別途用意して、このXAMLファイルを取り込む形で利用することもできる
-
リソース利用
- StaticResourceマークアップ拡張もしくはDynamicResourceマークアップ拡張を利用
-
システム・リソース
- 「個人設定」などで設定されたシステム色やフォントなどの、システム・リソースも利用できる
-
スタイル
-
スタイルの定義
- <Setter>要素(=プロパティの値を設定するための要素)のリストとして定義
- <Style TargetType="Button">
<Setter Property="Background" Value="DarkSeaGreen" />
<Setter Property="Foreground" Value="LightPink" />
</Style>
-
スタイルの適用
- Styleプロパティに値を設定することで、定義したスタイルを適用できる
- 常は、本稿の前半で解説したリソースの中で定義して利用する
-
スタイルの継承
- <Style>要素は、BasedOnプロパティを指定することで、ほかのスタイルを継承できる
-
トリガー
-
特定の条件下でのみ働くスタイル
- 「マウス・カーソルが上に乗っているときや、クリックされたときだけスタイルを変えたい」
- <Style>要素のTriggersプロパティを設定
- <StackPanel Width="60">
<StackPanel.Resources>
<Style TargetType="TextBox">
<Setter Property="Background" Value="LightGray" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightBlue" />
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background" Value="LightPink" />
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBox Text="テキスト" />
</StackPanel>
-
種類
- Trigger: 特定のプロパティの値の変化をトリガーとして、Setterを用いてプロパティ値を変更
- MultiTrigger: Triggerを複数条件に対応させたもの。指定したすべての条件が満たされた場合にトリガーがかかる
- DataTrigger: スタイル適用先のUI要素だけでなく、データ・バインディングされたデータを監視
- MultiDataTrigger: DataTriggerの複数条件版
- EventTrigger: プロパティ値の変化ではなく、イベントの発生をトリガーとする
-
コントロール・テンプレート
- 機能を残したまま、外観だけを任意に変更可能
- ボタンなどのコントロールの基底となるControlクラスはTemplateというプロパティを持っており、ここにControlTemplateクラスのインスタンスを設定
-
コントロール・テンプレートのリソース化と自動適用
- コントロール・テンプレートもリソース化可能
- ContentPresenterとTemplateBinding
-
<StackPanel Width="80">
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"/>
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<Button Content="ボタン1" Background="LightBlue" Height="30" />
<Button Content="ボタン2" Background="LightPink" Height="30" />
</StackPanel>
- <ContentPresenter>要素: この要素が置かれた位置にコントロールの中身(=Contentプロパティに与えた値)が配置される
- TemplateBindingマークアップ拡張: コントロール・テンプレートの適用先のコントロールに与えられたプロパティ値を取得
-
ルーティング・コマンド
- 簡単にいうと、「ページ・アップ」や「端までスクロール」などといった操作が発生したことを親要素に伝達するための仕組み
-
WPF Themes(WPFテーマ)
- 自作ではなく、出来合いのものをどこかから探してきて利用する方が現実的