将WPF组合框绑定到自定义列表

我有一个组合框,似乎没有更新SelectedItem/SelectedValue。

ComboBox ItemsSource绑定到ViewModel类上的一个属性,该属性将一系列RAS电话簿条目作为CollectionView列出。然后我将SelectedItemSelectedValue(在不同的时间)绑定到ViewModel的另一个属性。我已经在保存命令中添加了一个MessageBox来调试由数据绑定设置的值,但是没有设置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的值,因此DisplayMemberPathSelectedValuePath都是ConnectionViewModel的Name属性。组合框位于DataTemplate中,应用于窗口上的ItemsControl,其DataContext已设置为ViewModel实例。

组合框可以正确地显示项目列表,我可以在UI中毫无问题地选择一个。然而,当我从命令中显示消息框时,PhonebookEntry属性仍然有初始值,而不是从组合框中选择的值。其他文本框实例更新良好,并显示在消息框中。

我错过了什么与数据绑定组合框?我做了很多搜索,似乎找不到任何我做错的地方。


这是我所看到的行为,但在我的特定环境中,由于某种原因,它不起作用。

我有一个MainWindowViewModel,它有一个ConnectionViewModels的CollectionView。在MainWindowView中。xaml文件代码背后,我设置DataContext为MainWindowViewModel。MainWindowView。xaml有一个ItemsControl绑定到ConnectionViewModels集合。我有一个数据模板,持有组合框以及其他一些文本框。文本框使用Text="{Binding Path=ConnectionName}"直接绑定到ConnectionViewModel的属性。

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; }
}
}
}

如果你运行这个例子,你就会得到我所说的行为。当你编辑文本框时,文本框可以很好地更新它的绑定,但组合框却不能。我唯一做的就是引入父ViewModel,这很让人困惑。

我目前的印象是,一个绑定到一个DataContext的子元素的项目将该子元素作为它的DataContext。我找不到任何能证明这一点的文件。

也就是说,

< p >窗口→DataContext = MainWindowViewModel
. DataContext = MainWindowViewModel ..项→绑定到DataContext。PhonebookEntries < br / > ....项→DataContext = PhonebookEntry(隐式关联)

. DataContext = PhonebookEntry(隐式关联

我不知道这是否能更好地解释我的假设(?)


为了确认我的假设,将TextBox的绑定更改为

<TextBox Text="{Binding Mode=OneWay}" Width="50" />

这将显示TextBox绑定根(我比较DataContext)是ConnectionViewModel实例。

657909 次浏览

您将DisplayMemberPath和SelectedValuePath设置为"Name",因此我假设您有一个PhoneBookEntry类,它具有公共属性Name。

你设置DataContext到你的ConnectionViewModel对象了吗?

我复制了你的代码并做了一些小的修改,它似乎工作得很好。 我可以设置viewmodels PhoneBookEntry属性并且在组合框中所选的项目发生变化,我可以在组合框中更改所选项目并且视图模型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属性的双向绑定工作正常。

也许是CollectionView有问题?我在输出控制台中注意到一个警告:

System.Windows.Data警告:50:不完全支持直接使用CollectionView。基本功能可以工作,尽管有一些低效率,但高级功能可能会遇到已知的错误。考虑使用派生类来避免这些问题。

Edit2(。NET 4.5):下拉列表的内容可以基于ToString(),而不是DisplayMemberPath,而DisplayMemberPath仅指定所选和显示项的成员。

我有什么一开始似乎是相同的问题,但它原来是由于NHibernate/WPF兼容性问题。该问题是由WPF检查对象相等性的方式引起的。我能够让我的东西通过使用SelectedValue和SelectedValuePath属性中的对象ID属性来工作。

<ComboBox Name="CategoryList"
DisplayMemberPath="CategoryName"
SelectedItem="{Binding Path=CategoryParent}"
SelectedValue="{Binding Path=CategoryParent.ID}"
SelectedValuePath="ID">

详见Chester的博客文章, WPF组合框-SelectedItem, SelectedValue, SelectedValuePath与NHibernate

将数据绑定到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看起来像:

public class ComboData
{
public int Id { get; set; }
public string Value { get; set; }
}

(注意IdValue必须是属性,而不是类字段)

我有一个类似的问题,其中SelectedItem从未更新。

我的问题是所选项目与列表中包含的项目不是同一个实例。所以我必须重写MyCustomObject中的Equals()方法,并比较这两个实例的id,以告诉ComboBox它是相同的对象。

public override bool Equals(object obj)
{
return this.Id == (obj as MyCustomObject).Id;
}