C# 将 WPF ComboBox 绑定到自定义列表
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/561166/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Binding a WPF ComboBox to a custom list
提问by Geoff Bennett
I have a ComboBox that doesn't seem to update the SelectedItem/SelectedValue.
我有一个似乎没有更新 SelectedItem/SelectedValue 的 ComboBox。
The ComboBox ItemsSource is bound to a property on a ViewModel class that lists a bunch of RAS phonebook entries as a CollectionView. Then I've bound (at separate times) both the SelectedItem
or SelectedValue
to another property of the ViewModel. I have added a MessageBox into the save command to debug the values set by the databinding, but the SelectedItem
/SelectedValue
binding is not being set.
ComboBox ItemsSource 绑定到 ViewModel 类上的一个属性,该类将一堆 RAS 电话簿条目列为 CollectionView。然后我(在不同的时间)将SelectedItem
或绑定SelectedValue
到 ViewModel 的另一个属性。我在 save 命令中添加了一个 MessageBox 来调试数据绑定设置的值,但没有设置SelectedItem
/SelectedValue
绑定。
The ViewModel class looks something like this:
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");
}
}
}
The _phonebookEntries collection is being initialised in the constructor from a business object. The ComboBox XAML looks something like this:
_phonebookEntries 集合正在构造函数中从业务对象初始化。ComboBox XAML 看起来像这样:
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=PhonebookEntry}" />
I am only interested in the actual string value displayed in the ComboBox, not any other properties of the object as this is the value I need to pass across to RAS when I want to make the VPN connection, hence DisplayMemberPath
and SelectedValuePath
are both the Name property of the ConnectionViewModel. The ComboBox is in a DataTemplate
applied to an ItemsControl
on a Window who's DataContext has been set to a ViewModel instance.
我只是在实际字符串值兴趣显示在组合框,而不是对象,因为这任何其他属性是我需要跨传递给RAS时,我想使VPN连接,因此价值DisplayMemberPath
和SelectedValuePath
是双方的名称属性连接视图模型。ComboBoxDataTemplate
应用到ItemsControl
Window 上,其 DataContext 已设置为 ViewModel 实例。
The ComboBox displays the list of items correctly, and I can select one in the UI with no problem. However when I display the message box from the command, the PhonebookEntry property still has the initial value in it, not the selected value from the ComboBox. Other TextBox instances are updating fine and displaying in the MessageBox.
ComboBox 正确显示项目列表,我可以毫无问题地在 UI 中选择一个。但是,当我从命令中显示消息框时,PhonebookEntry 属性中仍然具有初始值,而不是从 ComboBox 中选择的值。其他 TextBox 实例正在更新并显示在 MessageBox 中。
What am I missing with databinding the ComboBox? I've done a lot of searching and can't seem to find anything that I'm doing wrong.
数据绑定 ComboBox 时我缺少什么?我做了很多搜索,似乎找不到任何我做错的地方。
This is the behaviour I'm seeing, however it's not working for some reason in my particular context.
这是我看到的行为,但是由于某种原因在我的特定上下文中它不起作用。
I have a MainWindowViewModel which has a CollectionView
of ConnectionViewModels. In the MainWindowView.xaml file code-behind, I set the DataContext to the MainWindowViewModel. The MainWindowView.xaml has an ItemsControl
bound to the collection of ConnectionViewModels. I have a DataTemplate that holds the ComboBox as well as some other TextBoxes. The TextBoxes are bound directly to properties of the ConnectionViewModel using Text="{Binding Path=ConnectionName}"
.
我有一个 MainWindowViewModel,它有一个CollectionView
ConnectionViewModels。在 MainWindowView.xaml 文件代码隐藏中,我将 DataContext 设置为 MainWindowViewModel。MainWindowView.xamlItemsControl
绑定到 ConnectionViewModels 的集合。我有一个包含 ComboBox 以及其他一些 TextBox 的 DataTemplate。使用 .TextBoxes 直接绑定到 ConnectionViewModel 的属性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; }
}
The XAML code-behind:
XAML 代码隐藏:
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
Then XAML:
然后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}" />
The TextBoxes all bind correctly, and data moves between them and the ViewModel with no trouble. It's only the ComboBox that isn't working.
TextBoxes 都正确绑定,数据在它们和 ViewModel 之间移动没有问题。只有 ComboBox 不起作用。
You are correct in your assumption regarding the PhonebookEntry class.
您对 PhonebookEntry 类的假设是正确的。
The assumption I am making is that the DataContext used by my DataTemplate is automatically set through the binding hierarchy, so that I don't have to explicitly set it for each item in the ItemsControl
. That would seem a bit silly to me.
我所做的假设是我的 DataTemplate 使用的 DataContext 是通过绑定层次结构自动设置的,因此我不必为ItemsControl
. 这对我来说似乎有点傻。
Here is a test implementation that demonstrates the problem, based on the example above.
这是一个基于上述示例的测试实现,用于演示该问题。
XAML:
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>
The code-behind:
在后台代码:
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; }
}
}
}
If you run that example, you will get the behaviour I'm talking about. The TextBox updates its binding fine when you edit it, but the ComboBox does not. Very confusing seeing as really the only thing I've done is introduce a parent ViewModel.
如果你运行那个例子,你会得到我正在谈论的行为。当您编辑 TextBox 时,它会很好地更新其绑定,但 ComboBox 不会。非常令人困惑,因为我所做的唯一一件事就是引入父 ViewModel。
I am currently labouring under the impression that an item bound to the child of a DataContext has that child as its DataContext. I can't find any documentation that clears this up one way or the other.
我目前的印象是绑定到 DataContext 的子项的项目将该子项作为其 DataContext。我找不到任何可以以一种或另一种方式解决这个问题的文档。
I.e.,
IE,
Window -> DataContext = MainWindowViewModel
..Items -> Bound to DataContext.PhonebookEntries
....Item -> DataContext = PhonebookEntry (implicitly associated)
Window -> DataContext = MainWindowViewModel
..Items -> Bound to DataContext.PhonebookEntries
....Item -> DataContext = PhonebookEntry(隐式关联)
I don't know if that explains my assumption any better(?).
我不知道这是否能更好地解释我的假设(?)。
To confirm my assumption, change the binding of the TextBox to be
为了确认我的假设,将 TextBox 的绑定更改为
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
And this will show the TextBox binding root (which I'm comparing to the DataContext) is the ConnectionViewModel instance.
这将显示 TextBox 绑定根(我将其与 DataContext 进行比较)是 ConnectionViewModel 实例。
采纳答案by Kjetil Watnedal
You set the DisplayMemberPath and the SelectedValuePath to "Name", so I assume that you have a class PhoneBookEntry with a public property Name.
您将 DisplayMemberPath 和 SelectedValuePath 设置为“Name”,因此我假设您有一个带有公共属性 Name 的类 PhoneBookEntry。
Have you set the DataContext to your ConnectionViewModel object?
您是否将 DataContext 设置为 ConnectionViewModel 对象?
I copied you code and made some minor modifications, and it seems to work fine. I can set the viewmodels PhoneBookEnty property and the selected item in the combobox changes, and I can change the selected item in the combobox and the view models PhoneBookEntry property is set correctly.
我复制了你的代码并做了一些小的修改,它似乎工作正常。我可以设置 viewmodels PhoneBookEnty 属性,组合框中的所选项目发生变化,我可以更改组合框中的所选项目,并且视图模型 PhoneBookEntry 属性设置正确。
Here is my XAML content:
这是我的 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>
And here is my code-behind:
这是我的代码隐藏:
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;
}
}
Edit:Geoffs second example does not seem to work, which seems a bit odd to me. If I change the PhonebookEntries property on the ConnectionViewModel to be of type ReadOnlyCollection, the TwoWay binding of the SelectedValue property on the combobox works fine.
编辑:Geoffs 第二个例子似乎不起作用,这对我来说似乎有点奇怪。如果我将 ConnectionViewModel 上的 PhonebookEntries 属性更改为 ReadOnlyCollection 类型,则组合框上 SelectedValue 属性的 TwoWay 绑定工作正常。
Maybe there is an issue with the CollectionView? I noticed a warning in the output console:
也许 CollectionView 有问题?我注意到输出控制台中有一个警告:
System.Windows.Data Warning: 50 : Using CollectionView directly is not fully supported. The basic features work, although with some inefficiencies, but advanced features may encounter known bugs. Consider using a derived class to avoid these problems.
System.Windows.Data 警告:50:不完全支持直接使用 CollectionView。基本功能虽然效率低下,但高级功能可能会遇到已知错误。考虑使用派生类来避免这些问题。
Edit2 (.NET 4.5):The content of the DropDownList can be based on ToString() and not of DisplayMemberPath, while DisplayMemberPath specifies the member for the selected and displayed item only.
Edit2 (.NET 4.5):DropDownList 的内容可以基于 ToString() 而不是 DisplayMemberPath,而 DisplayMemberPath 仅指定所选和显示项目的成员。
回答by CyberMonk
I had what at first seemed to be an identical problem, but it turned out to be due to an NHibernate/WPF compatibility issue. The problem was caused by the way WPF checks for object equality. I was able to get my stuff to work by using the object ID property in the SelectedValue and SelectedValuePath properties.
我遇到了起初似乎是相同的问题,但结果证明是由于 NHibernate/WPF 兼容性问题。问题是由 WPF 检查对象相等性的方式引起的。通过使用 SelectedValue 和 SelectedValuePath 属性中的对象 ID 属性,我能够让我的东西工作。
<ComboBox Name="CategoryList"
DisplayMemberPath="CategoryName"
SelectedItem="{Binding Path=CategoryParent}"
SelectedValue="{Binding Path=CategoryParent.ID}"
SelectedValuePath="ID">
See the blog post from Chester, The WPF ComboBox - SelectedItem, SelectedValue, and SelectedValuePath with NHibernate, for details.
有关详细信息,请参阅 Chester 的博客文章The WPF ComboBox - SelectedItem、SelectedValue 和 SelectedValuePath with NHibernate。
回答by Roy
To bind the data to ComboBox
将数据绑定到 ComboBox
List<ComboData> ListData = new List<ComboData>();
ListData.Add(new ComboData { Id = "1", Value = "One" });
ListData.Add(new ComboData { Id = "2", Value = "Two" });
ListData.Add(new ComboData { Id = "3", Value = "Three" });
ListData.Add(new ComboData { Id = "4", Value = "Four" });
ListData.Add(new ComboData { Id = "5", Value = "Five" });
cbotest.ItemsSource = ListData;
cbotest.DisplayMemberPath = "Value";
cbotest.SelectedValuePath = "Id";
cbotest.SelectedValue = "2";
ComboData looks like:
组合数据看起来像:
public class ComboData
{
public int Id { get; set; }
public string Value { get; set; }
}
回答by phifi
I had a similar issue where the SelectedItem never got updated.
我有一个类似的问题,其中 SelectedItem 从未更新过。
My problem was that the selected item was not the same instance as the item contained in the list. So I simply had to override the Equals() method in my MyCustomObject and compare the IDs of those two instances to tell the ComboBox that it's the same object.
我的问题是所选项目与列表中包含的项目不同。所以我只需要覆盖 MyCustomObject 中的 Equals() 方法并比较这两个实例的 ID 来告诉 ComboBox 它是同一个对象。
public override bool Equals(object obj)
{
return this.Id == (obj as MyCustomObject).Id;
}