it-swarm.com.de

Zwei-Wege-Datenbindung mit einem Wörterbuch in WPF

Ich möchte einen Dictionary<string, int> an ein ListView in WPF binden. Ich möchte das so machen, dass Values in Dictionary über den Datenbindungsmechanismus aktualisiert wird. Ich möchte nicht das Keys ändern, sondern nur das Values. Ich möchte auch keine neuen Zuordnungen zu Dictionary hinzufügen. Ich möchte nur vorhandene aktualisieren.

Wenn Sie das Wörterbuch als ItemsSource der ListView festlegen, wird dies nicht erreicht. Es funktioniert nicht, weil ListView den Enumerator verwendet, um auf den Inhalt von Dictionary zuzugreifen, und die Elemente dieser Enumeration sind unveränderliche KeyValuePair-Objekte.

Meine derzeitige Untersuchungslinie versucht, die Keys-Eigenschaft zu verwenden. Ich ordne dies der ItemsSource-Eigenschaft meines ListView zu. Dadurch kann ich Keys anzeigen, aber ich weiß nicht genug über WPFs Datenbindungsmechanismus, um auf Values in Dictionary zuzugreifen.

Ich habe diese Frage gefunden: Zugriff auf Codebehind-Variable in XAML , kann aber immer noch die Lücke nicht schließen.

Weiß jemand von Ihnen, wie dieser Ansatz funktioniert? Hat jemand einen besseren Ansatz?

Es scheint, als könnte ich als letzter Ausweg ein benutzerdefiniertes Objekt erstellen und es in ein List-Objekt einfügen, aus dem ich mein/Dictionary-Objekt neu erstellen/aktualisieren. Dies scheint jedoch eine Möglichkeit zu sein, die integrierte Datenbindungsfunktion zu umgehen, anstatt sie effektiv zu nutzen.

23
Waylon Flinn

Ich glaube nicht, dass Sie mit einem Wörterbuch tun können, was Sie möchten.

  1. Weil das Wörterbuch INotifyPropertyChanged oder INotifyCollectionChanged nicht implementiert
  2. Weil es keine wechselseitige Bindung zulässt, wie Sie möchten.

Ich bin nicht sicher, ob es genau Ihren Anforderungen entspricht, aber ich würde eine ObservableCollection und eine benutzerdefinierte Klasse verwenden.

Ich verwende eine DataTemplate, um zu zeigen, wie die Bindung für die Werte auf zwei Arten erstellt wird.

Natürlich wird in dieser Implementierung kein Schlüssel verwendet, Sie können also einfach einen ObservableCollection<int> verwenden.

Benutzerdefinierte Klasse

public class MyCustomClass
{
    public string Key { get; set; }
    public int Value { get; set; }
}

Stellen Sie ItemsSource ein

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

Dies ist ein bisschen hackig, aber ich habe es zum Laufen gebracht (vorausgesetzt, ich verstehe, was Sie wollen).

Ich habe zuerst eine Ansichtsmodellklasse erstellt:

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));
        }
    }
}

Und dann in der 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>

Der Wert der Data-Eigenschaft ist an die ItemsSource der ListBox gebunden. Wie Sie in der Frage angeben, werden dadurch Instanzen von KeyValuePair<int, string> als Daten hinter der ListBox verwendet. Ich setze DisplayMemberPath auf Key, so dass der Wert des Schlüssels als der angezeigte Wert für jedes Element in der ListBox verwendet wird.

Wie Sie herausgefunden haben, können Sie nicht einfach die Value der KeyValuePair als Daten für die TextBox verwenden, da dies nur lesbar ist. Stattdessen ist TextBox an eine Eigenschaft im Ansichtsmodell gebunden, die den Wert für den aktuell ausgewählten Schlüssel abrufen und festlegen kann (der durch das Binden der SelectedItem-Eigenschaft der ListBox an eine andere Eigenschaft im Ansichtsmodell aktualisiert wird). Ich musste diese Eigenschaft auf Null setzen (da KeyValuePair eine Struktur ist), damit der Code erkennen kann, wenn es keine Auswahl gibt.

In meiner Test-App scheint dies dazu zu führen, dass Änderungen an der Variable TextBox im Sichtmodell auf die Variable Dictionary übertragen werden.

Ist das mehr oder weniger das, wonach du strebst? Es scheint, als sollte es etwas sauberer sein, aber ich bin nicht sicher, ob es einen Weg gibt, dies zu tun.

6
Andy

Ich poste einfach das, was ich aus dem Obigen abgeleitet habe. Sie können das unten in einem ObservableCollection<ObservableKvp<MyKeyObject, MyValueObject>> platzieren

Der Schlüssel wurde schreibgeschützt, da er wahrscheinlich nicht geändert werden sollte. Dazu muss Prism funktionieren - oder Sie können stattdessen Ihre eigene Version von INotifyPropertyChanged implementieren

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

Anstatt 

Dictionary<string, int>

können Sie eine haben? 

Dictionary<string, IntWrapperClass>?

Lassen Sie IntWrapperClass INotifyPropertyCHanged implementieren. Dann können Sie eine Listview/Listbox-Verknüpfung auf zwei Arten an Elemente im Wörterbuch herstellen lassen.

0
Adarsha