tmytのらくがき

個人の日記レベルです

UAPの新しいBinding

プレビュー版時点での情報です。リリース版では異なることがあります。

Windows 10 UAP からコンパイル時データバインディングというなんかよくわからないものが追加されました。いままでのバインディングは実行時に名前をもとにプロパティを特定していたりしてとても遅かったので、コンパイル時にメンバを解決することで速度がかなり早くなったよ!!!!!というものらしいです。

使い方

コンパイル時データバインディングの使い方はとてもかんたん。

<Button Command="{Binding HogeCommand}" />

と書いていたところを

<Button Command="{x:Bind HogeCommand}" />

と書くだけ! ってChannel9で言ってました。

制限とか

コンパイル時データバインディングなので結構制約があるみたいです。

1. ModeとPathしか設定できない

  • Source={StaticResource AAA} とかできません。
  • Converter={StaticResource AAA} とかできません。

2. コンパイル時に名前が解決できないとコンパイルエラーになる

3. (要確認) DataTemplateでしか使い物にならない

  • DataTemplate以外だとルート要素のプロパティしかバインディングできないのでいまいち使い道なし

実際に使ってみると...

こんなXAMLを書いてみました。

<ListBox x:Name="listbox">
    <ListBox.ItemTemplate>
        <DataTemplate x:DataType="local:HogeType">
            <TextBlock Text="{x:Bind TextValue}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

DataTemplate以外で使い物にならない。という話をしたのは、DataTemplateでないとx:DataTypeが使えないからです。ちなみにDataTemplate以外で使うとコンパイラにこんなこと言われてコンパイルできません。

MainPage.xaml(10,6): XamlCompiler error WMC0908: DataType is only allowed for DataTemplate.

ListBoxにはコードビハインドで適当にデータを詰めておきました。

istbox.ItemsSource = Enumerable.Range(0, 100).Select(i => new HogeType { TextValue = $"Item {i}" } );

実行するとこんな感じで見えました。

f:id:tmyt:20150419132425p:plain

ここでItemsSourceにHogeType以外のデータが入っていると...?

f:id:tmyt:20150419132638p:plain

InvalidCastExceptionで怒られました。

x:Bindを使ったとき何が起きているのか

コンパイルバインディングとかいうけど何が起きてるのさ… そこでMainPage.g.cs へ潜入してみることに。

まずは、このXAMLコンパイルした時のMainPage.g.cs

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
</Grid>

こんな感じ。シンプルですね。

partial class MainPage : global::Windows.UI.Xaml.Controls.Page, global::Windows.UI.Xaml.Markup.IComponentConnector
{
    /// <summary>
    /// Connect()
    /// </summary>
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 14.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    public void Connect(int connectionId, object target)
    {
        this._contentLoaded = true;
    }
}

続いて、従来のBinding で書いたとき。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ListBox x:Name="listbox">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding TextValue}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

listboxのx:Nameに対するコードが生成されたぐらいの変更点しかないです。

partial class MainPage : global::Windows.UI.Xaml.Controls.Page, global::Windows.UI.Xaml.Markup.IComponentConnector
{
    /// <summary>
    /// Connect()
    /// </summary>
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 14.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    public void Connect(int connectionId, object target)
    {
        switch(connectionId)
        {
        case 1:
            this.listbox = (global::Windows.UI.Xaml.Controls.ListBox)(target);
            break;
        }
        this._contentLoaded = true;
    }
}

最後にx:Bind で書き直したもの

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ListBox x:Name="listbox">
        <ListBox.ItemTemplate>
            <DataTemplate x:DataType="local:HogeType">
                <TextBlock Text="{x:Bind TextValue}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

