Двусторонняя привязка данных со словарем в WPF

Я хочу привязать Dictionary<string, int> к ListView в WPF. Я хотел бы сделать это таким образом, чтобы Values в Dictionary обновлялись через механизм привязки данных. Я не хочу менять Keys только Values. Я также не забочусь о добавлении новых сопоставлений в Dictionary. Я просто хочу обновить существующие.

Установка словаря как ItemsSource из ListView этого не делает. Это не работает, потому что ListView использует перечислитель для доступа к содержимому Dictionary, а элементы этого перечисления являются неизменяемыми объектами KeyValuePair.

В моем текущем исследовании делается попытка использовать свойство Keys. Я назначаю это свойству ItemsSource моего ListView. Это позволяет мне отображать Keys, но я недостаточно знаю о механизме привязки данных WPF для доступа к Values в Dictionary.

Я нашел этот вопрос: Доступ к переменной codebehind-variable-in-xaml"> Доступа к переменной codebehind в XAML, но все еще не удается преодолеть разрыв.

Кто-нибудь из вас знает, как заставить этот подход работать? Есть ли у кого-нибудь подход лучше?

Похоже, что в крайнем случае я мог бы создать собственный объект и вставить его в List, из которого я воссоздаю / обновляю свой Dictionary, но это похоже на способ обойти встроенную функцию привязки данных, а не эффективно ее использовать.


person Waylon Flinn    schedule 28.04.2009    source источник
comment
Не лучше ли использовать что-то вроде ObservableDictionary с WPF?   -  person Rauhotz    schedule 29.04.2009
comment
Как вы собираетесь сделать это двумя путями? Отображаются ли они в текстовом поле?   -  person Ray    schedule 29.04.2009
comment
@Ray У меня есть собственный редактор для моих ценностей. Если бы у меня был пример с TextBox, я уверен, что смог бы удовлетворить свои потребности.   -  person Waylon Flinn    schedule 29.04.2009
comment
Можете ли вы указать какой-либо хороший пример с помощью ObservableDictionary @Rauhotz   -  person ali kerim erkan    schedule 05.02.2019


Ответы (5)


Я не думаю, что вы сможете делать то, что хотите, со словарем.

  1. Поскольку Словарь не реализует INotifyPropertyChanged или INotifyCollectionChanged
  2. Потому что он не разрешит двустороннюю привязку, как вы хотите.

Я не уверен, что он точно соответствует вашим требованиям, но я бы использовал ObservableCollection и специальный класс.

Я использую DataTemplate, чтобы показать, как сделать двустороннюю привязку значений.

Конечно, в этой реализации Key не используется, поэтому вы можете просто использовать ObservableCollection<int>

Пользовательский класс

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

Установить 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>
person Ray    schedule 28.04.2009
comment
На самом деле меня меньше беспокоит распространение изменений из словаря в ListView, чем распространение изменений из ListView в словарь. Похоже, что интерфейсы INotify в основном связаны с первым. - person Waylon Flinn; 29.04.2009
comment
Да, это правильно. Однако я попытался правильно привязать словарь и не смог заставить его работать, как и вы. Посмотрим, есть ли у кого-нибудь предложения. - person Ray; 29.04.2009
comment
MyCustomClass необходимо реализовать INotifyPropertyChanged - person MikeT; 07.09.2016
comment
А как насчет ObservableDictionary? РЕДАКТИРОВАТЬ: это может быть новее, чем этот ответ. - person Owen Johnson; 27.03.2017
comment
@OwenJohnson, если ObservableDictionary теперь решает проблему, вы должны добавить новый ответ на этот вопрос. - person Ray; 30.03.2017

Это немного взломано, но я заставил его работать (при условии, что я понимаю, что вы хотите).

Сначала я создал класс модели представления:

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

А затем в 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>

Значение свойства Data привязано к ItemsSource ListBox. Как вы указываете в вопросе, это приводит к использованию экземпляров KeyValuePair<int, string> в качестве данных, стоящих за ListBox. Я установил DisplayMemberPath на Key, чтобы значение ключа использовалось как отображаемое значение для каждого элемента в ListBox.

Как вы обнаружили, вы не можете просто использовать Value из KeyValuePair в качестве данных для TextBox, поскольку он доступен только для чтения. Вместо этого TextBox привязан к свойству в модели представления, которое может получать и устанавливать значение для текущего выбранного ключа (который обновляется путем привязки свойства SelectedItem объекта ListBox к другому свойству в модели представления). Мне пришлось сделать это свойство допускающим значение NULL (поскольку KeyValuePair - это структура), чтобы код мог определять отсутствие выделения.

В моем тестовом приложении это, похоже, приводит к тому, что изменения TextBox распространяются на Dictionary в модели представления.

Это более или менее то, к чему вы стремитесь? Кажется, это должно быть немного чище, но я не уверен, есть ли способ сделать это.

person Andy    schedule 29.04.2009
comment
моя ситуация немного другая. Я хочу показать элементы Dictionary ‹string, float› как несколько пар Label и Textbox. Есть ли у вас какие-либо идеи? - person YukiSakura; 07.12.2015
comment
@YukiSakura Я бы начал с ItemsControl, найденного в словаре, и установил в шаблоне элемента Label и TextBox, привязанные к соответствующим свойствам. - person Andy; 07.12.2015
comment
Я пробовал, но во время выполнения возникла ошибка, в которой говорилось, что «нельзя использовать TwoWay для KeyValuePair только для чтения ...» - person YukiSakura; 07.12.2015
comment
@YukiSakura Это потому, что член Value KeyValuePair доступен только для чтения. Вы можете попробовать привязать к другому свойству, которое просто возвращает значение для данного ключа в словаре (например, свойство SelectedValue в коде в моем ответе). - person Andy; 07.12.2015

Просто размещаю то, что я извлек из вышеизложенного. Вы можете разместить нижеприведенное в ObservableCollection<ObservableKvp<MyKeyObject, MyValueObject>>

Сделал ключ доступным только для чтения, так как ключ, вероятно, не должен быть изменен. Для этого требуется, чтобы Prism работал - или вместо этого вы можете реализовать свою собственную версию INotifyPropertyChanged

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;
    }
}
person Anthony Nichols    schedule 10.03.2017

Вместо

Dictionary<string, int>

у тебя есть

Dictionary<string, IntWrapperClass>?

Попросите IntWrapperClass реализовать INotifyPropertyCHanged. Затем вы можете иметь двустороннюю привязку Listview / Listbox к элементам в словаре.

person Adarsha    schedule 18.05.2012

Мне удалось это сделать в моем проекте, используя наблюдаемый словарь, реализованный dr.wpf здесь и в качестве значения используется не строка, а объект []. В xaml у меня получилось что-то вроде:

<ListView ItemsSource="{Binding myObservableDictionary}" >
                            <ListView.View>
                                <GridView>
                                    <GridViewColumn DisplayMemberBinding="{Binding Key}"/>
                                    <GridViewColumn>
                                        <GridViewColumn.CellTemplate>
                                            <DataTemplate>
                                                <TextBox Text="{Binding Value[0], Mode=TwoWay}" AllowDrop="True" Drop="TextBox_Drop"></TextBox>
                                            </DataTemplate>
                                        </GridViewColumn.CellTemplate>
                                    </GridViewColumn>
                                </GridView>
                            </ListView.View>
                        </ListView> 
person elfo11    schedule 15.05.2020