【问题标题】:C# & wpf - Unexpected behavior of (OneWay-Mode) chain-binding between ListBox-Label-ComboBoxC# & wpf - ListBox-Label-ComboBox 之间的 (OneWay-Mode) 链绑定的意外行为
【发布时间】:2019-06-04 15:37:59
【问题描述】:

我有以下奇怪的(对我来说)情况
ListBox 绑定(作为源)到具有 OneWay 模式的标签,即 ListBox 是只读的。 然后将 Label 绑定到具有 TwoWay 绑定的 ComboBox

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

奇怪的是,当程序启动并且用户通过 ListBox 中的列表进行选择时,所有 3 个控件的行为都符合预期。 但是一旦从 Combobox 中选择了一个索引,Label 就会继续正常工作(由 Combo 更新),但是与 ListBox 的 OneWay 绑定消失(为空)并且 ListBox 无法再更新 Label。

在我看来,当通过 OneWay 绑定之外的其他方式设置标签内容时(如此处使用 Combo 更新或可能使用 ValueConverter),WPF 会清除此绑定。

另一个奇怪的行为是,如果 ListBox 和 Label 之间的这个 OneWay 绑定变成了 TwoWay 绑定,那么一切都会完美运行。

问题是我做错了什么,或者如果这是正常行为,我在哪里可以找到相关文档。

请在下面找到演示案例的简化代码和 XAML。 我的解决方法是使用 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

<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>

编辑

相关文档:Dependency properties overview

本地值: 可以通过属性包装器的便利设置本地值,这也等同于在 XAML 中设置为属性或属性元素,或者通过调用 SetValue 方法使用特定实例的属性。如果您使用绑定或静态资源设置本地值,则它们各自的优先级就像设置了本地值一样,如果设置了新的本地值,绑定或资源引用将被删除。

再往下

如果您为最初持有 Binding 值的属性设置另一个本地值,您将完全覆盖该绑定,而不仅仅是该绑定的运行时值。

据我了解,此案例存在某种错误,已通过引入 DependencyObject 修复。SetCurrentValue The Control Local Values Bug Solution

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

在我看来,Combobox TwoWay 绑定仍在使用 SetValue,这就是为什么在使用我的 (combobox) 时 (label) 的绑定会被删除。

为了克服这个问题,我将 (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);
     }
  }

通过使用 SetCurrentValue,代码现在可以通过“模拟”TwoWay 模式按照最初的预期工作。

【问题讨论】:

  • 为避免这样的混淆,请将所有控件绑定到一个通用视图模型。请注意,与其将标签的内容绑定到SelectedItem.iLDX,不如将​​其绑定到SelectedValue,并在列表框上设置SelectedValuePath="iLDX",就像在组合框上所做的那样。
  • 感谢您的建议。此处显示的代码仅用于演示案例。我真正的 ListBox 有 15+ 个字段和其他类似的索引,所以 SelectedValuePath 不能在那里应用。不过你是对的,我在注释掉的代码中有一个令人困惑的声明,现在已经更正了。尽管如此,问题仍然存在。
  • @Clemens 有很好的建议。不要做控件之间的绑定,只绑定到 ViewModel。这样,即使您不知道绑定框架的细节,它也始终如您所愿地工作
  • 很抱歉,我没有使用任何 ViewModel,也不打算使用。我以老式的方式为爱好和娱乐编码。同时,我希望基本的语言功能能够正常工作,而不是通过北极去巴黎:)

标签: c# wpf data-binding two-way-binding


【解决方案1】:

没有什么奇怪的。数据绑定旨在以这种方式工作。当您将绑定分配给依赖项属性时,这意味着您将此依赖项属性的本地值更改为绑定表达式。并且绑定源提供的任何更新都将是此依赖属性的有效值。如果绑定以单向模式工作,则从其他绑定源对此依赖属性的任何更新都将覆盖本地值,从而导致绑定丢失。另一方面,由于两种模式都是假设更新绑定源,依赖对象会将任何非表达式值视为有效值,绑定将继续工作,直到您替换或清除它。

  • DependencyObject.GetValue 获取有效值。
  • DependencyObject.ReadLocalValue 获取本地值。
  • DependencyObject.SetValue 设置本地值。
  • DependencyObject.SetCurrentValue 设置有效值。
  • DependencyObject.ClearValue 清除本地值。

【讨论】:

  • 感谢您的回答。您能否提供一个链接,按照最初的要求记录这种行为?我问是因为,例如,当在 OneWay 模式下绑定 2 个文本框并且手动更新第二个文本框(目标,设置绑定)时,绑定继续正常运行,即在第一个(源)文本框中输入的所有文本都是通过绑定显示在第二个中,即使第二个的 Text 是手动独立修改的。但是当第二个 Textbox.Text 属性被代码改变时,绑定就被清除了,所以行为并不一致。
  • @john_m 其实我知道的没有任何官方文档。但是你可以从DependencyObject.SetValuereferencesource.microsoft.com/#WindowsBase/Base/System/Windows/…的源代码中挖掘出这个事实。并且编辑TextBox 将替换绑定表达式,因为如果通过编辑更改文本,而不是调用SetValueTextBox 调用SetCurrentDeferredVaule,这不会替换本地值以获得更好的性能。这也可以在TextBox.OnTextContainerChanged的源代码中找到。
  • 我找到了一些文档(请参阅我的编辑)。实际上存在某种错误,在我看来尚未完全解决,双向绑定会擦除现有绑定。无论如何,您提到 SetCurrentValue 帮助我解决了问题。
  • 我认为早期版本的 wpf 没有提供设置有效值的 api,这更像是设计失误而不是错误。由于在大多数情况下,我们想要获取有效值并设置本地值,所以GetValueSetValue的实现没有错。
  • MS 称其为错误,我只是在使用他们的话。无论如何谢谢:)
猜你喜欢
  • 2010-12-02
  • 2017-06-05
  • 2016-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-20
  • 1970-01-01
  • 2011-04-15
相关资源
最近更新 更多