Wpf绑定桥

时间:2014-08-05 08:37:34

标签: c# wpf

我感觉这是wpf中的一个错误。让我知道你们对此的看法。

为了保持一切简单,我在.net 4.0

中制作了演示示例

我有一个ContentControl,内容绑定到Data和ContentTemplate,它包含一个绑定到Content的CheckBox。

问题是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的这个问题?

4 个答案:

答案 0 :(得分:3)

您的问题是与使用值类型相关的常见问题。您将复选框的数据绑定到原始值类型(bool)(这是非常罕见的事情)。由于Binding.Source的类型为object,因此您的布尔值会将装箱变为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>     

答案 1 :(得分:2)

你可以这样做

<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属性。

答案 2 :(得分:0)

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

您需要手动设置DataContext

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

答案 3 :(得分:0)

经过一些更多的研究(并在发布了一个不相关的答案,然后再删除它) - 并受到与ErenErsönmez和Dev Hedgehog的一些讨论的激励 - 为什么双向约束的原因IsChecked="{Binding Path=., Mode=TwoWay}"无法工作变得更加清晰。

(旁注:这个答案不包含问题的解决方案。Pushpraj's answer已经提供了一个很好的解决方案。)

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

但是,绑定源本身的绑定不能是双向的。通过指定{Binding Source=SomeSource, Mode=TwoWay}来尝试这样做会在运行时导致 InvalidOperationException

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

已经在其他与约束相关的问题的答案中找到并讨论了这个问题,例如Allon GuralnekH.B.的答案(如果不是他们的回答和我与Eren的讨论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!“)。这没有多大意义......好吧,它可能用 TextBox.Text 中的字符串替换原始绑定源(“ Hello World!”字符串)属性 - 但这将是毫无意义的,因为这种变化只是本地和内部的绑定。绑定将无法将新字符串分配给 Demo.SourceString - 除非有人知道如何通过应用绑定获取对 Demo.SourceString 的引用路径.到包含“Hello World!”的字符串对象。因此,双向绑定模式不是绑定绑定源本身的绑定的可行选项。

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