简体   繁体   English

C#和WPF-ListBox-Label-ComboBox之间的(OneWay-Mode)链绑定的意外行为

[英]C# & wpf - Unexpected behavior of (OneWay-Mode) chain-binding between ListBox-Label-ComboBox

I have the following strange (for me) situation 我有以下奇怪的情况(对我来说)
A ListBox is bound (as Source) to a Label with OneWay Mode, ie ListBox is read-only. ListBox通过OneWay模式绑定(作为源)到Label,即ListBox是只读的。 The Label is then bound to a ComboBox with TwoWay binding 然后使用TwoWay绑定将Label绑定到ComboBox

ListBox --> Label <--> ComboBox - arrows denote binding mode

The strange thing is that when the program starts and the user selects through the list in the ListBox, all 3 controls behave as expected. 奇怪的是,当程序启动并且用户在列表框的列表中进行选择时,所有3个控件的行为均与预期的一样。 But as soon as one index is chosen from Combobox, the Label continues to work properly (is updated by the Combo), but the OneWay binding to ListBox disappears (is null) and the ListBox cannot update the Label any more. 但是,一旦从Combobox中选择了一个索引,Label就会继续正常工作(由Combo更新),但是与ListBox的OneWay绑定会消失(为null),并且ListBox无法再更新Label。

It seems to me that when Label Content is set by other means besides the OneWay binding (as here with the Combo updating or maybe with a ValueConverter), this binding is cleared by WPF. 在我看来,当通过OneWay绑定(如此处的Combo更新或使用ValueConverter)以外的其他方式设置Label Content时,WPF会清除此绑定。

The other strange behavior is that if this OneWay binding between ListBox and Label is turned into a TwoWay one, then everything works perfectly. 另一个奇怪的行为是,如果将ListBox和Label之间的OneWay绑定转换为TwoWay,则一切正常。

The question is what am I doing wrong, or if this is the normal behavior, where could I find relevant documentation. 问题是我在做什么错,或者这是正常现象,在哪里可以找到相关文档。

Please find below simplified code and XAML demonstrating the case. 请在下面找到简化的代码和XAML演示案例。 My workaround is to set the Label Content with code in ListBox_SelectionChanged. 我的解决方法是使用ListBox_SelectionChanged中的代码设置标签内容。

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Test_Chained_controls
{
   public partial class MainWindow : Window
   {
      public class ComboItems
      {
         public int iDX { get; set; }
         public string sDesc { get; set; }

         public ComboItems(int a, string b)
         {
            iDX = a;
            sDesc = b;
         }
      }

      public class ListItems
      {
         public int iLDX { get; set; }
         public ListItems(int a)
         {
            iLDX = a;
         }
      }

      public List<ListItems> intList = new List<ListItems>();
      public List<ComboItems> idx_StrList = new List<ComboItems>();

      public MainWindow()
      {
         InitializeComponent();

         intList.Add(new ListItems(0));
         intList.Add(new ListItems(1));
         intList.Add(new ListItems(2));
         intList.Add(new ListItems(3));

         idx_StrList.Add(new ComboItems(0, "Zero"));
         idx_StrList.Add(new ComboItems(1, "One"));
         idx_StrList.Add(new ComboItems(2, "Two"));
         idx_StrList.Add(new ComboItems(3, "Three"));
      }

      private void Window_Loaded(object sender, RoutedEventArgs e)
      {
         listBox.ItemsSource = intList;
         comboBox.ItemsSource = idx_StrList;
      }

      private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
      {
         //// Set Label Content in case of OneWay
         // var binding = BindingOperations.GetBinding(label, Label.ContentProperty);
         // if (binding != null)
         // {
         //    if (binding.Mode == BindingMode.OneWay)
         //       {}  // Binding set - do nothing
         // }
         // else label.Content = listBox.SelectedItem;
      }
   }
}

XAML XAML

<Window ... normal stuff
        xmlns:local="clr-namespace:Test_Chained_controls"
        mc:Ignorable="d"
        Title="MainWindow" Height="182" Width="500" Loaded="Window_Loaded">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="140"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="100"/>
        </Grid.RowDefinitions>

        <Label Content="ListBox"    Grid.Row="0" Grid.Column="0" Margin="20,10,0,0" />
        <Label Content="Label"      Grid.Row="0" Grid.Column="1" Margin="20,10,0,0" />
        <Label Content="ComboBox"   Grid.Row="0" Grid.Column="2" Margin="20,10,0,0" />

        <ListBox x:Name="listBox"   Grid.Row="1" Grid.Column="0" Margin="0"  
                 DisplayMemberPath="iLDX" 
                 SelectedIndex="0"
                 IsSynchronizedWithCurrentItem="True" 
                 SelectionChanged="ListBox_SelectionChanged"/>

        <Border BorderThickness="1" Grid.Row="1" Grid.Column="1" Height="30" 
                Margin="20,20,0,0" BorderBrush="#FFACACAC"  >

            <!-- *********** Label with Mode=OneWay or TwoWay *********** -->
            <Label x:Name="label" Width="100" Height="25"
                   Content="{Binding ElementName=listBox, 
                             Path=SelectedItem.iLDX, Mode=OneWay }" />
        </Border>

        <ComboBox x:Name="comboBox" Grid.Row="1" Grid.Column="2" 
                                   Height="30" Margin="20,20,0,0"  

                  DisplayMemberPath="sDesc" 
                  SelectedValue="{Binding ElementName=label, Path=Content, 
                  TargetNullValue=0, FallbackValue=0, Mode=TwoWay}"
                  SelectedValuePath="iDX"  />
    </Grid>
