【问题标题】:Wpf Binding BridgeWpf 绑定桥
【发布时间】:2014-09-27 20:35:08
【问题描述】:

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

为了简单起见,我在 .net 4.0 中做了演示示例

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

问题是无论我多久点击一次复选框,Ok 属性都不会为真。

好像 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 解决此问题?

【问题讨论】:

  • 如果您将 datatemplate 更改为 controltemplate 并将 contentcontrols 模板属性绑定到 controltemplate,我认为 datacontext 将被保留。在您的情况下,数据上下文似乎没有保留
  • 嗯,我不能那样做。这只是一个演示。它表明 ContentTemplate 通常存在错误。我什么都改变不了!我必须坚持 ContentTemplate。还有其他方法吗?
  • 我很确定这不是 WPF 错误。您是否在调试输出中看到任何绑定错误?
  • MSDN: Binding Declarations Overview 表示可选,句点 (.) 路径可用于绑定到当前源。例如,Text="{Binding}" 等价于 Text="{Binding Path=.}"。
  • @DanPuzey 我的意思是,如果有问题的代码 IsChecked="{Binding Path=.}" 写成 IsChecked="{Binding}" 会失败,因为双向绑定需要定义路径或 xpath。但是通过设置Path=. 验证/警告被抑制,导致这种行为

标签: c# wpf


【解决方案1】:

经过更多研究(并在发布不相关的答案然后再次将其删除之后)- 并受到与 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!";
}

静态属性Demo.SourceString提供的字符串(引用类型)将在以下XAML sn-p中用作绑定源:

<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.SourceString 在带有 DataContext 的文本中。)

【讨论】:

    【解决方案2】:

    您的问题是与使用值类型有关的常见问题。您正在将您的复选框数据绑定到原始 值类型 (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>     
    

    【讨论】:

    • 您所解释的只是使用结构的副作用——但是 O/P 在哪里使用结构作为 DataContext 的值?虽然一般来说确实是“年轻球员的陷阱”,但与 O/P 的问题无关......
    • @elgonzo 这个问题对所有值类型都是普遍的,而不仅仅是结构。他将数据绑定到ViewModel.Ok,这是一个值类型。我提到这个结构作为一个例子来测试这个理论,因为他可以在 getter/setter 上放置断点并引用父对象。
    • 不...看看你的例子: MyStruct (如你的回答)是一个属性(结构类型)——你绑定它。然后另一个绑定绑定到 结构的属性。您要解释的问题不是一般的值类型,而是与 结构的属性 的绑定。关于与 ViewModel.Ok 的绑定尚无定论,因为 ViewModel 不是结构(即,Ok 不是结构的属性)
    • @elgonzo 你对什么说“不……”?您是否建议使用像 (a bool or an int) 这样的值类型作为绑定 Source,只要它不是结构?
    • 我不确定我是否正确理解了您的最后评论。 O/P 使用的绑定的来源始终是 DataContext 属性 - 在 ContentControl 使用的绑定中,DataContext 包含一个引用 value,关于模板中使用的绑定 DataContext 包含一个 bool 价值...
    【解决方案3】:

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

    您需要手动设置DataContext

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

    【讨论】:

      【解决方案4】:

      你可以这样做

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

      【讨论】:

      • 嗯,这是另一个星座。这行得通。但是我想知道为什么我的星座不起作用?
      • 该方法的问题是无法确定目标对象。如果属性是依赖属性,绑定在依赖框架上工作以传播更改,否则它使用反射。所以当你将属性绑定为绑定源时,扩展无法确定目标对象来执行设置值操作。通常,双向绑定需要路径,但设置 Path=。抑制警告,从而导致这种不一致。
      猜你喜欢
      • 2018-05-25
      • 1970-01-01
      • 1970-01-01
      • 2018-12-16
      • 1970-01-01
      • 2014-08-18
      • 2018-06-20
      • 2011-04-09
      • 2015-07-02
      相关资源
      最近更新 更多