-Custom Markup Extensions Custom markup extensions allow you to run custom code from XAML. Markup extensions allow code to be run at XAML parse time for both properties and event handlers, enabling cutting‐edge MVVM support.
Silverlightに独自マークアップ拡張が無いことでSilverlightとWPFのアプリの互換性を保とうと思うと独自マークアップ拡張を作るのは控えられてきてました。でもマークアップ拡張を使うと普通にXAMLで書くと大変なことが簡単に書けるという凄いやつなんです。
まだ実際に、SDK入れれないのでドキュメントにあるコードをそのままコピペで恐縮ですが以下のようなマークアップ拡張が紹介されています。
<TextBox Text="Some Text" GotFocus="{local:MethodInvoke Path=TextChanged}" Width="102" Canvas.Left="35" Canvas.Top="42" />
このMethodInvokeは以下のように実装されてるみたいです。
using System; using System.Reflection; using System.Windows; using System.Windows.Markup; using System.Xaml; namespace BookShelf { public class MethodInvokeExtension : IMarkupExtension<object> { public string Method { get; set; } private object _target; private MethodInfo _targetMethod; public object ProvideValue(IServiceProvider serviceProvider) { try { Delegate del = null; // IProvideValueTarget ‐ Provides information about the target object IProvideValueTarget targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; // IXamlTypeResolver ‐ Handles types that use xml prefixes IXamlTypeResolver xamlResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver; // IRootObjectProvider ‐ //Provides access to the root object (the Framework Element) IRootObjectProvider rootObject = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider; if (targetProvider != null) { // Get the target element (ex: ListBox) var targetObject = targetProvider.TargetObject; // Get the target element's event info EventInfo targetEvent = targetProvider.TargetProperty as EventInfo; if (targetEvent != null) { // Get the event's parameters and types for the target element ParameterInfo[] pars = targetEvent.GetAddMethod().GetParameters(); Type delegateType = pars[0].ParameterType; MethodInfo invoke = delegateType.GetMethod("Invoke"); pars = invoke.GetParameters(); // Create the function call (to invoke the event handler) Type customType = typeof(MethodInvokeExtension); var nonGenericMethod = customType.GetMethod("PrivateHandlerGeneric"); MethodInfo genericMethod = nonGenericMethod.MakeGenericMethod(pars[1].ParameterType); // Grab the root FrameworkElement if (rootObject != null) { FrameworkElement rootObjFE = rootObject.RootObject as FrameworkElement; if (rootObjFE != null) { // Grab the FrameworkElement's DataContext //and the method name exposed by it _target = rootObjFE.DataContext; _targetMethod = _target.GetType().GetMethod(Method); // Make sure the FE's DataContext has the Method name //or get out. if (_target == null) return null; if (_targetMethod == null) return null; // Create the event handler and attach it from //the target element to the DataContext's method name del = Delegate.CreateDelegate( delegateType, this, genericMethod); } } } } return del; } catch (Exception ex) { string innerex = ex.InnerException.ToString(); return null; } } // Invoke the generic handler public void PrivateHandlerGeneric<T>(object sender, T e) { _targetMethod.Invoke(_target, null); } } }
かなり、ServiceProviderを駆使して色々やっちゃってる感じのコードですが、このマークアップ拡張あたりが標準で提供されれば嬉しいかもですね。とりあえず、引数のないメソッドのみに対応してるみたいです。これでイベント引数からView非依存の型への変換がつけば、もうちょっといい感じですね。
CanExecute的なことをやろうとするとどうなるのだろうか。でも、CanExecuteが必要無いシーンでは、使うことを検討するとViewModelのコードがシンプルになると思います。Viewに機能を公開するのにCommandを用意するのか。メソッドを素直に使うのか、論争が起きそうですね。
個人的には、CanExecuteがいらないシーン(今回の例のようなGotFocusやSelectionChangedみたいなのはCommandよりもメソッド直呼び出しのほうが実行可否の判断がいらないのでメソッドを使ってもいいのかもと思います。
さて、続きを読みます。