Wpf Binding Bridge

本文关键字:Bridge Binding Wpf | 更新日期: 2023-09-27 18:16:40

我感觉这是wpf的一个bug。让我知道你们是怎么想的。

为了保持一切简单,我用。net 4.0制作了一个演示示例

我有一个内容绑定到数据的ContentControl和一个CheckBox绑定到内容的ContentTemplate。

问题是Ok属性永远不会为真,无论我多久点击一次CheckBox。

就好像CheckBox没有传递新值给ViewModel。

看一下这个:

  <Window.Resources>
    <DataTemplate x:Key="dataTemplate">
      <CheckBox IsChecked="{Binding Path=., Mode=TwoWay}"/>
    </DataTemplate>
  </Window.Resources>
  <Grid>
    <ContentControl Content="{Binding Path=Ok, Mode=TwoWay}" ContentTemplate="{StaticResource dataTemplate}"/>
  </Grid>

这是我的ViewModel

public MainWindow()
{
    InitializeComponent();
    ViewModel vm = new ViewModel();
    this.DataContext = vm;
}
public class ViewModel : INotifyPropertyChanged
{
    private string txt;
    public string Txt
    {
        get { return txt; }
        set { txt = value; this.OnPropertyChanged("Txt"); }
    }
    private bool ok;
    public bool Ok
    {
        get { return ok; }
        set { ok = value; this.OnPropertyChanged("Ok");}
    }

    private void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

任何想法如何解决这个问题与ContentTemplate?

Wpf Binding Bridge

您的问题是与值类型的使用有关的常见问题。您将复选框数据绑定到一个基本的值类型 (bool)(这是一种非常不常见的事情)。由于Binding.Sourceobject类型,因此布尔值将框入object中。对该盒装对象的任何更新都不会对ViewModel的原始属性产生影响。

你可以用一个结构体替换布尔值来验证这个理论:

public struct MyStruct
{
    private bool _isChecked;
    public bool IsChecked
    {
        get { return _isChecked; }
        set { _isChecked = value; }
    }
    public ViewModel Parent { get; set; }
} 

并更改绑定:

<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}"/>

如果你在IsChecked的getter和setter上设置了断点,你会看到绑定工作了。但是,当您遇到其中一个断点时,请尝试在立即窗口中调查此值:

? this.Parent.Ok.IsChecked

您应该看到父视图模型上的MyStruct属性根本没有受到数据绑定的影响。


完整测试代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            ViewModel vm = new ViewModel();
            vm.Ok = new MyStruct { Parent = vm, IsChecked = false };
            this.DataContext = vm;
        }

    }
    public class ViewModel : INotifyPropertyChanged
    {
        private string txt;
        public string Txt
        {
            get { return txt; }
            set { txt = value; this.OnPropertyChanged("Txt"); }
        }
        private MyStruct ok;
        public MyStruct Ok
        {
            get { return ok; }
            set { ok = value; this.OnPropertyChanged("Ok"); }
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
    public struct MyStruct
    {
        private bool _isChecked;
        public bool IsChecked
        {
            get { return _isChecked; }
            set { _isChecked = value; }
        }
        public ViewModel Parent { get; set; }
    }
}
xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
    <DataTemplate x:Key="dataTemplate">
      <CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}"/>
    </DataTemplate>
  </Window.Resources>
  <Grid>
    <ContentControl Content="{Binding Path=Ok, Mode=TwoWay}" ContentTemplate="{StaticResource dataTemplate}"/>
  </Grid>
</Window>     

你可以这样做

<Window.Resources>
    <DataTemplate x:Key="dataTemplate">
      <CheckBox IsChecked="{Binding Path=Ok, Mode=TwoWay}"/>
    </DataTemplate>
</Window.Resources>
<Grid>
  <ContentControl Content="{Binding}" ContentTemplate="{StaticResource dataTemplate}"/>
</Grid>
在上面的例子中,我将Content绑定到视图模型本身,然后将CheckBox的IsChecked绑定到Ok属性。

问题是ContentControl不会将它的DataContext传递给内容。

需要手动设置DataContext

<CheckBox DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}" IsChecked="{Binding Path=., Mode=TwoWay}"/>

经过更多的研究(在发布了一个不相关的答案然后再次删除之后),并受到与even Ersönmez和Dev Hedgehog的一些讨论的推动,双向绑定IsChecked="{Binding Path=., Mode=TwoWay}"无法工作的原因变得更加清楚了。

(旁注:此答案不包含问题的解决方案。Pushpraj的回答已经提供了一个很好的解决方案。

{Binding Path=., Source=SomeSource}表示直接绑定到绑定源的绑定。一个等价的绑定语法是{Binding Source=SomeSource}

但是,到绑定源本身的绑定不能是双向的。如果指定{Binding Source=SomeSource, Mode=TwoWay},将在运行时导致InvalidOperationException

另一方面,{Binding Path=., Source=SomeSource, Mode=TwoWay}不会产生异常——但是它仍然没有为这样的数据绑定启用双向绑定。相反,它只是欺骗绑定引擎的绑定验证/错误处理机制。

这已经被发现并在其他绑定相关问题的答案中进行了讨论,例如Allon Guralnek或H.B.的答案(如果不是他们的答案和我与甚至Ersönmez的讨论,我可能仍然不知道这里发生了什么…)。

这也意味着问题不是由盒装值类型本身是绑定源引起的,而是由于试图使用带有绑定路径.的双向绑定。


为什么使用绑定路径.进行双向绑定没有意义?

下面是一个演示这个问题的小示例。它还演示了问题并不局限于绑定源为盒装值类型的情况,也不局限于涉及DataContext的绑定。

public class Demo
{
    public static string SourceString
    {
        get { return _ss; }
        set { _ss = value; }
    }
    private static string _ss = "Hello World!";
}

静态属性Demo提供的字符串(引用类型)。SourceString将用作以下XAML代码片段中的绑定源:

<TextBox Text="{Binding Source={x:Static My:Demo.SourceString}, Mode=TwoWay}" />

(注意,提供的对象是My:Demo。SourceString属性是绑定源;属性本身是而不是绑定源。)

上面的XAML将在运行时产生InvalidOperationException。但是,使用等价的XAML:

<TextBox Text="{Binding Path=., Source={x:Static My:Demo.SourceString}, Mode=TwoWay}" />

不会在运行时产生异常,但绑定仍然不是一个工作的双向绑定。在文本框中输入的文本不会被提升回Demo。SourceString 属性。它不能——绑定只知道它有一个字符串对象作为源和一个绑定路径.

…但它只需要更新Demo。SourceString,这不会太难吧?

假设上面XAML中显示的路径.的双向绑定将尝试作为双向绑定工作-它会导致有意义的行为吗?当TextBox。Text属性由于用户输入文本而改变了其值?

理论上,绑定将尝试通过将绑定路径.应用于作为绑定源的对象(字符串"Hello World!")来将新的字符串值提升回绑定源。这没有多大意义……好吧,它也许可以取代原来的绑定源("Hello World!" string ")与来自TextBox的字符串。Text属性—但这将是相当无意义的,因为这个更改将是局部的和内部的绑定。绑定将无法将新字符串分配给Demo。SourceString——除非有人知道如何获得对Demo的引用。SourceString通过将绑定路径.应用于包含"Hello World!"的字符串对象。因此,对于绑定到绑定源本身的绑定来说,双向绑定模式不是一个可行的选择。

(如果有人对上面的例子如何应用于使用DataContext(如{Binding Path=., Mode=TwoWay})的绑定感到困惑,只需忽略Demo类并替换出现的Demo。)SourceString中的文本与DataContext)