</Window>

EDIT 编辑

Relevant documentation: Dependency properties overview 相关文档: 依赖项属性概述

Local value: A local value might be set through the convenience of the property wrapper, which also equates to setting as an attribute or property element in XAML, or by a call to the SetValue method using a property of a specific instance. 局部值:局部值可以通过属性包装器的便利性来设置,这也等同于在XAML中设置为属性或属性元素,或者通过使用特定实例的属性来调用SetValue方法。 If you set a local value by using a binding or a static resource, these each act in the precedence as if a local value was set, and bindings or resource references are erased if a new local value is set. 如果您通过使用绑定或静态资源来设置本地值,则它们各自的优先级就好像设置了本地值一样, 并且如果设置了新的本地值,则绑定或资源引用将被删除。

and further down 再往下走

If you set another local value for a property that originally held a Binding value, you will overwrite the binding entirely, not just the binding's run-time value. 如果为原来保留有Binding值的属性设置了另一个本地值, 则将完全覆盖绑定,而不仅是绑定的运行时值。

As I understand, there was some kind of bug related to this case, fixed with the introduction of DependencyObject. 据我了解,这种情况下存在某种错误,并通过引入DependencyObject进行了修复。 SetCurrentValue The Control Local Values Bug Solution SetCurrentValue 控件局部值错误解决方案

public void SetCurrentValue (System.Windows.DependencyProperty dp, object value);
// Sets the value of a dependency property without changing its value source.

It seems to me that Combobox TwoWay binding is still using SetValue , and that's why the binding for (label) gets erased when my (combobox) is used. 在我看来,Combobox TwoWay绑定仍在使用SetValue ,这就是为什么使用我的(combobox)时会删除(label)的绑定的原因。

To overcome this, I changed the TwoWay binding of (comboBox) to OneWay, and entered the following in the comboBox_DropDownClosed event (showing the currently selected Item), in order to update (label) by code without erasing the existing binding 为了克服这个问题,我将(comboBox)的TwoWay绑定更改为OneWay,并在comboBox_DropDownClosed事件(显示当前选择的项)中输入了以下内容,以便在不删除现有绑定的情况下通过代码更新(标签)

  private void comboBox_DropDownClosed(object sender, System.EventArgs e)
  {
     Binding binding = BindingOperations.GetBinding(label, Label.ContentProperty);
     if (binding != null)
     {
        ComboItems ComboItem = comboBox.SelectedItem as ComboItems;
        int iDX = ComboItem.iDX;

        // Set label value without affecting existing binding
        label.SetCurrentValue(Label.ContentProperty, iDX);
     }
  }

With the use of SetCurrentValue , code works now as originally intended by "simulating" the TwoWay mode. 通过使用SetCurrentValue ,代码现在可以通过“模拟” TwoWay模式原本预期的工作。

There's nothing strange at all. 一点也不奇怪。 Data binding is designed to work this way. 数据绑定旨在以这种方式工作。 When you assign a binding to a dependency property, it means you change the local value of this dependency property to a binding expression. 当您将绑定分配给依赖项属性时,这意味着您将此依赖项属性的本地值更改为绑定表达式。 And any update provide by the binding source will be the effective value of this dependency property. 绑定源提供的任何更新将是此依赖项属性的有效值。 If the binding is working in one way mode, any update to this dependency property from other then binding source, will overwrite the local value, result in losing the binding. 如果绑定以单向方式工作,则从其他绑定源开始对此依赖项属性进行的任何更新都将覆盖本地值,从而导致丢失绑定。 On the other side, becuase two mode is suppose to update the binding source, dependency object will count any non-expression value as effective value, binding will keep working until you replace or clear it. 另一方面,由于假设两种方式都将更新绑定源,所以依赖对象会将任何非表达式值都视为有效值,绑定将一直起作用,直到您替换或清除它为止。

  • DependencyObject.GetValue gets the effective value. DependencyObject.GetValue获取有效值。
  • DependencyObject.ReadLocalValue gets the local value. DependencyObject.ReadLocalValue获取本地值。
  • DependencyObject.SetValue sets the local value. DependencyObject.SetValue设置本地值。
  • DependencyObject.SetCurrentValue sets the effective value. DependencyObject.SetCurrentValue设置有效值。
  • DependencyObject.ClearValue clears the local value. DependencyObject.ClearValue清除本地值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM