かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Silverlight 5 betaのMVVMサポートのキーはマークアップ拡張だった?

Silverlight 5 beta features

-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に独自マークアップ拡張が無いことでSilverlightWPFのアプリの互換性を保とうと思うと独自マークアップ拡張を作るのは控えられてきてました。でもマークアップ拡張を使うと普通に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よりもメソッド直呼び出しのほうが実行可否の判断がいらないのでメソッドを使ってもいいのかもと思います。

さて、続きを読みます。