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

*爱你&永不变心* 提交于 2020-12-27 06:36:26

问题


I have the following strange (for me) situation
A ListBox is bound (as Source) to a Label with OneWay Mode, i.e. ListBox is read-only. The Label is then bound to a ComboBox with TwoWay binding

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

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.

The other strange behavior is that if this OneWay binding between ListBox and Label is turned into a TwoWay one, then everything works perfectly.

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. My workaround is to set the Label Content with code in 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>

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

As I understand, there was some kind of bug related to this case, fixed with the introduction of 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.

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.

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

  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.


回答1:


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.ReadLocalValue gets the local value.
  • DependencyObject.SetValue sets the local value.
  • DependencyObject.SetCurrentValue sets the effective value.
  • DependencyObject.ClearValue clears the local value.


来源:https://stackoverflow.com/questions/54117308/c-sharp-wpf-unexpected-behavior-of-oneway-mode-chain-binding-between-listb

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!