Подтвердить что ты не робот

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

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

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

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

Я нашел этот вопрос: Доступ к переменной codebehind в XAML, но по-прежнему не может устранить пробел.

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

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

4b9b3361

Ответ 1

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

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

Я не уверен, что он точно соответствует вашим требованиям, но я бы использовал 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>

Ответ 2

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

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

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 к другому свойству в модели представления). Я должен был сделать это свойство нулевым (поскольку KeyValuePair является структурой), так что код мог обнаружить, когда нет выбора.

В моем тестовом приложении это приводит к редактированию TextBox, распространяющегося на Dictionary в модели представления.

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

Ответ 3

Вместо

Dictionary<string, int>

можете ли вы

Dictionary<string, IntWrapperClass>?

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

Ответ 4

Просто опубликуйте, что я получил из вышеизложенного. Вы можете разместить ниже в 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;
    }
}