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

时间:2015-10-11 22:16:08

标签: c# windows-runtime winrt-xaml win-universal-app

我目前正在尝试使用新编译的绑定,并且已经(再次)达到了我在拼图中错过了一个小点的一点:为什么我必须调用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举起了一个属性更改事件。我甚至可以调试到用户控件的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

<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方法以使用任务(参见上面的代码)!

4 个答案:

答案 0 :(得分:38)

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

到目前为止有意义吗?

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

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

您的代码隐藏中的ViewModel属性的值将具有实际值并且将绑定得很好。另一方面,您的用户将没有值,因为它尚未从服务器返回。因此,既不能显示用户的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();。如果您的工作是异步的,这很容易 - 但是,就像上面的示例一样,如果您不能确定计时器可能是您唯一可行的选择。

  

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

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

祝你好运!

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

最后我自己发现了这个错误:我正在使用基于任务的操作来加载我的视图模型,这导致通过错误的线程设置依赖项属性(我认为)。如果我通过调度程序设置if64OutOctets属性,它就可以工作。

Instance

但也没有例外,只是gui没有显示任何价值!

答案 3 :(得分:1)

首先,x:Bind的默认绑定模式为OneTime,您需要将其更改为OneWay,如上所述,如果您调用{{1 }} 方法。

您的数据绑定代码似乎有问题。 请粘贴所有相关代码,以便我们查看此问题的来源。