えむナウさんがまたやってくれました
添付ビヘイビアーの利点は View のコードビハインドを書かなくてすむ、一度作成すると再利用が容易、使用するのにXAMLだけで書ける、
Blend でサポートされている、といろいろある。
MVVM パターンで VM から VIEW を操作するには、添付ビヘイビアーを使用することも推奨する。
MVVM パターンで VM から VIEW を操作したい その2
この発想も無かったです。
添付ビヘイビアはひさしく使ってないから、頭の片隅に追いやっていました。。。
私は文章だけだと理解できないので実践してみます
Expression Blend は持っていないので、Visual C# 2010 Express Edition で。
添付ビヘイビアはコードで書きます。
前回の依存関係プロパティを使ったサンプルを、添付ビヘイビアを使ったものに書き変えていきます。
添付ビヘイビアを実装します
using System.Windows; using System.Windows.Input; using System.Windows.Controls; namespace MvvmSample { public static class ViewBehavior { private static readonly DependencyProperty HasShowMessageCommandProperty = DependencyProperty.RegisterAttached( "HasShowMessageCommand", typeof(bool), typeof(ViewBehavior), new PropertyMetadata(false)); public static readonly DependencyProperty ShowMessageCommandProperty = DependencyProperty.RegisterAttached( "ShowMessageCommand", typeof(ICommand), typeof(ViewBehavior), new PropertyMetadata(null, ShowMessageCommandChanged, CoerceShowMessageCommand)); public static ICommand GetShowMessageCommand(DependencyObject textBox) { return textBox.GetValue(ShowMessageCommandProperty) as ICommand; } public static void SetShowMessageCommand(DependencyObject textBox, ICommand command) { textBox.SetValue(ShowMessageCommandProperty, command); } private static void ShowMessageCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { // 今のところ何もしない } private static object CoerceShowMessageCommand(DependencyObject sender, object value) { var hasCommand = (bool)sender.GetValue(HasShowMessageCommandProperty); if (hasCommand == false) { var command = new DelegateCommand( ShowMessageCommandExecute, CanShowMessageCommandExecute); sender.SetValue(HasShowMessageCommandProperty, true); sender.SetValue(ShowMessageCommandProperty, command); } return GetShowMessageCommand(sender); } private static void ShowMessageCommandExecute(object param) { MessageBox.Show(param.ToString()); } private static bool CanShowMessageCommandExecute(object param) { return param != null; } } }
OneWayToSource でバインドするのが前提なので、ViewModel に渡すための Command を内部で作成しています。
CoerceShowMessageCommand 内で GetShowMessageCommand(sender) を使って Command が設定済みかどうか判定するとスタックオーバーフローになってしまうため、HasShowMessageCommandProperty を使って回避しています。
ViewModel を実装します
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Windows.Input; namespace MvvmSample { public class GreetingViewModel : INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { if (_name != value) { _name = value; OnPropertyChanged("Name"); } } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { var h = PropertyChanged; if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); } } // あいさつをメッセージボックスで表示するコマンド private ICommand _greetCommand; public ICommand GreetCommand { get { return _greetCommand = _greetCommand ?? new DelegateCommand(_ => { // View の機能を呼び出す if (ShowMessageCommand.CanExecute(Name)) { ShowMessageCommand.Execute(string.Format("Hello, {0}.", Name)); } }); } } // View が公開するコマンドがこのプロパティにバインドされる public ICommand ShowMessageCommand { get; set; } } }
依存関係プロパティを利用した前回のサンプルと、まったく同じです。
View を実装します
<UserControl x:Class="MvvmSample.GreetingView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <StackPanel> <TextBox Text="{Binding Path=Name}"/> <Button Content="Greet" Command="{Binding Path=GreetCommand}"/> </StackPanel> </UserControl>
XAML は前回と同じです。
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace MvvmSample { public partial class GreetingView : UserControl { public GreetingView() { InitializeComponent(); } } }
ViewModel の Command を View にバインドするための依存関係プロパティは不要なので、コードビハインドは何も追加していません。
添付ビヘイビアを使ってCommandをバインドします
<Window x:Class="MvvmSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MvvmSample" Title="MainWindow" Height="350" Width="525"> <Grid> <!--添付ビヘイビアを使ってViewModelのCommandをバインド--> <local:GreetingView local:ViewBehavior.ShowMessageCommand="{Binding Path=ShowMessageCommand, Mode=OneWayToSource}"> <local:GreetingView.DataContext> <local:GreetingViewModel/> </local:GreetingView.DataContext> </local:GreetingView> </Grid> </Window>
OneWayToSource を指定して、添付ビヘイビアが内部で作成する Command を ViewModel に渡します。
再利用できるからスマートですね!
ViewModel に Command のプロパティを用意するのは変わらないですけど、View に依存関係プロパティを用意する手間が省ける分、スマートですね。再利用もできるし。
例えば、「コントロールにフォーカスを設定するコマンド」を添付ビヘイビアにしておけば、Viewの中で使っているコントロール1つ1つに添付ビヘイビアを適用できます。ユーザーの入力をまとめて検証し、エラーになったコントロールにフォーカスを設定する、なんてことが比較的楽に実現できそうです。