web-dev-qa-db-ja.com

コンパイルされたバインディング(x:bind)で、Bindings.Update()を呼び出さなければならないのはなぜですか?

私は現在、新しくコンパイルされたバインディングを実験していて、(再び)パズルのピースが欠けているところに到達しました:なぜ_Bindings.Update_を呼び出さなければならないのですか?今まで、INotifyPropertyChangedを実装するだけで十分だと思いましたか?

私の例では、この不思議なメソッド(コンパイルされたバインディングによって自動生成される)を呼び出すと、GUIは正しい値のみを表示します。

次の(ここでは簡略化された)xaml構文でユーザーコントロールを使用しています。

_<UserControl>
  <TextBlock Text="x:Bind TextValue"/>
</UserControl>
_

ここで、TextValueは、このユーザーコントロールの単純な依存関係プロパティです。ページでは、このコントロールを次のように使用しています。

_<Page>
  <SampleControl TextValue="{x:Bind ViewModel.Instance.Name}"/>
</Page>
_

どこ:

  • ViewModelは、InitializeComponent()が実行される前に設定される標準プロパティです。
  • Instanceは、INotifyPropertyChangedを実装する単純なオブジェクトです。

Instanceを読み込んだ後、Instanceのプロパティ変更イベントを発生させます。ユーザーコントロールの依存プロパティTextValue正しい値-を取得する行までデバッグすることもできますが、何も表示されません。 Bindings.Update()を呼び出した場合にのみ、値が表示されます。ここで何が欠けていますか?

更新

_{x:Bind ... Mode=OneWay}_も使用していません。

その他のコード

Person.cs

_using System.ComponentModel;
using System.Threading.Tasks;

namespace App1 {
    public class Person : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;

        private string name;
        public string Name { get {
                return this.name;
            }
            set {
                name = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Name"));
            }
        }
    }

    public class ViewModel : INotifyPropertyChanged {

        public event PropertyChangedEventHandler PropertyChanged;

        private Person instance;
        public Person Instance {
            get {
                return instance;
            }
            set {
                instance = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Instance"));
            }
        }

        public Task Load() {
            return Task.Delay(1000).ContinueWith((t) => {
                var person = new Person() { Name = "Sample Person" };                
                this.Instance = person;
            });
        }


    }
}
_

SampleControl.cs

_<UserControl
    x:Class="App1.SampleControl"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="100"
    d:DesignWidth="100">

    <TextBlock Text="{x:Bind TextValue, Mode=OneWay}"/>

</UserControl>
_

SampleControl.xaml.cs

_using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App1 {
    public sealed partial class SampleControl : UserControl {

        public SampleControl() {
            this.InitializeComponent();
        }

        public string TextValue {
            get { return (string)GetValue(TextValueProperty); }
            set { SetValue(TextValueProperty, value); }
        }

        public static readonly DependencyProperty TextValueProperty =
            DependencyProperty.Register("TextValue", typeof(string), typeof(SampleControl), new PropertyMetadata(string.Empty));

    }
}
_

MainPage.xaml

_<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <local:SampleControl TextValue="{x:Bind ViewModel.Instance.Name, Mode=OneWay}"/>
    </StackPanel>
</Page>
_

MainPage.xaml.cs

_using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App1 {

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.DataContext = new ViewModel();
            this.Loaded += MainPage_Loaded;
            this.InitializeComponent();
        }

        public ViewModel ViewModel {
            get {
                return DataContext as ViewModel;
            }
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e) {
            ViewModel.Load();
            Bindings.Update(); /* <<<<< Why ????? */
        }
    }
}
_

もう1つの更新

タスクを使用するようにLoadメソッドを更新しました(上記のコードを参照)。

17
ventiseis

ページが読み込まれてレンダリングされてから数秒後まで、表示したいデータが利用できない場合があります(サーバーやデータベースから返されるなど)。これは、UIを解放してハングせずにレンダリングできるバックグラウンド/非同期プロセスでデータを呼び出す場合に特に当てはまります。

これまでのところ意味がありますか?

次に、バインディングを作成します。このようなことを言いましょう:

_<TextBlock Text="{x:Bind ViewModel.User.FirstName}" />
_

コードビハインドのViewModelプロパティの値は実際の値を持ち、正常にバインドされます。一方、ユーザーはまだサーバーから返されていないため、値はありません。その結果、それもユーザーのFirstNameプロパティも表示できませんよね?

その後、データが更新されます。

