我有一个似乎没有更新 SelectedItem/SelectedValue 的组合框。
ComboBox ItemsSource 绑定到 ViewModel 类的一个属性,该类将一堆 RAS 电话簿条目列为 CollectionView。然后我已经(在不同的时间)绑定SelectedItem或绑定SelectedValue到 ViewModel 的另一个属性。我在 save 命令中添加了一个 MessageBox 来调试数据绑定设置的值,但是SelectedItem/SelectedValue绑定没有被设置。
SelectedItem
SelectedValue
ViewModel 类看起来像这样:
public ConnectionViewModel { private readonly CollectionView _phonebookEntries; private string _phonebookeEntry; public CollectionView PhonebookEntries { get { return _phonebookEntries; } } public string PhonebookEntry { get { return _phonebookEntry; } set { if (_phonebookEntry == value) return; _phonebookEntry = value; OnPropertyChanged("PhonebookEntry"); } } }
_phonebookEntries 集合正在从业务对象的构造函数中初始化。ComboBox XAML 看起来像这样:
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path=PhonebookEntry}" />
我只对 ComboBox 中显示的实际字符串值感兴趣,而不是对象的任何其他属性,因为这是我在建立 VPN 连接时需要传递给 RAS 的值,因此DisplayMemberPath它们SelectedValuePath都是的 Name 属性连接视图模型。ComboBoxDataTemplate应用于ItemsControl其 DataContext 已设置为 ViewModel 实例的 Window 上。
DisplayMemberPath
SelectedValuePath
DataTemplate
ItemsControl
ComboBox 正确显示项目列表,我可以在 UI 中毫无问题地选择一个。但是,当我从命令中显示消息框时,PhonebookEntry 属性仍然具有初始值,而不是 ComboBox 中的选定值。其他 TextBox 实例正在更新并显示在 MessageBox 中。
对 ComboBox 进行数据绑定时我缺少什么?我做了很多搜索,似乎找不到任何我做错的事情。
这是我看到的行为,但是在我的特定上下文中由于某种原因它不起作用。
我有一个 MainWindowViewModel,它有一个CollectionViewConnectionViewModels。在 MainWindowView.xaml 文件代码隐藏中,我将 DataContext 设置为 MainWindowViewModel。MainWindowView.xamlItemsControl绑定到 ConnectionViewModels 的集合。我有一个包含 ComboBox 以及其他一些 TextBoxes 的 DataTemplate。文本框直接绑定到 ConnectionViewModel 的属性,使用Text="{Binding Path=ConnectionName}".
CollectionView
Text="{Binding Path=ConnectionName}"
public class ConnectionViewModel : ViewModelBase { public string Name { get; set; } public string Password { get; set; } } public class MainWindowViewModel : ViewModelBase { // List<ConnectionViewModel>... public CollectionView Connections { get; set; } }
XAML 代码隐藏:
public partial class Window1 { public Window1() { InitializeComponent(); DataContext = new MainWindowViewModel(); } }
然后是 XAML:
<DataTemplate x:Key="listTemplate"> <Grid> <ComboBox ItemsSource="{Binding Path=PhonebookEntries}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path=PhonebookEntry}" /> <TextBox Text="{Binding Path=Password}" /> </Grid> </DataTemplate> <ItemsControl ItemsSource="{Binding Path=Connections}" ItemTemplate="{StaticResource listTemplate}" />
文本框都正确绑定,数据在它们和 ViewModel 之间移动没有问题。只有 ComboBox 不起作用。
您对 PhonebookEntry 类的假设是正确的。
我所做的假设是我的 DataTemplate 使用的 DataContext 是通过绑定层次结构自动设置的,因此我不必为ItemsControl. 这对我来说似乎有点愚蠢。
这是基于上面示例的演示问题的测试实现。
XAML:
<Window x:Class="WpfApplication7.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Window.Resources> <DataTemplate x:Key="itemTemplate"> <StackPanel Orientation="Horizontal"> <TextBox Text="{Binding Path=Name}" Width="50" /> <ComboBox ItemsSource="{Binding Path=PhonebookEntries}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path=PhonebookEntry}" Width="200"/> </StackPanel> </DataTemplate> </Window.Resources> <Grid> <ItemsControl ItemsSource="{Binding Path=Connections}" ItemTemplate="{StaticResource itemTemplate}" /> </Grid> </Window>
代码 隐藏:
namespace WpfApplication7 { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); DataContext = new MainWindowViewModel(); } } public class PhoneBookEntry { public string Name { get; set; } public PhoneBookEntry(string name) { Name = name; } } public class ConnectionViewModel : INotifyPropertyChanged { private string _name; public ConnectionViewModel(string name) { _name = name; IList<PhoneBookEntry> list = new List<PhoneBookEntry> { new PhoneBookEntry("test"), new PhoneBookEntry("test2") }; _phonebookEntries = new CollectionView(list); } private readonly CollectionView _phonebookEntries; private string _phonebookEntry; public CollectionView PhonebookEntries { get { return _phonebookEntries; } } public string PhonebookEntry { get { return _phonebookEntry; } set { if (_phonebookEntry == value) return; _phonebookEntry = value; OnPropertyChanged("PhonebookEntry"); } } public string Name { get { return _name; } set { if (_name == value) return; _name = value; OnPropertyChanged("Name"); } } private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; } public class MainWindowViewModel { private readonly CollectionView _connections; public MainWindowViewModel() { IList<ConnectionViewModel> connections = new List<ConnectionViewModel> { new ConnectionViewModel("First"), new ConnectionViewModel("Second"), new ConnectionViewModel("Third") }; _connections = new CollectionView(connections); } public CollectionView Connections { get { return _connections; } } } }
如果您运行该示例,您将得到我正在谈论的行为。TextBox 在您编辑它时会更新其绑定,但 ComboBox 不会。看到我所做的唯一一件事就是引入一个父 ViewModel 非常令人困惑。
我目前的印象是绑定到 DataContext 的子项的项具有该子项作为其 DataContext。我找不到任何可以以一种或另一种方式解决此问题的文档。
IE,
Window -> DataContext = MainWindowViewModel ..Items -> 绑定到 DataContext.PhonebookEntries ....Item -> DataContext = PhonebookEntry(隐式关联)
我不知道这是否更好地解释了我的假设(?)。
为了确认我的假设,将 TextBox 的绑定更改为
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
这将显示 TextBox 绑定根(我将其与 DataContext 进行比较)是 ConnectionViewModel 实例。
您将 DisplayMemberPath 和 SelectedValuePath 设置为“Name”,因此我假设您有一个具有公共属性 Name 的类 PhoneBookEntry。
您是否将 DataContext 设置为 ConnectionViewModel 对象?
我复制了你的代码并做了一些小的修改,它似乎工作正常。我可以设置 viewmodels PhoneBookEnty 属性并且组合框中的选定项发生更改,并且我可以更改组合框中的选定项并且视图模型 PhoneBookEntry 属性设置正确。
这是我的 XAML 内容:
<Window x:Class="WpfApplication6.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Grid> <StackPanel> <Button Click="Button_Click">asdf</Button> <ComboBox ItemsSource="{Binding Path=PhonebookEntries}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path=PhonebookEntry}" /> </StackPanel> </Grid> </Window>
这是我的代码隐藏:
namespace WpfApplication6 { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); ConnectionViewModel vm = new ConnectionViewModel(); DataContext = vm; } private void Button_Click(object sender, RoutedEventArgs e) { ((ConnectionViewModel)DataContext).PhonebookEntry = "test"; } } public class PhoneBookEntry { public string Name { get; set; } public PhoneBookEntry(string name) { Name = name; } public override string ToString() { return Name; } } public class ConnectionViewModel : INotifyPropertyChanged { public ConnectionViewModel() { IList<PhoneBookEntry> list = new List<PhoneBookEntry>(); list.Add(new PhoneBookEntry("test")); list.Add(new PhoneBookEntry("test2")); _phonebookEntries = new CollectionView(list); } private readonly CollectionView _phonebookEntries; private string _phonebookEntry; public CollectionView PhonebookEntries { get { return _phonebookEntries; } } public string PhonebookEntry { get { return _phonebookEntry; } set { if (_phonebookEntry == value) return; _phonebookEntry = value; OnPropertyChanged("PhonebookEntry"); } } private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; } }
编辑: Geoffs 的第二个例子似乎不起作用,这对我来说似乎有点奇怪。如果我 将 ConnectionViewModel 上的 PhonebookEntries 属性更改为 ReadOnlyCollection 类型 ,则组合框上 SelectedValue 属性的 TwoWay 绑定可以正常工作。
可能是 CollectionView 有问题?我注意到输出控制台中有一个警告:
System.Windows.Data 警告:50:不完全支持直接使用 CollectionView。基本功能工作,虽然效率低下,但高级功能可能会遇到已知的错误。考虑使用派生类来避免这些问题。
Edit2 (.NET 4.5): DropDownList 的内容可以基于 ToString() 而不是 DisplayMemberPath,而 DisplayMemberPath 仅指定所选和显示项目的成员。