使用编译的绑定(x:bind),为什么我必须调用Bindings.Update()?

我目前正在尝试新的编译绑定,并且已经达到(再次)我在错误中错过了一个点:为什么我必须调用Bindings.Update ? 到现在为止,我认为实现INotifyPropertyChanged已经足够了?

在我的例子中,如果我调用这个神秘的方法(由编译的绑定自动生成),GUI只显示正确的值。

我正在使用具有以下(此处简化的)xaml语法的用户控件:

    

其中TextValue是此用户控件的简单依赖项属性。 在页面中,我将此控件用作:

    

哪里:

  • ViewModel是在运行InitializeComponent()之前设置的标准属性
  • Instance是一个实现INotifyPropertyChanged的简单对象

加载Instance ,我为Instance引发了一个属性更改事件。 我甚至可以调试到该行,其中用户控件的dependency属性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

    

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

      

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 ????? */ } } } 

还有一个更新

我更新了Load方法以使用任务(参见上面的代码)!

有时,您要显示的数据在页面加载和呈现后几秒钟内不可用(如从服务器或数据库返回)。 如果您在后台/异步进程中调用数据,从而释放UI以在不挂起的情况下呈现,则尤其如此。

到目前为止有道理吗?

现在创建一个绑定; 让我们这样说:

  

代码隐藏中ViewModel属性的值将具有实际值,并且绑定得很好。 另一方面,您的用户将没有值,因为它尚未从服务器返回。 因此,既不能显示用户的FirstName属性,也不能显示用户的FirstName属性,对吧?

然后您的数据会更新。

当您将User对象的值设置为真实对象时,您会认为绑定会自动更新。 特别是如果你花时间把它变成一个INotifyPropertyChanged属性,对吧? 传统的{Binding}也是如此,因为默认的绑定模式是OneWay。

什么是OneWay绑定模式?

OneWay绑定模式意味着您可以更新实现INotifyPropertyChanged的后端模型属性,并且绑定到该属性的UI元素将反映数据/值更改。 太棒了。

为什么不起作用?

这不是因为{x:Bind}不支持Mode = OneWay,而是因为它默认为Mode = OneTime。 回顾一下,传统的{Binding}默认为Mode = OneWay,编译后的{x:Bind}默认为Mode = OneTime。

什么是OneTime绑定模式?

OneTime绑定模式意味着在使用绑定加载/呈现UI元素时,只绑定到底层模型一次。 这意味着如果您的基础数据尚不可用,则无法显示该数据,一旦数据可用,它将不会显示该数据。 为什么? 因为OneTime不监视INotifyPropertyChanged。 它只在加载时读取。

模式 (来自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}尝试提高绑定性能的众多方法之一。 那是因为OneTime是最快的,内存需求最少。 更改绑定到OneWay会破坏这一点,但可能需要您的应用程序。

解决这个问题的另一种方法仍然是保持{x:Bind}开箱即用的性能优势,就是调用Bindings.Update(); 在您的视图模型完全准备好您的数据以进行呈现之后。 如果您的工作是异步的,这很容易 – 但是,就像上面的示例一样,如果您不能确定计时器可能是您唯一可行的选择。

当然,这很糟糕,因为计时器意味着时钟时间,而在像手机这样的慢速设备上,时钟时间可能不适用。 这是每个开发人员必须针对他们的应用程序制定的 – 也就是说,您的数据何时完全加载并准备就绪?

我希望这可以解释发生了什么。

祝你好运!

虽然“传统”绑定默认为“单向”(或在某些情况下为双向),但编译绑定默认为“一次”。 只需在设置绑定时更改模式:

  

最后我自己发现了这个错误:我正在使用基于任务的操作来加载我的视图模型,这导致通过错误的线程设置依赖项属性(我认为)。 如果我通过调度程序设置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显示没有价值!

首先, x:Bind的默认绑定模式是OneTime ,您需要将其更改为OneWay ,如上所述,如果您调用RaisePropertyChanged方法,则可以使其工作。

您的数据绑定代码似乎有问题。 请粘贴所有涉及的代码,让我们看看这个问题的根源。