Userオブジェクトの値を実際のオブジェクトに設定すると、バインディングが自動的に更新されると思います。特に、時間をかけてINotifyPropertyChangedプロパティにする場合は、そうですか?デフォルトのバインディングモードはOneWayであるため、これは従来の{Binding}にも当てはまります。

OneWayバインディングモードとは何ですか?

OneWayバインディングモードは、INotifyPropertyChangedを実装するバックエンドモデルプロパティを更新できることを意味し、そのプロパティにバインドされたUI要素はデータ/値の変更を反映します。素晴らしいです。

なぜ機能しないのですか?

{x:Bind}がMode = OneWayをサポートしていないためではなく、デフォルトでMode = OneTimeに設定されているためです。要約すると、従来の{Binding}のデフォルトはMode = OneWayであり、コンパイルされた{x:Bind}のデフォルトはMode = OneTimeです。

OneTimeバインディングモードとは何ですか?

OneTimeバインディングモードとは、バインディングを使用したUI要素のロード/レンダリング時に、基になるモデルに1回だけバインドすることを意味します。つまり、基になるデータがまだ利用できない場合、そのデータを表示することはできず、データが利用可能になると、そのデータは表示されません。どうして? OneTimeはINotifyPropertyChangedを監視しないためです。ロード時にのみ読み取ります。

Modes(from [〜#〜] msdn [〜#〜] ):OneWayおよびTwoWayバインディングの場合、動的にソースからのサポートを提供しないと、ソースがターゲットに自動的に伝播することはありません。バインディングエンジンがリッスンするイベントを通じてソースが変更をレポートできるように、ソースオブジェクトにINotifyPropertyChangedインターフェイスを実装する必要があります。 C#またはMicrosoft Visual Basicの場合は、System.ComponentModel.INotifyPropertyChangedを実装します。 Visual C++コンポーネント拡張機能(C++/CX)の場合は、Windows :: UI :: Xaml :: Data :: INotifyPropertyChangedを実装します。

この問題を解決する方法は?

いくつかの方法があります。最初の最も簡単な方法は、バインディングを_="{x:Bind ViewModel.User.FirstName}_から_="{x:Bind ViewModel.User.FirstName, Mode=OneWay}_に変更することです。これを行うと、INotifyPropertyChangedイベントが監視されます。

これは、デフォルトでOneTimeを使用することが、{x:Bind}がバインディングのパフォーマンスを向上させようとする多くの方法の1つであることを警告する適切なタイミングです。これは、OneTimeが最小のメモリ要件で可能な限り最速であるためです。バインディングをOneWayに変更すると、これが損なわれますが、アプリでは必要になる場合があります。

この問題を修正し、{x:Bind}ですぐに使用できるパフォーマンス上の利点を維持する別の方法は、ビューモデルがデータを表示する準備を完全に完了した後でBindings.Update();を呼び出すことです。作業が非同期の場合、これは簡単ですが、上記のサンプルのように、タイマーが唯一の実行可能なオプションであるかどうか確信が持てない場合。

もちろん、タイマーは時刻を意味するので、それは残念です。電話のような遅いデバイスでは、その時刻が適切に適用されない可能性があります。これは、すべての開発者がアプリに固有に解決する必要があることです。つまり、データが完全に読み込まれ、準備ができたのはいつですか。

これが何が起こっているのかを説明することを願っています。

頑張ってください!

42
Jerry Nixon

最後に、自分でバグを見つけました。タスクベースの操作を使用してビューモデルをロードしていたため、誤ったスレッドによって依存関係プロパティが設定されていました(私は思います)。ディスパッチャを介してInstanceプロパティを設定すると機能します。

    public Task Load() {
        return Task.Delay(1000).ContinueWith((t) => {
            var person = new Person() { Name = "Sample Person" };
            Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
            () => {
                this.Instance = person;
            });                
        });
    }

しかし、例外はありませんでした。GUIが値を表示しないだけです。

2
ventiseis

「従来の」バインディングはデフォルトで「一方向」(または場合によっては双方向)になりますが、コンパイルされたバインディングはデフォルトで「一回限り」になります。バインディングを設定するときにモードを変更するだけです。

<TextBlock Text="{x:Bind TextValue, Mode=OneWay}" />
2
Kevin Gosse

まず、デフォルトのバインディングモードであるx:BindOneTimeです。OneWayメソッドを呼び出した場合に機能させるには、上記の回答のようにRaisePropertyChangedに変更する必要があります。

データバインディングのコードに問題があるようです。 この問題の原因を確認するには、関連するすべてのコードを貼り付けてください。

1
JuniperPhoton