J'aimerais lier un Dictionary<string, int>
à une ListView
dans WPF. J'aimerais procéder de manière à ce que la variable Values
dans la variable Dictionary
soit mise à jour via le mécanisme de liaison de données. Je ne veux pas changer la Keys
uniquement la Values
. Je ne me soucie pas non plus d'ajouter de nouvelles correspondances à la Dictionary
. Je veux juste mettre à jour les existants.
Définir le dictionnaire comme la ItemsSource
de la ListView
n'accomplit pas cela. Cela ne fonctionne pas, car la variable ListView
utilise l'énumérateur pour accéder au contenu de la variable Dictionary
et les éléments de cette énumération sont des objets KeyValuePair
immuables.
Ma ligne d’enquête actuelle tente d’utiliser la propriété Keys
. Je l'assigne à la propriété ItemsSource
de ma ListView
. Cela me permet d’afficher la variable Keys
mais je ne connais pas suffisamment le mécanisme de liaison de données de WPF pour accéder à la variable Values
dans la variable Dictionary
.
J'ai trouvé cette question: Variable d'accès codebehind en XAML mais n'arrive toujours pas à combler le fossé.
Est-ce que certains d'entre vous savent comment faire fonctionner cette approche? Quelqu'un a-t-il une meilleure approche?
Il semble qu'en dernier recours, je puisse créer un objet personnalisé et le coller dans une List
à partir de laquelle je recrée/met à jour ma Dictionary
, mais cela semble être un moyen de contourner la fonctionnalité de liaison de données intégrée plutôt que de l'utiliser efficacement.
Je ne pense pas que vous serez capable de faire ce que vous voudriez avec un dictionnaire.
Je ne sais pas si cela correspond exactement à vos besoins, mais j'utiliserais une ObservableCollection
et une classe personnalisée.
J'utilise un DataTemplate pour montrer comment faire la liaison sur les valeurs dans les deux sens.
Bien sûr, dans cette implémentation, la clé n'est pas utilisée, vous pouvez donc simplement utiliser un ObservableCollection<int>
Classe personnalisée
public class MyCustomClass
{
public string Key { get; set; }
public int Value { get; set; }
}
Définir ItemsSource
ObservableCollection<MyCustomClass> dict = new ObservableCollection<MyCustomClass>();
dict.Add(new MyCustomClass{Key = "test", Value = 1});
dict.Add(new MyCustomClass{ Key = "test2", Value = 2 });
listView.ItemsSource = dict;
XAML
<Window.Resources>
<DataTemplate x:Key="ValueTemplate">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</Window.Resources>
<ListView Name="listView">
<ListView.View>
<GridView>
<GridViewColumn CellTemplate="{StaticResource ValueTemplate}"/>
</GridView>
</ListView.View>
</ListView>
Ceci est un peu hacky, mais je l’ai fait au travail (en supposant que je comprends ce que vous voulez).
J'ai d'abord créé une classe de modèle de vue:
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.data.Add(1, "One");
this.data.Add(2, "Two");
this.data.Add(3, "Three");
}
Dictionary<int, string> data = new Dictionary<int, string>();
public IDictionary<int, string> Data
{
get { return this.data; }
}
private KeyValuePair<int, string>? selectedKey = null;
public KeyValuePair<int, string>? SelectedKey
{
get { return this.selectedKey; }
set
{
this.selectedKey = value;
this.OnPropertyChanged("SelectedKey");
this.OnPropertyChanged("SelectedValue");
}
}
public string SelectedValue
{
get
{
if(null == this.SelectedKey)
{
return string.Empty;
}
return this.data[this.SelectedKey.Value.Key];
}
set
{
this.data[this.SelectedKey.Value.Key] = value;
this.OnPropertyChanged("SelectedValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
var eh = this.PropertyChanged;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
Et puis dans le XAML:
<Window x:Class="WpfApplication1.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>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="ItemsListBox" Grid.Row="0"
ItemsSource="{Binding Path=Data}"
DisplayMemberPath="Key"
SelectedItem="{Binding Path=SelectedKey}">
</ListBox>
<TextBox Grid.Row="1"
Text="{Binding Path=SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
La valeur de la propriété Data
est liée à la ItemsSource
de la ListBox
. Comme vous le dites dans la question, cela se traduit par l'utilisation d'instances de KeyValuePair<int, string>
en tant que données derrière la ListBox
. Je règle DisplayMemberPath
sur Key
pour que la valeur de la clé soit utilisée comme valeur affichée pour chaque élément de la ListBox
.
Comme vous l'avez constaté, vous ne pouvez pas simplement utiliser la Value
de la KeyValuePair
comme donnée pour la TextBox
, car elle est en lecture seule. Au lieu de cela, la variable TextBox
est liée à une propriété du modèle de vue qui peut obtenir et définir la valeur de la clé sélectionnée (mise à jour en liant la propriété SelectedItem
de la ListBox
à une autre propriété du modèle de vue). J'ai dû rendre cette propriété nullable (étant donné que KeyValuePair
est une structure), afin que le code puisse détecter l'absence de sélection.
Dans mon application de test, cela semble entraîner des modifications de TextBox
se propageant à Dictionary
dans le modèle d'affichage.
Est-ce plus ou moins ce que vous allez faire? Il semble que cela devrait être un peu plus propre, mais je ne suis pas sûr s'il existe un moyen de le faire.
Je viens de poster ce que j'ai dérivé de ce qui précède. Vous pouvez placer le ci-dessous dans un ObservableCollection<ObservableKvp<MyKeyObject, MyValueObject>>
Made the Key en lecture seule car ils ne devraient probablement pas être changés. Cela nécessite que Prism fonctionne - ou vous pouvez implémenter votre propre version de INotifyPropertyChanged
à la place
public class ObservableKvp<K, V> : BindableBase
{
public K Key { get; }
private V _value;
public V Value
{
get { return _value; }
set { SetProperty(ref _value, value); }
}
public ObservableKvp(K key, V value)
{
Key = key;
Value = value;
}
}
Au lieu de
Dictionary<string, int>
, pouvez-vous avoir un
Dictionary<string, IntWrapperClass>?
Demandez à IntWrapperClass de mettre en œuvre INotifyPropertyCHanged. Vous pouvez ensuite avoir une liaison bidirectionnelle Listview/Listbox aux éléments du dictionnaire.