TwoWay 模式的绑定和 x:Bind 问题

Posted

技术标签:

【中文标题】TwoWay 模式的绑定和 x:Bind 问题【英文标题】:Binding and x:Bind problems with TwoWay mode 【发布时间】:2016-09-10 02:27:08 【问题描述】:

我遇到了奇怪的问题,我无法理解。在主页中,我只有一个按钮可以导航到第二页并保存我的模型:

public class Model : INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;
    public void RaiseProperty(string property) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));

    private int index = 0;
    public int Index
    
        get  Debug.WriteLine($"Getting value index"); return index; 
        set  Debug.WriteLine($"Setting value value"); index = value; RaiseProperty(nameof(Index)); 
    


public sealed partial class MainPage : Page

    public static Model MyModel = new Model();

    public MainPage()
    
        this.InitializeComponent();
        SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
        SystemNavigationManager.GetForCurrentView().BackRequested += (s, e) =>  if (Frame.CanGoBack)  e.Handled = true; Frame.GoBack();  ;
    

    private void Button_Click(object sender, RoutedEventArgs e) => Frame.Navigate(typeof(BlankPage));

在第二页只有 ComboBoxSelectedIndex 中有双向绑定:

<Grid Background="ThemeResource ApplicationPageBackgroundThemeBrush">
    <ComboBox SelectedIndex="x:Bind MyModel.Index, Mode=TwoWay">
        <x:String>First</x:String>
        <x:String>Second</x:String>
        <x:String>Third</x:String>
    </ComboBox>
</Grid>
public sealed partial class BlankPage : Page

    public Model MyModel => MainPage.MyModel;

    public BlankPage()
    
        this.InitializeComponent();
        this.Unloaded += (s, e) => Debug.WriteLine("--- page unloaded ---");
        DataContext = this;
    

没什么特别的。问题是当我使用Bindingx:Bind 时会得到两个不同的输出,但最糟糕的是,在每次新导航到同一页面后,属性的getter(和x:Bind 中的setter)被调用的次数越来越多:

页面仍然驻留在内存中,并且仍然订阅属性,这是可以理解的。如果我们从页面返回后运行GC.Collect(),我们将从头开始。

但如果我们使用旧的 Bindingone-way 并且选择更改事件:

<ComboBox SelectedIndex="Binding MyModel.Index, Mode=OneWay" SelectionChanged="ComboBox_SelectionChanged">

连同事件处理程序:

private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)

    if (e.RemovedItems.Count > 0 && e.AddedItems.FirstOrDefault() != null)
        MyModel.Index = (sender as ComboBox).Items.IndexOf(e.AddedItems.FirstOrDefault());

那么它将“正常”工作 - 只有一个 getter 和 setter,无论我们之前导航到页面多少次。

所以我的主要问题是:

one-way - two-way 绑定的差异从何而来? 考虑到 单向绑定 只触发一次 getter - 所描述的 双向 行为是否需要/有意? 在调用多个 getter/setter 的情况下,您如何处理这种 双向 绑定?

一个工作示例,您可以download from here。

【问题讨论】:

这是因为你的静态模型实例吗?只是我的猜测。 @KiranPaul 不,使用非静态它的行为完全相同。我几乎可以肯定它与内存有某种联系——如果我在返回主页后触发GC.Collect(),那么我又回到了开始。不过我不知道为什么这两个绑定有这么大的不同,为什么单向 getter 总是被调用一次。 【参考方案1】:

实际上,当您使用OneWay 绑定SectionChanged 事件时,更改选择后仅调用Index 属性的settergetter 永远不会到达,因此您不会看到多个 "Getting value ..."

但是为什么getter没有被调用呢??

在这一行下断点-

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));

您会看到PropertyChanged 的值是null。所以Invoke 方法永远不会被触发。我怀疑这可能是ComboBox 中的一个错误,传统绑定设置为OneWay。每当您更改选择时,绑定就会中断,因此PropertyChangednull。如果你改用x:Bind,这个问题就消失了。

如您所知,GC 只会在需要时收集废弃的页面实例。因此,有时您会看到Index 在多个地方被引用,无论您选择哪种绑定机制。

