J'ai un ComboBox qui ne semble pas mettre à jour SelectedItem / SelectedValue.
Le ComboBox ItemsSource est lié à une propriété sur une classe ViewModel qui répertorie un ensemble d'entrées d'annuaire RAS en tant que CollectionView. Ensuite, j'ai lié (à des moments différents) à la fois le SelectedItem
ou SelectedValue
à une autre propriété du ViewModel. J'ai ajouté un MessageBox dans la commande save pour déboguer les valeurs définies par la liaison de données , mais la liaison SelectedItem
/ SelectedValue
n'est pas définie.
La classe ViewModel ressemble à ceci:
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");
}
}
}
La collection _phonebookEntries est en cours d'initialisation dans le constructeur à partir d'un objet métier. Le ComboBox XAML ressemble à ceci:
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=PhonebookEntry}" />
Je ne suis intéressé que par la valeur de chaîne réelle affichée dans la zone de liste déroulante, pas par les autres propriétés de l'objet car c'est la valeur que je dois transmettre à RAS lorsque je veux établir la connexion VPN, donc DisplayMemberPath
et SelectedValuePath
sont à la fois la propriété Name de le ConnectionViewModel. Le ComboBox est DataTemplate
appliqué à un ItemsControl
sur une fenêtre dont le DataContext a été défini sur une instance de ViewModel.
Le ComboBox affiche correctement la liste des éléments et je peux en sélectionner un dans l'interface utilisateur sans problème. Cependant, lorsque j'affiche la boîte de message à partir de la commande, la propriété PhonebookEntry contient toujours la valeur initiale, pas la valeur sélectionnée dans le ComboBox. D'autres instances de TextBox se mettent à jour correctement et s'affichent dans MessageBox.
Que me manque-t-il avec la liaison de données à la ComboBox? J'ai fait beaucoup de recherches et je n'arrive pas à trouver quoi que ce soit de mal.
C'est le comportement que je vois, mais cela ne fonctionne pas pour une raison quelconque dans mon contexte particulier.
J'ai un MainWindowViewModel qui a un CollectionView
de ConnectionViewModels. Dans le fichier code-behind MainWindowView.xaml, j'ai défini le DataContext sur MainWindowViewModel. Le MainWindowView.xaml est ItemsControl
lié à la collection de ConnectionViewModels. J'ai un DataTemplate qui contient le ComboBox ainsi que d'autres TextBoxes. Les TextBoxes sont directement liés aux propriétés du ConnectionViewModel à l'aide de 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; }
}
Le code XAML derrière:
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
Puis 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}" />
Les TextBoxes se lient tous correctement et les données se déplacent entre elles et le ViewModel sans problème. Ce n'est que la ComboBox qui ne fonctionne pas.
Vous avez raison dans votre hypothèse concernant la classe PhonebookEntry.
L'hypothèse que je fais est que le DataContext utilisé par mon DataTemplate est automatiquement défini via la hiérarchie de liaison, de sorte que je n'ai pas à le définir explicitement pour chaque élément du ItemsControl
. Cela me semblerait un peu ridicule.
Voici une implémentation de test qui illustre le problème, basée sur l'exemple ci-dessus.
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>
Le code derrière :
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; }
}
}
}
Si vous exécutez cet exemple, vous obtiendrez le comportement dont je parle. Le TextBox met à jour sa liaison correctement lorsque vous le modifiez, mais pas le ComboBox. Très déroutant vu que la seule chose que j'ai faite est d'introduire un ViewModel parent.
Je travaille actuellement sous l'impression qu'un élément lié à l'enfant d'un DataContext a cet enfant comme DataContext. Je ne trouve aucune documentation qui clarifie cela d'une manière ou d'une autre.
C'est à dire,
Window -> DataContext = MainWindowViewModel
..Items -> Lié à DataContext.PhonebookEntries
.... Item -> DataContext = PhonebookEntry (implicitement associé)
Je ne sais pas si cela explique mieux mon hypothèse (?).
Pour confirmer mon hypothèse, modifiez la liaison de la zone de texte en
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
Et cela montrera que la racine de liaison TextBox (que je compare au DataContext) est l'instance ConnectionViewModel.
<
T>
et dans le getter de propriété en utilisant _list.AsReadOnly () de la même manière que vous l'avez mentionné. Cela fonctionne comme j'aurais espéré que la méthode originale le ferait. En outre, j'ai pensé que, même si la liaison ItemsSource fonctionnait correctement, j'aurais pu simplement utiliser la propriété Current dans le ViewModel pour accéder à l'élément sélectionné dans le ComboBox. Pourtant, cela ne semble pas aussi naturel que de lier la propriété ComboBoxes SelectedValue / SelectedItem.