我有一个ComboBox,它似乎无法更新SelectedItem/SelectedValue。
ComboBox的ItemsSource绑定到ViewModel类的一个属性上,该属性将一堆RAS电话簿条目列为CollectionView。然后,我分别将SelectedItem或SelectedValue绑定到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的值,因此DisplayMemberPath和SelectedValuePath都是ConnectionViewModel的Name属性。该ComboBox位于应用于窗口上ItemsControl的DataTemplate中,DataContext已设置为ViewModel实例。
ComboBox正确显示了列表项,并且我可以在UI中选择其中一个,但是当我从命令显示消息框时,PhonebookEntry属性仍然具有初始值而不是ComboBox中选定的值。其他TextBox实例更新得很好并显示在MessageBox中。
我在数据绑定ComboBox方面错过了什么?我已经搜索了很多,但似乎找不到我的错误。
这就是我看到的行为,但出于某些原因,在我特定的环境中它无法正常工作。
我有一个MainWindowViewModel,其中有一个ConnectionViewModels的CollectionView。在MainWindowView.xaml文件的代码后台中,我将DataContext设置为MainWindowViewModel。MainWindowView.xaml绑定到ConnectionViewModels集合的ItemsControl。我有一个DataTemplate,其中包含ComboBox以及其他一些TextBox。TextBox直接使用Binding 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之间流动得很顺畅。唯独下拉列表框没有工作。
关于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。我找不到任何可以澄清这个问题的文档。
即,
窗口 -> DataContext = MainWindowViewModel
..项 -> 绑定到DataContext.PhonebookEntries
....项 -> DataContext = PhonebookEntry(隐式关联)
我不知道这是否更好地解释了我的假设(?)。
为了确认我的假设,请将TextBox的绑定更改为
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
这将显示文本框绑定根(我正在将其与DataContext进行比较)是ConnectionViewModel实例。
<
T>
,并在属性getter中使用_list.AsReadOnly(),类似于您提到的方式。它的工作方式就像我最初希望的那样。此外,我想到了虽然ItemsSource绑定正常工作,但我可以只使用ViewModel中的Current属性来访问ComboBox中选择的项目。尽管如此,它仍然不像绑定ComboBoxes SelectedValue/SelectedItem属性那样自然。 - Geoff BennettItemsSource
属性绑定的集合更改为只读集合就可以使其正常工作。在我的情况下,我不得不将它从ObservableCollection
更改为ReadOnlyObservableCollection
。真是让人头疼。这是.NET 3.5版本 - 不确定它是否在4.0版本中修复了。 - ChrisWue