C# 和 wpf - ListBox-Label-ComboBox 之间(单向模式)链绑定的意外行为
C# & wpf - Unexpected behavior of (OneWay-Mode) chain-binding between ListBox-Label-ComboBox
我有以下奇怪的(对我来说)情况
ListBox 绑定(作为源)到具有单向模式的标签,即 ListBox 是只读的。
然后使用 TwoWay 绑定将 Label 绑定到 ComboBox
ListBox --> Label <--> ComboBox - arrows denote binding mode
奇怪的是,当程序启动并且用户通过 ListBox 中的列表进行选择时,所有 3 个控件都按预期运行。
但是一旦从 Combobox 中选择了一个索引,Label 继续正常工作(由 Combo 更新),但是绑定到 ListBox 的 OneWay 消失(为 null)并且 ListBox 无法再更新 Label。
在我看来,当通过 OneWay 绑定之外的其他方式设置标签内容时(如此处的 Combo 更新或可能使用 ValueConverter),此绑定将被 WPF 清除。
另一个奇怪的行为是,如果将 ListBox 和 Label 之间的这种单向绑定转换为双向绑定,那么一切都会完美无缺。
问题是我做错了什么,或者如果这是正常行为,我在哪里可以找到相关文档。
请在下面找到简化代码和 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
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.
再往下
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.
据我了解,存在与此案例相关的某种错误,已通过引入 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事件中输入以下内容(显示当前选中的Item),以便通过代码更新(label)不删除现有绑定
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,代码现在可以按照 "simulating" TwoWay 模式的最初预期运行。
没什么奇怪的。数据绑定旨在以这种方式工作。当您将绑定分配给依赖项 属性 时,这意味着您将此依赖项 属性 的本地值更改为绑定表达式。绑定源提供的任何更新都将是此依赖项的有效值属性。如果绑定以单向模式工作,则从其他绑定源对此依赖项 属性 的任何更新都将覆盖本地值,从而导致绑定丢失。另一方面,因为两种模式假设更新绑定源,依赖对象会将任何非表达式值计为有效值,绑定将一直有效,直到您替换或清除它。
DependencyObject.GetValue
获取有效值。
DependencyObject.ReadLocalValue
获取本地值。
DependencyObject.SetValue
设置本地值。
DependencyObject.SetCurrentValue
设置有效值。
DependencyObject.ClearValue
清除本地值。
我有以下奇怪的(对我来说)情况
ListBox 绑定(作为源)到具有单向模式的标签,即 ListBox 是只读的。
然后使用 TwoWay 绑定将 Label 绑定到 ComboBox
ListBox --> Label <--> ComboBox - arrows denote binding mode
奇怪的是,当程序启动并且用户通过 ListBox 中的列表进行选择时,所有 3 个控件都按预期运行。 但是一旦从 Combobox 中选择了一个索引,Label 继续正常工作(由 Combo 更新),但是绑定到 ListBox 的 OneWay 消失(为 null)并且 ListBox 无法再更新 Label。
在我看来,当通过 OneWay 绑定之外的其他方式设置标签内容时(如此处的 Combo 更新或可能使用 ValueConverter),此绑定将被 WPF 清除。
另一个奇怪的行为是,如果将 ListBox 和 Label 之间的这种单向绑定转换为双向绑定,那么一切都会完美无缺。
问题是我做错了什么,或者如果这是正常行为,我在哪里可以找到相关文档。
请在下面找到简化代码和 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
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.
再往下
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.
据我了解,存在与此案例相关的某种错误,已通过引入 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事件中输入以下内容(显示当前选中的Item),以便通过代码更新(label)不删除现有绑定
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,代码现在可以按照 "simulating" TwoWay 模式的最初预期运行。
没什么奇怪的。数据绑定旨在以这种方式工作。当您将绑定分配给依赖项 属性 时,这意味着您将此依赖项 属性 的本地值更改为绑定表达式。绑定源提供的任何更新都将是此依赖项的有效值属性。如果绑定以单向模式工作,则从其他绑定源对此依赖项 属性 的任何更新都将覆盖本地值,从而导致绑定丢失。另一方面,因为两种模式假设更新绑定源,依赖对象会将任何非表达式值计为有效值,绑定将一直有效,直到您替换或清除它。
DependencyObject.GetValue
获取有效值。DependencyObject.ReadLocalValue
获取本地值。DependencyObject.SetValue
设置本地值。DependencyObject.SetCurrentValue
设置有效值。DependencyObject.ClearValue
清除本地值。