对于编译绑定(x:bind),为什么我必须调用bindings . update()

本文关键字:为什么 调用 update bindings 于编译 编译 绑定 bind | 更新日期: 2023-09-27 18:17:15

我目前正在试验新的编译绑定,并且已经(再次)达到了我在拼图中缺少一块的点:为什么我必须调用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()时,才会显示该值。我遗漏了什么?

更新

I也不能与{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 ????? */
        }
    }
}

再更新一次

我更新了Load方法来使用task(参见上面的代码)!

对于编译绑定(x:bind),为什么我必须调用bindings . update()

有时,要在页面加载和呈现几秒钟后才能显示想要显示的数据(比如从服务器或数据库返回的数据)。如果你在后台/异步进程中调用你的数据,释放你的UI来渲染而不挂起,这尤其正确。

明白了吗?

现在创建绑定;我们这样写:

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

你的ViewModel属性的值在你的代码后面将有一个真实的值,并将绑定很好。另一方面,您的User将没有值,因为它还没有从服务器返回。结果是User的FirstName属性都不能显示,对吧?

那么你的数据就更新了

您可能认为,当您将User对象的值设置为实际对象时,绑定将自动更新。特别是如果你花时间让它成为INotifyPropertyChanged属性,对吧?这对于传统的{Binding}来说是正确的,因为默认的绑定模式是单向的。

单向绑定模式是什么?

单向绑定模式意味着你可以更新实现INotifyPropertyChanged的后端模型属性,并且绑定到该属性的UI元素将反映数据/值的更改。这是美妙的。

为什么不工作?

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

OneTime绑定模式是什么?

OneTime绑定模式意味着你只绑定到底层模型一次,在加载/呈现带有绑定的UI元素的时候。这意味着,如果您的底层数据还不可用,它就不能显示该数据,而且一旦数据可用,它也不会显示该数据。为什么?因为OneTime不监控INotifyPropertyChanged。它只在加载时读取。

Modes (from MSDN):对于单向和双向绑定,对源的动态更改不会自动传播到目标,除非源提供一些支持。您必须在源对象上实现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是最快的,占用内存最少。将绑定更改为单向会破坏这一点,但对于你的应用来说,这可能是必要的。

修复此问题并保持{x:Bind}的性能优势的另一种方法是在视图模型完全准备好要呈现的数据后调用Bindings.Update();。如果你的工作是异步的,这很容易——但是,就像上面的例子一样,如果你不能确定计时器可能是你唯一可行的选择。

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

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

祝你好运!

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

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

最后我自己发现了错误:我正在使用基于任务的操作来加载我的视图模型,这导致通过错误的线程设置依赖属性(我认为)。如果我通过调度程序设置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,如果调用RaisePropertyChanged方法,需要按照上面的答案将其改为OneWay才能正常工作。

看起来你的数据绑定代码有问题。请粘贴所有涉及的代码,让我们看到这个问题的根源。