【发布时间】: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