x:Bindに対応したコードがかなり大量に生成されていることがわかります。

    partial class MainPage : global::Windows.UI.Xaml.Controls.Page, global::Windows.UI.Xaml.Markup.IComponentConnector
    {
        private IMainPage_Bindings _bindings;

        private IMainPage_Bindings Bindings;

        internal interface IMainPage_Bindings
        {
            void Initialize();
            void UpdateAll();
            void StopChangeTracking();
        }

        /// <summary>
        /// Auto generated class for compiled bindings.
        /// </summary>
        internal class MainPage_obj2_Bindings : IMainPage_Bindings
        {
            // Pointer to the Root of the UI tree for this binding scope.
            private global::App6.HogeType dataRoot;
            private bool _initialized=false;

            // Fields for each control that has bindings.
            internal global::Windows.UI.Xaml.Controls.TextBlock obj2;

            public MainPage_obj2_Bindings(global::App6.HogeType dataRoot)
            {
                this.dataRoot = dataRoot;
            }

            public void Initialize()
            {
                if (!_initialized)
                {
                    _initialized=true;
                    this.InitializeCore();
                }
            }

            public void UpdateAll()
            {
                this.InitializeCore();
        }

        public void StopChangeTracking()
        {
        }

        private void InitializeCore()
        {
            this.Update_(this.dataRoot, true);
        }

        // Update method for each path node used in binding steps.
        internal void Update_(global::App6.HogeType obj, bool isInitialUpdate = false)
        {
            this.UpdatePathStep_TextValue(obj, isInitialUpdate);
        }

        // Update method for each path node used in binding steps.
        internal void Update_TextValue(global::System.String obj, bool isInitialUpdate = false)
        {
            if (isInitialUpdate)
            {
                this.SetValue_obj2_Text(obj);
            }
        }

        private void UpdatePathStep_TextValue(global::App6.HogeType obj, bool isInitialUpdate = false)
        {
            if (obj != null)
            {
                try
                {
                    this.Update_TextValue(obj.TextValue, isInitialUpdate);
                    return;
                }
                catch (
#pragma warning disable 0168
                global::System.Exception ex
#pragma warning restore 0168
                )
                {
#if DEBUG && !DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT
                        global::System.Diagnostics.Debug.WriteLine("Binding update failed for 'TextValue' : " + ex.Message);
#endif
                }
            }
        }

        private void SetValue_obj2_Text(global::System.String obj)
        {
            try
            {
                this.obj2.Text = obj;
            }
            catch (
#pragma warning disable 0168
                global::System.Exception ex
#pragma warning restore 0168
            )
            {
#if DEBUG && !DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT
                        global::System.Diagnostics.Debug.WriteLine("Binding failure in assigning 'TextBlock.Text' = 'TextValue' at c:\\users\\yutaka\\documents\\visual studio 2015\\Projects\\App6\\App6\\MainPage.xaml, line 14 : " + ex.Message);
#endif

            }
        }

    }
    /// <summary>
    /// Connect()
    /// </summary>
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 14.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    public void Connect(int connectionId, object target)
    {
        switch(connectionId)
        {
        case 1:
            this.listbox = (global::Windows.UI.Xaml.Controls.ListBox)(target);
            break;
        case 2:
            {
            global::Windows.UI.Xaml.Controls.TextBlock element2 = (global::Windows.UI.Xaml.Controls.TextBlock)(target);
            MainPage_obj2_Bindings bindings = new MainPage_obj2_Bindings((global::App6.HogeType)element2.DataContext);
            this._bindings = bindings;
            // TODO: This is temporary and will be moved in the binding helper.
            element2.DataContextChanged += (global::Windows.UI.Xaml.FrameworkElement sender, global::Windows.UI.Xaml.DataContextChangedEventArgs args) => { 
                bindings.Initialize();
                bindings.Update_((global::App6.HogeType)args.NewValue, true);
            };
            ((MainPage_obj2_Bindings)_bindings).obj2 = element2;
            }
            break;
        }
        this._contentLoaded = true;
    }
}

この生成されたコードを読んでいくと、こんな行があります。

MainPage_obj2_Bindings bindings = new MainPage_obj2_Bindings((global::App6.HogeType)element2.DataContext);
...snip...
((MainPage_obj2_Bindings)_bindings).obj2 = element2;

ここで、MainPage_obj2_BindingsのXMLドキュメントをみてみると、 Auto generated class for compiled bindings. と書いてあります。さらにこのクラスを見てみると

private void UpdatePathStep_TextValue(global::App6.HogeType obj, bool isInitialUpdate = false)
{
    if (obj != null)
    {
        try
        {
            this.Update_TextValue(obj.TextValue, isInitialUpdate);
            return;
        }
        catch (global::System.Exception ex)
        {
        }
    }
}

private void SetValue_obj2_Text(global::System.String obj)
{
    try
    {
        this.obj2.Text = obj;
    }
    catch (global::System.Exception ex)
    {
    }
}

という箇所があります。まずUpdatePathStep_TextValue関数ですがここで、objはx:DataTypeで指定したHogeType、さらに静的にTextValueプロパティにアクセスしています。また、SetValue_obj2_Text関数ではobj2はTextBlock型なことから、代入も静的に解決されています。

こうしてみてみると、x:Bindは文字通りコンパイル時にバインディングが処理されていました。従来のBindingと比べてもなかなか早そうですが、速度測定はまた今度…