У меня есть 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, а не любые другие свойства объекта, поскольку это значение, которое мне нужно передать в RAS, когда я хочу установить VPN-соединение, следовательно, DisplayMemberPath
и SelectedValuePath
оба являются свойством Name ConnectionViewModel. ComboBox DataTemplate
применяется к ItemsControl
окну, для которого DataContext был установлен в экземпляр ViewModel.
ComboBox отображает список элементов правильно, и я могу выбрать один в пользовательском интерфейсе без проблем. Однако, когда я отображаю окно сообщения из команды, свойство PhonebookEntry по-прежнему имеет начальное значение, а не выбранное значение из ComboBox. Другие экземпляры TextBox обновляются нормально и отображаются в MessageBox.
Чего мне не хватает в привязке данных ComboBox? Я много искал и не могу найти ничего, что я делаю неправильно.
Это поведение, которое я вижу, однако по какой-то причине оно не работает в моем конкретном контексте.
У меня есть MainWindowViewModel, которая имеет CollectionView
ConnectionViewModels. В коде файла MainWindowView.xaml я установил для DataContext значение MainWindowViewModel. MainWindowView.xaml имеет ItemsControl
привязку к коллекции ConnectionViewModels. У меня есть DataTemplate, который содержит ComboBox, а также некоторые другие TextBox. 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; }
}
Код 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.
Я предполагаю, что DataContext, используемый моим DataTemplate, автоматически устанавливается через иерархию привязки, поэтому мне не нужно явно устанавливать его для каждого элемента в 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. Я не могу найти документацию, которая проясняет это так или иначе.
То есть,
Окно -> DataContext = MainWindowViewModel
..Items -> Связано с DataContext.PhonebookEntries
.... Item -> DataContext = PhonebookEntry (неявно связано)
Я не знаю, объясняет ли это мое предположение лучше (?).
Чтобы подтвердить мое предположение, измените привязку TextBox на
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
И это покажет, что корнем привязки TextBox (который я сравниваю с DataContext) является экземпляр ConnectionViewModel.
<
T>
и в методе получения свойств с помощью _list.AsReadOnly (), аналогично тому, как вы упомянули. Это работает так, как я бы надеялся, что оригинальный метод будет иметь. Кроме того, мне пришло в голову, что, хотя привязка ItemsSource работала нормально, я мог просто использовать свойство Current в ViewModel для доступа к выбранному элементу в ComboBox. Тем не менее, это не так естественно, как привязка свойства ComboBoxes SelectedValue / SelectedItem.