「WPF アクション」の版間の差分
(ページの作成:「==WPF アクション== [WPF][.Net][Silverlight][Universal Windows Platform][C#] {{amazon|4798114200}} *WPFにはアクションを扱うための共通の方法が3…」) |
|||
(同じ利用者による、間の2版が非表示) | |||
1行目: | 1行目: | ||
− | ==WPF アクション== | + | ==[[WPF アクション]]== |
− | [WPF][.Net][Silverlight][Universal Windows Platform][C | + | [[WPF]] | [[.Net]] | [[Silverlight]] | [[Universal Windows Platform]] | [[C Sharp]] | |
{{amazon|4798114200}} | {{amazon|4798114200}} | ||
− | * | + | *[[WPF]]にはアクションを扱うための共通の方法が3つある |
**イベント、コマンド、およびトリガ | **イベント、コマンド、およびトリガ | ||
25行目: | 25行目: | ||
b.Click += | b.Click += | ||
delegete { MessageBox.Show("Clicked!"); } | delegete { MessageBox.Show("Clicked!"); } | ||
− | + | <blockquote>このコードはうまく機能するように見えるが、クリックされているのはボタンそのものではなく、ボタンの表示を構成する要素</blockquote> | |
− | * | + | *[[WPF]]ではこれをシームレスに機能させるためにルーティングイベントという概念を導入 |
====ルーティングイベント==== | ====ルーティングイベント==== | ||
*コンテンツとしてボタンを含むように変更する | *コンテンツとしてボタンを含むように変更する | ||
34行目: | 34行目: | ||
delegete { MessageBox.Show("Clicked!"); } | delegete { MessageBox.Show("Clicked!"); } | ||
*コンテンツとしてボタンを含むように変更した場合、内側もしくは外側のいずれかのボタンをクリックするとイベントが発生する | *コンテンツとしてボタンを含むように変更した場合、内側もしくは外側のいずれかのボタンをクリックするとイベントが発生する | ||
− | + | <blockquote>イベントの合成は、イベントだけでなく、アクション処理のすべての側面に影響を与える</blockquote> | |
===疎結合=== | ===疎結合=== | ||
*Buttonのイベントを見ると、直接的なマウスイベント(MouseUp,MouseDownなど)とClickイベントの両方をサポートすることが分かる | *Buttonのイベントを見ると、直接的なマウスイベント(MouseUp,MouseDownなど)とClickイベントの両方をサポートすることが分かる | ||
− | * | + | *Clickはマウスイベントよりも上位に位置する抽象[[概念]](フォーカスがある状態でスペースキーや規定ボタンである場合Enterキー押下で発生する) |
*Clickはセマンティック(意味的)イベントで、マウスイベントは、フィジカル(物理的)イベント | *Clickはセマンティック(意味的)イベントで、マウスイベントは、フィジカル(物理的)イベント | ||
− | + | <blockquote>Clickイベントに対するコードを記述することは、特定の入力に縛られない、ボタンに縛られない(クリック可能なコンポーネントにのみ依存する)という利点がある</blockquote> | |
*ただし、イベント自体はメソッドの実装を特定のシグネチャにすることが要求される | *ただし、イベント自体はメソッドの実装を特定のシグネチャにすることが要求される | ||
=====Button.Clickのデリゲート===== | =====Button.Clickのデリゲート===== | ||
− | public delegate void | + | public delegate void [[R]]outedEventHandler(object sender,[[R]]outedEventArgs e); |
− | + | <blockquote>[[WPF]]の目標の一つは密接に結合されたフィジカルイベントから完全にセマンティックな通知までの幅広いアクションを許容すること</blockquote> | |
====コマンド==== | ====コマンド==== | ||
*疎結合を許容することでコントロールを劇的に変化させるテンプレートを記述することが可能となる | *疎結合を許容することでコントロールを劇的に変化させるテンプレートを記述することが可能となる | ||
=====Closeコマンドに結び付けられたボタンを追加する===== | =====Closeコマンドに結び付けられたボタンを追加する===== | ||
*ウィンドウを閉じるためのクロムを追加するウィンドウ用テンプレートを記述できる | *ウィンドウを閉じるためのクロムを追加するウィンドウ用テンプレートを記述できる | ||
− | + | <ControlTemplate TargetType="{x:Type Window}"> | |
− | + | <DocPanel> | |
− | + | <StatusBar DockPanel.Dock="Buttom"> | |
− | + | <StatusBarItem> | |
− | + | <Button Command="{x:Static ApplicationCommands.Close}"> | |
閉じる | 閉じる | ||
− | + | </Button> | |
− | + | </StatusBarItem> | |
− | + | </StatusBar> | |
− | + | </DocPanel> | |
− | + | </ControlTemplate> | |
*その後、ウィンドウにコマンドバインディングを追加することで、任意のコンポーネントがCloseコマンドを発行したときにウィンドウが閉じるようにすることが可能 | *その後、ウィンドウにコマンドバインディングを追加することで、任意のコンポーネントがCloseコマンドを発行したときにウィンドウが閉じるようにすることが可能 | ||
public MyWindow() { | public MyWindow() { | ||
67行目: | 67行目: | ||
); | ); | ||
} | } | ||
− | void CloseExecuted(object sender, | + | void CloseExecuted(object sender, Execute[[R]]outeEventArgs e) { |
this.Close(); | this.Close(); | ||
} | } | ||
− | * | + | *コマンドは[[WPF]]における最も疎結合なアクションモデルを表す |
*コマンドはアクションのソース(ボタンなど)とアクションのハンドラからの完全な抽象化を提供 | *コマンドはアクションのソース(ボタンなど)とアクションのハンドラからの完全な抽象化を提供 | ||
− | + | <blockquote>アプリケーションを破壊せずに全く異なるコントロールを使用するようにスタイルの変更が可能となる</blockquote> | |
===宣言型アクション=== | ===宣言型アクション=== | ||
*コマンドと疎結合が導入されたことで、ソフトウェアが意図を宣言するモデルへと向かうことがわかる | *コマンドと疎結合が導入されたことで、ソフトウェアが意図を宣言するモデルへと向かうことがわかる | ||
**「このボタンがクリックされたら、Window.Close()を呼び出す」から「このコマンドが実行されたらウィンドウを閉じます」へと向かっている | **「このボタンがクリックされたら、Window.Close()を呼び出す」から「このコマンドが実行されたらウィンドウを閉じます」へと向かっている | ||
− | * | + | *[[WPF]]の主要な基盤は宣言型プログラミングという考え方 |
− | * | + | *視覚要素、UIレイアウトに加え、アプリケーション[[ロジック]]の大半をマークアップで指定することが可能 |
− | + | <blockquote>宣言型[[ロジック]]は、宣言形式の周囲で、ユーザーにツールを提供することによりエクスペリエンスを向上させたり、より高度なサービスをシステムで提供したりできるという点できわめて有効</blockquote> | |
=====アクションの処理方法によって、宣言型プログラミングのサポートレベルはことなる===== | =====アクションの処理方法によって、宣言型プログラミングのサポートレベルはことなる===== | ||
{|class="wikitable" | {|class="wikitable" | ||
96行目: | 96行目: | ||
|} | |} | ||
==イベント== | ==イベント== | ||
− | *イベントは他の. | + | *イベントは他の[[.NET]]クラスライブラリと全く同じように機能 |
*各オブジェクトは一覧のイベントを公開し、イベントに対しデリゲートを使用してリスナを添付できる | *各オブジェクトは一覧のイベントを公開し、イベントに対しデリゲートを使用してリスナを添付できる | ||
===ルーティングイベント=== | ===ルーティングイベント=== | ||
− | * | + | *[[WPF]]はルーティングイベントに関連する追加の機能セットを備える |
{|class="wikitable" | {|class="wikitable" | ||
!ルーティングイベント | !ルーティングイベント | ||
105行目: | 105行目: | ||
|- | |- | ||
|直接イベント | |直接イベント | ||
− | |単一のソースで発生する単純なイベント。標準の. | + | |単一のソースで発生する単純なイベント。標準の[[.NET]]イベントとほぼ同じだが、WPFのルーティングイベントシステムに登録される点が異なる |
|- | |- | ||
|バブルイベント | |バブルイベント | ||
114行目: | 114行目: | ||
|- | |- | ||
|} | |} | ||
− | * | + | *バブルイベントとトンネルイベントは裏表の関係、通常、対になっており、トンネルバージョンには、Pre[[vi]]ewという接頭語がつく |
====要素の階層を作成してイベントをリッスンする==== | ====要素の階層を作成してイベントをリッスンする==== | ||
*グループボックスと複数のボタンを含むウィンドウ | *グループボックスと複数のボタンを含むウィンドウ | ||
− | + | <Window ... | |
− | + | Pre[[vi]]ewMouseRightbuttonDown="WindowPre[[vi]]ewRightButtonDown" | |
− | + | Mouse[[R]]ightButtonDown="Window[[R]]ightButtonDown" | |
− | + | > | |
− | + | <GroupBox | |
− | + | Pre[[vi]]ewMouseRightbuttonDown="GroupBoxPre[[vi]]ewRightButtonDown" | |
− | + | Mouse[[R]]ightButtonDown="GroupBox[[R]]ightButtonDown" | |
− | + | > | |
− | + | <StackPanel> | |
− | + | <Button>ボタン1</Button> | |
− | + | <Button | |
− | + | Pre[[vi]]ewMouseRightbuttonDown="ButtonTwoPre[[vi]]ewRightButtonDown" | |
− | + | Mouse[[R]]ightButtonDown="ButtonTwo[[R]]ightButtonDown" | |
− | + | > | |
ボタン2 | ボタン2 | ||
− | + | </Button> | |
− | + | </StackPanel> | |
− | + | </GroupBox> | |
− | + | </Window> | |
*イベントハンドラでイベント名を出力 | *イベントハンドラでイベント名を出力 | ||
− | void | + | void ButtonTwoPre[[vi]]ewRightButtonDown(object sender,MouseButtonEventArgs e) { |
− | Debug.WriteLine("ButtonTwo | + | Debug.WriteLine("ButtonTwo Pre[[vi]]ewRightButtonDown"); |
} | } | ||
− | void | + | void ButtonTwo[[R]]ightButtonDown(object sender,MouseButtonEventArgs e) { |
− | Debug.WriteLine("ButtonTwo | + | Debug.WriteLine("ButtonTwo [[R]]ightButtonDown"); |
} | } | ||
− | void | + | void GroupBoxPre[[vi]]ewRightButtonDown(object sender,MouseButtonEventArgs e) { |
− | Debug.WriteLine("GroupBox | + | Debug.WriteLine("GroupBox Pre[[vi]]ewRightButtonDown"); |
} | } | ||
− | void | + | void GroupBox[[R]]ightButtonDown(object sender,MouseButtonEventArgs e) { |
− | Debug.WriteLine("GroupBox | + | Debug.WriteLine("GroupBox [[R]]ightButtonDown"); |
} | } | ||
− | void | + | void WindowPre[[vi]]ewRightButtonDown(object sender,MouseButtonEventArgs e) { |
− | Debug.WriteLine("Window | + | Debug.WriteLine("Window Pre[[vi]]ewRightButtonDown"); |
} | } | ||
− | void | + | void Window[[R]]ightButtonDown(object sender,MouseButtonEventArgs e) { |
− | Debug.WriteLine("Window | + | Debug.WriteLine("Window [[R]]ightButtonDown"); |
} | } | ||
=====イベント順序===== | =====イベント順序===== | ||
− | #Window | + | #Window Pre[[vi]]ewMouseRightButtonDown |
− | #GroupBox | + | #GroupBox Pre[[vi]]ewMouseRightButtonDown |
− | #ButtonTwo | + | #ButtonTwo Pre[[vi]]ewMouseRightButtonDown |
− | #ButtonTwo | + | #ButtonTwo Mouse[[R]]ightButtonDown |
− | #GroupBox | + | #GroupBox Mouse[[R]]ightButtonDown |
− | #Window | + | #Window Mouse[[R]]ightButtonDown |
*コントロールの既定の動作は常にイベントのバブルバージョンで実装する必要がある | *コントロールの既定の動作は常にイベントのバブルバージョンで実装する必要がある | ||
− | * | + | *プレビューイベントを使用しないパターンにより、開発者がプレビューイベントを使用して[[ロジック]]にフックしたり、既定の動作をキャンセルすることが可能になる |
*イベントルーティングの任意のポイントでHandledプロパティを設定し、さらなるイベントハンドラが呼び出されるのを防ぐことができる | *イベントルーティングの任意のポイントでHandledプロパティを設定し、さらなるイベントハンドラが呼び出されるのを防ぐことができる | ||
*すべての要素がクリックされるのを防ぐ | *すべての要素がクリックされるのを防ぐ | ||
public Window() { | public Window() { | ||
− | this. | + | this.Pre[[vi]]ewMouseRightButtonDown += WindowPre[[vi]]ewRightButtonDown; |
} | } | ||
− | void | + | void WindowPre[[vi]]ewRightButtonDown(object sender, MouseButtonEventArgs e) { |
e.Handled = true; | e.Handled = true; | ||
} | } | ||
*Handleプロパティはすべてのルーティングイベントが共有するプロパティの一つ | *Handleプロパティはすべてのルーティングイベントが共有するプロパティの一つ | ||
− | public class | + | public class [[R]]outedEventArgs : EventArgs { |
public bool Handled { get; set; } | public bool Handled { get; set; } | ||
public object OriginalSource { get; } | public object OriginalSource { get; } | ||
− | public | + | public [[R]]outeEvent [[R]]outeEvent { get; set; } |
public object Source { get; set; } | public object Source { get; set; } | ||
} | } | ||
− | + | <blockquote>WPFはルーティングという概念をイベントに含めるように[[.NET]]イベントモデルを拡張し、それにより要素の合成を可能にする。ほかのアクション処理機構は、すべてこの基盤イベントルーティングモデル状に構築される</blockquote> | |
==コマンド== | ==コマンド== | ||
− | * | + | *[[WPF]]のほとんどのイベントは、各コントロールの実装の詳細に結びついている |
*イベントはコードの特定の部分をコントロールからの通知に結び付ける場合には便利だが、もっと抽象的に処理を行いたい場合もある | *イベントはコードの特定の部分をコントロールからの通知に結び付ける場合には便利だが、もっと抽象的に処理を行いたい場合もある | ||
===プログラムを終了する機能=== | ===プログラムを終了する機能=== | ||
====イベントでの実装例==== | ====イベントでの実装例==== | ||
*まず必要なのは、プログラムを終了するためのメニュー | *まず必要なのは、プログラムを終了するためのメニュー | ||
− | + | <[[Menu]]Item Hader="ファイル(_F)"> | |
− | + | <MenuItem [[Header]]="終了(_X)" Click="ExitClicked"/> | |
− | + | </[[Menu]]Item> | |
*次に分離コードでイベントハンドラを実装 | *次に分離コードでイベントハンドラを実装 | ||
− | void ExitClicked(object sender, | + | void ExitClicked(object sender, [[R]]outeEventArgs e) { |
Application.Current.Shutdown(); | Application.Current.Shutdown(); | ||
} | } | ||
− | * | + | *この方法はうまく機能するが、アプリケーションを終了するハイパー[[リンク]]も加えてみる |
− | + | <TextBlock> | |
− | + | <Hyperlink Click="ExitClicked">終了</Hyperlink> | |
− | + | </TextBlock> | |
*ここでは様々なことが仮定されている | *ここでは様々なことが仮定されている | ||
**シグネチャが、Hyperlink.Clicked と互換性があること | **シグネチャが、Hyperlink.Clicked と互換性があること | ||
213行目: | 213行目: | ||
*コマンドの実装を定義する | *コマンドの実装を定義する | ||
*コマンドのトリガを作成する | *コマンドのトリガを作成する | ||
− | + | <blockquote>WPFのコマンドシステムの基盤になるのは、[http://msdn.microsoft.com/ja-jp/library/system.windows.input.icommand(v=vs.110).aspx ICommand]インターフェース</blockquote> | |
public interface ICommand { | public interface ICommand { | ||
event EventHandler CanExecuteChanged; | event EventHandler CanExecuteChanged; | ||
220行目: | 220行目: | ||
} | } | ||
*CanExecute はコマンドが利用可能かどうか | *CanExecute はコマンドが利用可能かどうか | ||
− | ** | + | **使用可能の[[概念]]を共有することで同じコマンドに結び付けられている複数のコントロールが一貫した共有可能状態を持つことが可能となる |
*Execute コマンドの実行をトリガする | *Execute コマンドの実行をトリガする | ||
====対応するコマンドの実装==== | ====対応するコマンドの実装==== | ||
− | protected | + | protected [[vi]]rtual void OnClick(RoutedEventArgs e) { |
if (Command != null && Command.CanExecute(CommandParameter)) { | if (Command != null && Command.CanExecute(CommandParameter)) { | ||
Command.Execute(CommandParmeter); | Command.Execute(CommandParmeter); | ||
238行目: | 238行目: | ||
} | } | ||
====マークアップとバインド==== | ====マークアップとバインド==== | ||
− | + | <[[Menu]]Item Hader="ファイル(_F)"> | |
− | + | <MenuItem [[Header]]="終了(_X)"> | |
− | + | <[[Menu]]Item.Command> | |
− | + | <l:Exit /> | |
− | + | </[[Menu]]Item.Command> | |
− | + | </[[Menu]]Item> | |
− | + | </[[Menu]]Item> | |
: | : | ||
− | + | <TextBlock> | |
− | + | <Hyperlink>終了 | |
− | + | <Hyperlink.Command><l:Exit /></Hyperlink.Command> | |
− | + | </Hyperlink> | |
− | + | </TextBlock> | |
====機能セットの発行==== | ====機能セットの発行==== | ||
*コマンドは複数の場所から利用されることが一般的 | *コマンドは複数の場所から利用されることが一般的 | ||
260行目: | 260行目: | ||
*ICommand をフィールド型とすることで、Exitの実装を完全にプライベートにできる利点 | *ICommand をフィールド型とすることで、Exitの実装を完全にプライベートにできる利点 | ||
*これでExitをプライベートクラスとしてマークし、静的フィールドにバインドするようにマークアップを変更できる | *これでExitをプライベートクラスとしてマークし、静的フィールドにバインドするようにマークアップを変更できる | ||
− | + | <[[Menu]]Item Hader="ファイル(_F)"> | |
− | + | <MenuItem [[Header]]="終了(_X)" | |
− | Command="{x:Static l:Window1.ExitCommand}" | + | Command="{x:Static l:Window1.ExitCommand}"> |
− | + | </[[Menu]]Item> | |
− | + | <blockquote>こうすることで、ウィンドウでコマンドを公開することで、機能セットを発行できる。</blockquote> | |
====変更==== | ====変更==== | ||
*現在Exitはどこからでも呼び出すことができ、アプリケーションを終了する | *現在Exitはどこからでも呼び出すことができ、アプリケーションを終了する | ||
271行目: | 271行目: | ||
*コマンドで新しいイベントを定義し、イベントルーティングシステムを利用してコンポーネントに通知することができる | *コマンドで新しいイベントを定義し、イベントルーティングシステムを利用してコンポーネントに通知することができる | ||
class Exit : ICommand { | class Exit : ICommand { | ||
− | public static readonly | + | public static readonly [[R]]outeEvent ExecuteEvent = |
− | EventManager. | + | EventManager.[[R]]egister[[R]]outeEvent( |
"Execute", | "Execute", | ||
− | + | [[R]]outingStrategy.Bubble, | |
− | typeof( | + | typeof([[R]]outedEventHandler), |
typeof(Exit) | typeof(Exit) | ||
); | ); | ||
284行目: | 284行目: | ||
*現在の要素を検索するようにExecuteを変更し、適切なイベントを発行する | *現在の要素を検索するようにExecuteを変更し、適切なイベントを発行する | ||
public void Execute(object parameter) { | public void Execute(object parameter) { | ||
− | + | [[R]]outedEventArgs e = | |
− | new | + | new [[R]]outedEventArgs(Exit.ExecuteEvent, Keyboard.FocusedElement); |
− | Keyboard.FocusedElement. | + | Keyboard.FocusedElement.[[R]]aiseEvent(e); |
} | } | ||
− | + | <blockquote>ここからコマンドバインディングという[[概念]]が生まれる</blockquote> | |
====コマンドバインディング==== | ====コマンドバインディング==== | ||
*コマンドバインディングとは、コマンドの実装を、そのコマンドの身元から切り離す機能 | *コマンドバインディングとは、コマンドの実装を、そのコマンドの身元から切り離す機能 | ||
299行目: | 299行目: | ||
AddHandler(Exit.ExecuteEvent, ExitExecuted); | AddHandler(Exit.ExecuteEvent, ExitExecuted); | ||
} | } | ||
− | void ExitExecuted(object sender, | + | void ExitExecuted(object sender, [[R]]outedEventArgs e) { |
this.Close(); | this.Close(); | ||
} | } | ||
} | } | ||
− | + | <blockquote>このケースでは、Exitコマンドは要素ツリーでExecuteイベントを発生させ、Windowがそのイベントをリッスンして対応できるようにする</blockquote> | |
#メニュー項目がクリックされる | #メニュー項目がクリックされる | ||
− | # | + | #[[Menu]]ItemがコマンドでExecuteを呼び出す |
− | #Exitの実装がフォーカスが設定されているオブジェクト( | + | #Exitの実装がフォーカスが設定されているオブジェクト(この場合[[Menu]]Item)で、Exit.Executeイベントを発生させる |
#そのイベントが要素ツリーをバブルアップ | #そのイベントが要素ツリーをバブルアップ | ||
#ウィンドウがExit.Execute(ウィンドウを閉じる)を実行する | #ウィンドウがExit.Execute(ウィンドウを閉じる)を実行する | ||
− | ==== | + | ====[[R]]outedCommand==== |
− | * | + | *この方法で、入力バインディング、パラメータ、および[[その他]]の機能をサポートするように基盤のICommandモデルを拡張することも可能 |
− | + | <blockquote>ただしフレームワークには、これらのほとんどを処理する組み込みのユーティリティクラス、[[R]]outedCommand がすでに含まれている</blockquote> | |
*ルーティングコマンドは、コマンドの実装をコマンドの身元から完全に分離する | *ルーティングコマンドは、コマンドの実装をコマンドの身元から完全に分離する | ||
*コマンドの定義は静的つまり、コマンドの定義は、コマンドの身元を提供する一種のトークンに過ぎない | *コマンドの定義は静的つまり、コマンドの定義は、コマンドの身元を提供する一種のトークンに過ぎない | ||
public partial class Window1 : Window { | public partial class Window1 : Window { | ||
public static readonly ICommand ExitCommand = | public static readonly ICommand ExitCommand = | ||
− | new | + | new [[R]]outedCommand("Exit", typeof(Window1)); |
: | : | ||
} | } | ||
328行目: | 328行目: | ||
CommandBindings.Add(new CommandBinding(ExitCommand, ExitExecuted)); | CommandBindings.Add(new CommandBinding(ExitCommand, ExitExecuted)); | ||
} | } | ||
− | void ExitExecuted(object sender, | + | void ExitExecuted(object sender, Executed[[R]]outedEventArgs e) { |
this.Close(); | this.Close(); | ||
} | } | ||
− | * | + | *コマンドバインディングを使用すると、コマンドを使用可能にするか否かを判断するための[[ロジック]]を使用できる |
====InputBindingsプロパティ==== | ====InputBindingsプロパティ==== | ||
*InputBindingsプロパティを使用して入力ジェスチャをマップできる | *InputBindingsプロパティを使用して入力ジェスチャをマップできる | ||
− | + | <Window x:Class="..." | |
− | : | + | :> |
− | + | <Window.InputBindings> | |
− | + | <KeyBinding Key="A" Modifires="Contorol" | |
− | Command="{x:Static l:Window1.ExitCommand}" / | + | Command="{x:Static l:Window1.ExitCommand}" /> |
− | + | </Window.InputBindings> | |
− | + | </Window> | |
====セキュアコマンド==== | ====セキュアコマンド==== | ||
*切り取り、コピー、貼り付けなどの一部コマンドはセキュリティに影響を与える | *切り取り、コピー、貼り付けなどの一部コマンドはセキュリティに影響を与える | ||
− | * | + | *システムがこれらの操作をユーザーによって要求された場合のみに実行することを保証するために、[[R]]outedCommandは、ユーザーによって開始されたかどうかを追跡可能 |
− | + | <blockquote>一般にアプリケーション[[ロジック]]はイベントハンドラではなく、コマンドの観点で実装することが推奨される。イベントハンドラが必要なケースの多くはトリガーを使用したほうがうまく処理できる。</blockquote> | |
===コマンドとデータバインディング=== | ===コマンドとデータバインディング=== | ||
*コマンドを使用する場合のもっとも強力な機能はデータバインディングの統合 | *コマンドを使用する場合のもっとも強力な機能はデータバインディングの統合 | ||
*CommandとCommandParameterはいずれも要素のプロパティのため、データをバインドすることができる | *CommandとCommandParameterはいずれも要素のプロパティのため、データをバインドすることができる | ||
− | * | + | *コマンドを使用することでデータ駆動型の[[ロジック]]も可能となる |
====ドライブ内のファイルをリストするアプリケーション例==== | ====ドライブ内のファイルをリストするアプリケーション例==== | ||
− | + | <Window x:Class="..." | |
− | : | + | :> |
− | + | <ListBox Margin="2" Name="_files"> | |
− | + | <ListBox.ItemTemplate> | |
− | + | <DataTemplate> | |
− | + | <TextBlock Text="{Binding Path=Name}" /> | |
− | + | </DataTemplate> | |
− | + | </ListBox.ItemTemplate> | |
− | + | </ListBox> | |
− | + | </Window> | |
*分離コードで、ItemSourceプロパティにファイルのリストを代入 | *分離コードで、ItemSourceプロパティにファイルのリストを代入 | ||
public partial class DataAndCommands : Window { | public partial class DataAndCommands : Window { |
2020年2月16日 (日) 04:34時点における最新版
WPF アクション
WPF | .Net | Silverlight | Universal Windows Platform | C Sharp |
- WPFにはアクションを扱うための共通の方法が3つある
- イベント、コマンド、およびトリガ
アクションの原則
- 3つのメカニズム(イベント、コマンド、トリガ)すべてに当てはまる原則
3つの原則に基づいて機能
- 要素の合成
- 表示ツリーを構築する方法にアクションを対応させる
- 疎結合
- イベントのソースとイベントを処理するコードが密接に結合されていると問題が生じる
- 宣言型アクション
- 宣言型のプログラミングをシステムのすべての側面で実現する必要がある
要素の合成
- ボタンは実際には複数の要素から構成されるため、イベント処理で問題が生じる
- コントロールモデルの原則は、要素の合成、リッチコンテンツ、簡単なプログラミングモデル
簡単なプル蔵民具モデルを実現するには
- クリックイベントをリッスンするコードを開発者に親しみやすいものにする必要がある
- Clickイベントにハンドラを添付するだけのものであることが望まれる
Button b = new Button(); b.Content = "Click"; b.Click += delegete { MessageBox.Show("Clicked!"); }
<blockquote>このコードはうまく機能するように見えるが、クリックされているのはボタンそのものではなく、ボタンの表示を構成する要素</blockquote>
- WPFではこれをシームレスに機能させるためにルーティングイベントという概念を導入
ルーティングイベント
- コンテンツとしてボタンを含むように変更する
Button b = new Button(); b.Content = new Button(); b.Click += delegete { MessageBox.Show("Clicked!"); }
- コンテンツとしてボタンを含むように変更した場合、内側もしくは外側のいずれかのボタンをクリックするとイベントが発生する
<blockquote>イベントの合成は、イベントだけでなく、アクション処理のすべての側面に影響を与える</blockquote>
疎結合
- Buttonのイベントを見ると、直接的なマウスイベント(MouseUp,MouseDownなど)とClickイベントの両方をサポートすることが分かる
- Clickはマウスイベントよりも上位に位置する抽象概念(フォーカスがある状態でスペースキーや規定ボタンである場合Enterキー押下で発生する)
- Clickはセマンティック(意味的)イベントで、マウスイベントは、フィジカル(物理的)イベント
<blockquote>Clickイベントに対するコードを記述することは、特定の入力に縛られない、ボタンに縛られない(クリック可能なコンポーネントにのみ依存する)という利点がある</blockquote>
- ただし、イベント自体はメソッドの実装を特定のシグネチャにすることが要求される
Button.Clickのデリゲート
public delegate void RoutedEventHandler(object sender,RoutedEventArgs e);
<blockquote>WPFの目標の一つは密接に結合されたフィジカルイベントから完全にセマンティックな通知までの幅広いアクションを許容すること</blockquote>
コマンド
- 疎結合を許容することでコントロールを劇的に変化させるテンプレートを記述することが可能となる
Closeコマンドに結び付けられたボタンを追加する
- ウィンドウを閉じるためのクロムを追加するウィンドウ用テンプレートを記述できる
<ControlTemplate TargetType="{x:Type Window}"> <DocPanel> <StatusBar DockPanel.Dock="Buttom"> <StatusBarItem> <Button Command="{x:Static ApplicationCommands.Close}"> 閉じる </Button> </StatusBarItem> </StatusBar> </DocPanel> </ControlTemplate>
- その後、ウィンドウにコマンドバインディングを追加することで、任意のコンポーネントがCloseコマンドを発行したときにウィンドウが閉じるようにすることが可能
public MyWindow() { InitializeComponent(); CommandBindings.Add( new CommandBinding(ApplicationCommands.Close, CloseExecuted) ); } void CloseExecuted(object sender, ExecuteRouteEventArgs e) { this.Close(); }
- コマンドはWPFにおける最も疎結合なアクションモデルを表す
- コマンドはアクションのソース(ボタンなど)とアクションのハンドラからの完全な抽象化を提供
<blockquote>アプリケーションを破壊せずに全く異なるコントロールを使用するようにスタイルの変更が可能となる</blockquote>
宣言型アクション
- コマンドと疎結合が導入されたことで、ソフトウェアが意図を宣言するモデルへと向かうことがわかる
- 「このボタンがクリックされたら、Window.Close()を呼び出す」から「このコマンドが実行されたらウィンドウを閉じます」へと向かっている
- WPFの主要な基盤は宣言型プログラミングという考え方
- 視覚要素、UIレイアウトに加え、アプリケーションロジックの大半をマークアップで指定することが可能
<blockquote>宣言型ロジックは、宣言形式の周囲で、ユーザーにツールを提供することによりエクスペリエンスを向上させたり、より高度なサービスをシステムで提供したりできるという点できわめて有効</blockquote>
アクションの処理方法によって、宣言型プログラミングのサポートレベルはことなる
アクション | 内容 |
---|---|
イベント | ターゲット関数をマークアップで宣言できるが、ハンドラはコードで実装する必要がある |
コマンド | 宣言型の使用のために特別に設計されており、アクションのソースと利用者の間に最良の抽象化を提供 |
トリガ | 最もリッチな宣言型サポートを提供するが、拡張性に欠けるため複雑なタスクで使用するのは困難 |
イベント
- イベントは他の.NETクラスライブラリと全く同じように機能
- 各オブジェクトは一覧のイベントを公開し、イベントに対しデリゲートを使用してリスナを添付できる
ルーティングイベント
- WPFはルーティングイベントに関連する追加の機能セットを備える
ルーティングイベント | 内容 |
---|---|
直接イベント | 単一のソースで発生する単純なイベント。標準の.NETイベントとほぼ同じだが、WPFのルーティングイベントシステムに登録される点が異なる |
バブルイベント | 要素ツリーのターゲットからルートに向かって移動 |
トンネルイベント | 要素ツリーのルートからターゲットに向かって移動 |
- バブルイベントとトンネルイベントは裏表の関係、通常、対になっており、トンネルバージョンには、Previewという接頭語がつく
要素の階層を作成してイベントをリッスンする
- グループボックスと複数のボタンを含むウィンドウ
<Window ... PreviewMouseRightbuttonDown="WindowPreviewRightButtonDown" MouseRightButtonDown="WindowRightButtonDown" > <GroupBox PreviewMouseRightbuttonDown="GroupBoxPreviewRightButtonDown" MouseRightButtonDown="GroupBoxRightButtonDown" > <StackPanel> <Button>ボタン1</Button> <Button PreviewMouseRightbuttonDown="ButtonTwoPreviewRightButtonDown" MouseRightButtonDown="ButtonTwoRightButtonDown" > ボタン2 </Button> </StackPanel> </GroupBox> </Window>
- イベントハンドラでイベント名を出力
void ButtonTwoPreviewRightButtonDown(object sender,MouseButtonEventArgs e) { Debug.WriteLine("ButtonTwo PreviewRightButtonDown"); } void ButtonTwoRightButtonDown(object sender,MouseButtonEventArgs e) { Debug.WriteLine("ButtonTwo RightButtonDown"); } void GroupBoxPreviewRightButtonDown(object sender,MouseButtonEventArgs e) { Debug.WriteLine("GroupBox PreviewRightButtonDown"); } void GroupBoxRightButtonDown(object sender,MouseButtonEventArgs e) { Debug.WriteLine("GroupBox RightButtonDown"); } void WindowPreviewRightButtonDown(object sender,MouseButtonEventArgs e) { Debug.WriteLine("Window PreviewRightButtonDown"); } void WindowRightButtonDown(object sender,MouseButtonEventArgs e) { Debug.WriteLine("Window RightButtonDown"); }
イベント順序
- Window PreviewMouseRightButtonDown
- GroupBox PreviewMouseRightButtonDown
- ButtonTwo PreviewMouseRightButtonDown
- ButtonTwo MouseRightButtonDown
- GroupBox MouseRightButtonDown
- Window MouseRightButtonDown
- コントロールの既定の動作は常にイベントのバブルバージョンで実装する必要がある
- プレビューイベントを使用しないパターンにより、開発者がプレビューイベントを使用してロジックにフックしたり、既定の動作をキャンセルすることが可能になる
- イベントルーティングの任意のポイントでHandledプロパティを設定し、さらなるイベントハンドラが呼び出されるのを防ぐことができる
- すべての要素がクリックされるのを防ぐ
public Window() { this.PreviewMouseRightButtonDown += WindowPreviewRightButtonDown; } void WindowPreviewRightButtonDown(object sender, MouseButtonEventArgs e) { e.Handled = true; }
- Handleプロパティはすべてのルーティングイベントが共有するプロパティの一つ
public class RoutedEventArgs : EventArgs { public bool Handled { get; set; } public object OriginalSource { get; } public RouteEvent RouteEvent { get; set; } public object Source { get; set; } }
<blockquote>WPFはルーティングという概念をイベントに含めるように.NETイベントモデルを拡張し、それにより要素の合成を可能にする。ほかのアクション処理機構は、すべてこの基盤イベントルーティングモデル状に構築される</blockquote>
コマンド
- WPFのほとんどのイベントは、各コントロールの実装の詳細に結びついている
- イベントはコードの特定の部分をコントロールからの通知に結び付ける場合には便利だが、もっと抽象的に処理を行いたい場合もある
プログラムを終了する機能
イベントでの実装例
- まず必要なのは、プログラムを終了するためのメニュー
<MenuItem Hader="ファイル(_F)"> <MenuItem Header="終了(_X)" Click="ExitClicked"/> </MenuItem>
- 次に分離コードでイベントハンドラを実装
void ExitClicked(object sender, RouteEventArgs e) { Application.Current.Shutdown(); }
- この方法はうまく機能するが、アプリケーションを終了するハイパーリンクも加えてみる
<TextBlock> <Hyperlink Click="ExitClicked">終了</Hyperlink> </TextBlock>
- ここでは様々なことが仮定されている
- シグネチャが、Hyperlink.Clicked と互換性があること
- 実装がアプリケーションを単純に終了すること
コマンド
- これらの問題をコマンドが解決する
- コマンドを使用すると、目的のアクションを示す単一の名前を提供できる
コマンドを使用するには
- コマンドを定義する
- コマンドの実装を定義する
- コマンドのトリガを作成する
<blockquote>WPFのコマンドシステムの基盤になるのは、ICommandインターフェース</blockquote>
public interface ICommand { event EventHandler CanExecuteChanged; bool CanExecute(object parameter); void Execute(object paramter); }
- CanExecute はコマンドが利用可能かどうか
- 使用可能の概念を共有することで同じコマンドに結び付けられている複数のコントロールが一貫した共有可能状態を持つことが可能となる
- Execute コマンドの実行をトリガする
対応するコマンドの実装
protected virtual void OnClick(RoutedEventArgs e) { if (Command != null && Command.CanExecute(CommandParameter)) { Command.Execute(CommandParmeter); } }
新しいコマンドの定義
public class Exit : ICommand { public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { Application.Current.Shutdown(); } }
マークアップとバインド
<MenuItem Hader="ファイル(_F)"> <MenuItem Header="終了(_X)"> <MenuItem.Command> <l:Exit /> </MenuItem.Command> </MenuItem> </MenuItem> : <TextBlock> <Hyperlink>終了 <Hyperlink.Command><l:Exit /></Hyperlink.Command> </Hyperlink> </TextBlock>
機能セットの発行
- コマンドは複数の場所から利用されることが一般的
- 通常はコマンドインスタンスを使用して制的フィールドを作成する
public partial class Window1 : Window { public static readonly ICommand ExitCommand = new Exit(); : }
- ICommand をフィールド型とすることで、Exitの実装を完全にプライベートにできる利点
- これでExitをプライベートクラスとしてマークし、静的フィールドにバインドするようにマークアップを変更できる
<MenuItem Hader="ファイル(_F)"> <MenuItem Header="終了(_X)" Command="{x:Static l:Window1.ExitCommand}"> </MenuItem>
<blockquote>こうすることで、ウィンドウでコマンドを公開することで、機能セットを発行できる。</blockquote>
変更
- 現在Exitはどこからでも呼び出すことができ、アプリケーションを終了する
- これを現在のウィンドウを閉じることになるように変更したいとした場合、実装を定義から分離する必要がある
分離のもっとも簡単な方法はイベントを利用する
- コマンドで新しいイベントを定義し、イベントルーティングシステムを利用してコンポーネントに通知することができる
class Exit : ICommand { public static readonly RouteEvent ExecuteEvent = EventManager.RegisterRouteEvent( "Execute", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Exit) ); : }
- このイベントはバブル方式のため、イベントソースからバブルアップする
イベントを発行するには
- 現在の要素を検索するようにExecuteを変更し、適切なイベントを発行する
public void Execute(object parameter) { RoutedEventArgs e = new RoutedEventArgs(Exit.ExecuteEvent, Keyboard.FocusedElement); Keyboard.FocusedElement.RaiseEvent(e); }
<blockquote>ここからコマンドバインディングという概念が生まれる</blockquote>
コマンドバインディング
- コマンドバインディングとは、コマンドの実装を、そのコマンドの身元から切り離す機能
Window1の実装に戻り、コマンドの実装を追加する
public Window1 : Window { : public Window1() { InitializeComponent(); AddHandler(Exit.ExecuteEvent, ExitExecuted); } void ExitExecuted(object sender, RoutedEventArgs e) { this.Close(); } }
<blockquote>このケースでは、Exitコマンドは要素ツリーでExecuteイベントを発生させ、Windowがそのイベントをリッスンして対応できるようにする</blockquote>
- メニュー項目がクリックされる
- MenuItemがコマンドでExecuteを呼び出す
- Exitの実装がフォーカスが設定されているオブジェクト(この場合MenuItem)で、Exit.Executeイベントを発生させる
- そのイベントが要素ツリーをバブルアップ
- ウィンドウがExit.Execute(ウィンドウを閉じる)を実行する
RoutedCommand
- この方法で、入力バインディング、パラメータ、およびその他の機能をサポートするように基盤のICommandモデルを拡張することも可能
<blockquote>ただしフレームワークには、これらのほとんどを処理する組み込みのユーティリティクラス、RoutedCommand がすでに含まれている</blockquote>
- ルーティングコマンドは、コマンドの実装をコマンドの身元から完全に分離する
- コマンドの定義は静的つまり、コマンドの定義は、コマンドの身元を提供する一種のトークンに過ぎない
public partial class Window1 : Window { public static readonly ICommand ExitCommand = new RoutedCommand("Exit", typeof(Window1)); : }
処理コードをバインド
- コマンドが実行されたときに何を行うかは、処理コードをコマンドにバインドする必要がある
- ルーティングコマンドはイベントと同じようにバブルアップする
- ルートウィンドウにコマンドバインディングを追加し、すべてのコマンドを確認することができる
- コマンドバインディングはリッスンするコマンドと、トリガされたときに実行するコードを受け取る
public Window1() { InitializeComponent(); CommandBindings.Add(new CommandBinding(ExitCommand, ExitExecuted)); } void ExitExecuted(object sender, ExecutedRoutedEventArgs e) { this.Close(); }
- コマンドバインディングを使用すると、コマンドを使用可能にするか否かを判断するためのロジックを使用できる
InputBindingsプロパティ
- InputBindingsプロパティを使用して入力ジェスチャをマップできる
<Window x:Class="..." :> <Window.InputBindings> <KeyBinding Key="A" Modifires="Contorol" Command="{x:Static l:Window1.ExitCommand}" /> </Window.InputBindings> </Window>
セキュアコマンド
- 切り取り、コピー、貼り付けなどの一部コマンドはセキュリティに影響を与える
- システムがこれらの操作をユーザーによって要求された場合のみに実行することを保証するために、RoutedCommandは、ユーザーによって開始されたかどうかを追跡可能
<blockquote>一般にアプリケーションロジックはイベントハンドラではなく、コマンドの観点で実装することが推奨される。イベントハンドラが必要なケースの多くはトリガーを使用したほうがうまく処理できる。</blockquote>
コマンドとデータバインディング
- コマンドを使用する場合のもっとも強力な機能はデータバインディングの統合
- CommandとCommandParameterはいずれも要素のプロパティのため、データをバインドすることができる
- コマンドを使用することでデータ駆動型のロジックも可能となる
ドライブ内のファイルをリストするアプリケーション例
<Window x:Class="..." :> <ListBox Margin="2" Name="_files"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Path=Name}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Window>
- 分離コードで、ItemSourceプロパティにファイルのリストを代入
public partial class DataAndCommands : Window { public DataAndCommands() { InitializeComponent(); FileInfo[] fileList = new DirectoryInfo("c:\\").GetFiles("*.*"); _files.ItemSource = fileList; } }
© 2006 矢木浩人