Wpf绑定桥

3

我觉得这是 wpf 里的一个 bug,请告诉我你们对此的看法。

为了保持简单,我在 .net 4.0 中制作了演示示例。

我有一个 ContentControl,其中 Content 绑定到 Data,ContentTemplate 包含一个绑定到 Content 的 CheckBox。

问题是 Ok 属性无论我点击多少次 CheckBox 都不会变成 true。

就好像 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>

这是我的视图模型。
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 的问题?

如果您将数据模板更改为控件模板,并将ContentControl的Template属性绑定到控件模板,我认为DataContext会被保留。在您的情况下,看起来DataContext没有被保留。 - JKennedy
@pushpraj:我不确定这与问题的相关性是什么。你能解释一下吗? - Dan Puzey
@elgonzo - 请将这些注释转化为适当的答案。注释很容易失去顺序并且难以阅读。 - Emond
1
@DanPuzey我的意思是,如果涉及的代码IsChecked="{Binding Path=.}"被写成IsChecked="{Binding}"将失败,因为双向绑定需要定义路径或xpath。然而,通过设置Path =.,可以抑制验证/警告,导致出现这种行为。 - pushpraj
@devhedgehog,Eren的回答更接近问题的实质,而在编辑我的答案时,我感觉它越来越像他的答案的重复,而我的原始答案的大部分变得越来越不相关,因此我删除了它。如果你想知道我的错误假设是什么,你可以邀请我进入聊天,我会很乐意解释的 ;) - user2819245
显示剩余9条评论
4个回答

3
您的问题是与值类型的使用有关的常见问题。您正在将复选框数据绑定到一个基元值类型(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上设置断点,您会发现绑定是有效的。但是,当您触发其中一个断点后,尝试在Immediate Window中查看此值:
? 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>     

你所解释的只是使用结构体的副作用,但 O/P 在哪里将结构体用作 DataContext 的值呢?虽然通常情况下确实是“年轻玩家的陷阱”,但这与 O/P 的问题无关... - user2819245
@elgonzo 这个问题不仅局限于结构体,而适用于所有值类型。他正在将数据绑定到“ViewModel.Ok”,这是一个值类型。我提到这个结构体作为一个例子来测试这个理论,因为他可以在getter/setter上设置断点,并引用父对象。 - Eren Ersönmez
不要看你的例子:MyStruct(就像你的答案中一样)是一个属性(结构类型)-您可以绑定它。然后,另一个绑定会绑定到结构体的属性。您所解释的问题与一般值类型无关,而是与结构体属性的绑定有关。关于针对ViewModel的绑定没有任何结论可得,因为ViewModel 不是结构体(即Ok不是结构体属性)。 - user2819245
@elgonzo,你在否定什么?你是在暗示说只要不是结构体,使用值类型(如bool或int)作为绑定_Source_是可以的吗? - Eren Ersönmez
但等等,伙计们,让我问你们一些事情 :) - dev hedgehog
显示剩余13条评论

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

这是另一个常量。这个有效。但我想知道为什么我的常量不起作用? - dev hedgehog
该方法的问题在于无法确定目标对象。绑定工作在依赖框架上,以传播更改,如果属性是依赖属性,则使用反射。因此,当您将属性绑定为绑定的源时,扩展无法确定目标对象以执行设置值操作。通常,双向绑定需要路径,但设置Path = .会抑制警告,从而导致这种不一致性。 - pushpraj

0
问题在于ContentControl不会将其DataContext传递给内容。
您需要手动设置DataContext
<CheckBox DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}" IsChecked="{Binding Path=., Mode=TwoWay}"/>

0

经过更多的研究(在发布了一个无关的答案后又删除了它),并在与Eren Ersö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!";
}

下面的XAML片段将使用静态属性Demo.SourceString提供的字符串(引用类型)作为绑定源:

<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!”字符串),但这将是相当无意义的,因为此更改仅在绑定内部和本地进行。除非有人知道如何通过将绑定路径 . 应用于包含“ Hello World!”的字符串对象来获取对 Demo.SourceString 的引用,否则绑定将无法将新字符串分配给 Demo.SourceString 。因此,双向绑定模式不适用于绑定到绑定源本身的绑定。
(如果有人对上面的示例如何应用于使用DataContext的绑定(例如{Binding Path =。, Mode = TwoWay} )感到困惑,请忽略类并将文本中的任何 Demo.SourceString 出现替换为 DataContext 。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接