web-dev-qa-db-fra.com

Liaison de données bidirectionnelle avec un dictionnaire dans WPF

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.

23
Waylon Flinn

Je ne pense pas que vous serez capable de faire ce que vous voudriez avec un dictionnaire.

  1. Parce que le dictionnaire n'implémente pas INotifyPropertyChanged ni INotifyCollectionChanged
  2. Parce que cela ne va pas permettre une liaison dans les deux sens comme vous le souhaitez.

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>
19
Ray

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.

6
Andy

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;
    }
}
1
Anthony Nichols

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.

0
Adarsha