保证 gettersetter 只被调用一次的一种方法是将第二个 PageNavigationCacheMode 更改为 Enabled/Required。这样做将确保页面的单个实例。

【讨论】:

我完全忘记了 NavigationCacheMode。也很好地抓住了这个 propertychanged beng null。感谢您的帮助。 不客气。你总是有有趣的问题。 :)【参考方案2】:

即使在您从新的BlankPage 导航到新的BlankPage 之后,其他页面仍然在内存中,并且仍然绑定在您的静态模型中,正如@KiranPaul 评论的那样。

现在,如果您如您所评论的那样,更改为无静态并且仍然表现相同,那是因为您犯了同样的错误。即使它不是静态的,你仍然使用 MainPage 中的相同变量。(我认为它不可能,因为它不是静态的)

所以内存中没有GC.Collect()-ed 的所有页面都会引发PropertyChanged 事件。因为 MyModel 始终是同一个。

试试这个应该可以。每次导航到 BlankPage 时,您都会实例化一个新模型并传递您的索引。然后,当您卸载页面时,您会更新 MainPage.Model 中的值。这样,当您离开 BlankPage 时,您只会在输出中看到 Set 和 Get。

 public sealed partial class BlankPage : Page
    
        public Model MyModel = new Model()  Index = MainPage.MyModel.Index ;

        public BlankPage()
        
            this.InitializeComponent();
            this.Unloaded += (s, e) =>  MainPage.MyModel.Index = MyModel.Index; Debug.WriteLine("--- page unloaded ---"); ;
            DataContext = this;
        
    

或者当您离开 BlankPage 时,您可以:

致电GC.Collect() 卸载页面时解绑MyModel?

编辑:

使用Binding,如果你做得非常快,它也会做同样的事情。我的猜测是 GC.Collect() 被称为

所以我搜索了一下,我发现了这个:

Binding vs. x:Bind, using StaticResource as a default and their differences in DataContext

回答说:

x:Bind 标记扩展(Windows 10 的新功能)是 Binding 的替代品。 x:Bind 缺少 Binding 的一些功能,但它比 Binding 运行时间和内存更少,并且支持更好的调试。

所以 Binding 的工作方式肯定不同,它可能会调用 GC.Collect() 或 Unbind it self??。也许看看x:Bind markup

【讨论】:

那么有几个问题:1)如果我想使用我的应用程序中定义的一个模型而不重新创建它怎么办? 2) 为什么Bindingx:Bind 表现不同? 3)为什么单向绑定可以正常工作呢? - 总是一个吸气剂,无论我导航到一个页面多少次(静态和非静态? @Romasz 检查我的编辑。如果解绑或者拨打GC.Collect()我觉得你没问题。 绑定肯定不会调用GC.Collect(),你甚至可以在调试的时候检查一下。另外我不确定您所说的它可能会自行解除绑定是什么意思。 开个玩笑那是什么??用于:P.(英语不是母语)Nvm。结论,Binding 和 x:Bind 有相同的问题,但有一个区别,在 Binding 中它消失得很快!像GC.Collect() 被调用(也许它发生在操作系统的本地)。而不是 x:Bind ,如果你不做任何事情,问题仍然存在。现在说它击中了我【参考方案3】:

您可以尝试将tracing 添加到此绑定中以了解一些情况。

我还建议交换这些行,使它们看起来像这样:

DataContext = this;
this.InitializeComponent();

它可能会干扰您的绑定。当您调用 initializeComponent 时,它会构建 xaml 树,但对于绑定,我猜它使用旧的 DataContext,然后您立即更改 DataContext,强制重新绑定每个属性。

【讨论】:

以上是关于TwoWay 模式的绑定和 x:Bind 问题的主要内容,如果未能解决你的问题,请参考以下文章

InvalidOperationException - TwoWay 或 OneWayToSource 绑定无法在只读属性上工作

InvalidOperationException - TwoWay 或 OneWayToSource 绑定无法在只读属性上工作

wpf model 默认啥模式? twoWay?

WPF CheckBox TwoWay 绑定不起作用

TwoWay 绑定不是真的 Two Way 吗?

有哪些不同的 WPF 绑定模式?