Двусторонняя привязка данных с помощью словаря в 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
, но это похоже на способ обойти встроенную привязку данных а не эффективно использовать его.
Ответы
Ответ 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;
}
}