【问题标题】:With compiled bindings (x:bind), why do I have to call Bindings.Update()?使用已编译的绑定 (x:bind),为什么我必须调用 Bindings.Update()?
【发布时间】:2015-10-11 22:16:08
【问题描述】:

我目前正在尝试使用新编译的绑定,并且(再次)达到了我在谜题中遗漏的一点:为什么我必须调用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 ????? */
        }
    }
}

另一个更新

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

【问题讨论】:

  • 请贴出您的视图背后的代码和视图模型代码,也许我们可以提供帮助
  • 我添加了所有相关代码。仅当我包含 Bindings.Update(); 时,主页才会显示“样本人”。
  • 感谢您提出的好问题!顺便说一句,Mode=OneWay 适用于我的 UWP 应用程序。您的问题和出色的回答说服了我继续使用 Binding。它可以使用很多改进,但不是 x:Bind 提供的。如果 MSFT 想改进 Binding,就看看数据绑定新人使用的强大的表达式语言——Android。

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


【解决方案1】:

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

到目前为止还有意义吗?

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

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

代码隐藏中的 ViewModel 属性的值将具有实际值,并且可以很好地绑定。另一方面,您的用户将没有值,因为它尚未从服务器返回。结果,这个和 User 的 FirstName 属性都不能显示,对吧?

然后您的数据就会更新。

您会认为当您将 User 对象的值设置为真实对象时,您的绑定会自动更新。特别是如果您花时间将其设为 INotifyPropertyChanged 属性,对吗?传统的 {Binding} 也是如此,因为默认绑定模式是 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();。如果您的工作是异步的,这很容易 - 但是,就像上面的示例一样,如果您不能确定计时器可能是您唯一可行的选择。

这当然很糟糕,因为计时器意味着时钟时间,而在手机等速度较慢的设备上,时钟时间可能无法正确应用。这是每个开发者都必须针对他们的应用解决的问题 - 也就是说,您的数据何时完全加载并准备就绪?

我希望这能解释正在发生的事情。

祝你好运!

【讨论】:

  • 如果您是绑定新手,您可能会觉得这篇文章很有趣:blogs.msdn.com/b/jerrynixon/archive/2012/10/12/…
  • 嗨,杰瑞,我想知道为什么 x:Bind 之前的默认模式是OneTime。虽然您已经回答说这会提高性能,但我认为我们使用 OneWay 的频率高于 OneTime 并且有很多次我忘记将其更改为 OneTime 这导致我发现“不显示数据" 到处都是错误:-D
  • 最特殊的解释。不能说得更好。我刚刚注意到你是模板 10 的杰里尼克松。
  • 您,先生,真是个天才!
  • 很好的答案。我不知道 x:Bind 默认为一次性绑定。我想知道为什么我不能让 x:Bind 正常工作。非常感谢。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      首先x:Bind的默认绑定模式是OneTime,如果你调用RaisePropertyChanged方法,你需要按照上面的答案把它改成OneWay才能工作。

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

      【讨论】:

      • 已添加代码。对不起,我总是害怕我发布太多代码!
      • 这是一个愚蠢的错误 ;-) 尝试将此代码 this.instance = person; 更改为 this.Instance = person;。您更新了字段instance 而不是属性Instance,这导致PropertyChanged 不起作用。
      • 哦,是的!这是我在 MCVE 中引入的一个错误。现在我纠正了它,它可以工作了。
      • 如果有帮助,请考虑将其标记为答案 :-)
      • 它有帮助,但原来的问题是不同的。我将 Jerry 的答案标记为正确,因为它解释了很多细节。但是你让我又看了一个小时的代码,试图解决这个错误 ;-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-05
      • 1970-01-01
      • 1970-01-01
      • 2015-03-09
      • 1970-01-01
      • 1970-01-01
      • 2016-12-02
      相关资源
      最近更新 更多