c# - Enlace de datos bidireccional con un diccionario en WPF
data-binding xaml (4)
En lugar de
Dictionary<string, int>
, puedes tener un
Dictionary<string, IntWrapperClass>?
Haga que IntWrapperClass implemente INotifyPropertyCHanged. Entonces puede hacer que Listview / Listbox se vinculen bidireccionalmente a los elementos en el diccionario.
Me gustaría vincular un Dictionary<string, int>
a un ListView
en WPF. Me gustaría hacer esto de tal manera que los Values
en el Dictionary
se actualicen a través del mecanismo de enlace de datos. No quiero cambiar las Keys
solo los Values
. Tampoco me importa agregar nuevas asignaciones al Dictionary
. Solo quiero actualizar los existentes.
Establecer el diccionario como ItemsSource
de ListView
no lo logra. No funciona porque ListView
usa el Enumerador para acceder a los contenidos del Dictionary
y los elementos de esa enumeración son objetos inmutables KeyValuePair
.
Mi línea actual de investigación intenta usar la propiedad Keys
. ItemsSource
esto a la propiedad ItemsSource
de mi ListView
. Esto me permite mostrar las Keys
pero no sé lo suficiente sobre el mecanismo de enlace de datos de WPF para acceder a los Values
en el Dictionary
.
Encontré esta pregunta: El código de acceso detrás de la variable en XAML pero todavía no parece cerrar la brecha.
¿Alguno de ustedes sabe cómo hacer que este enfoque funcione? ¿Alguien tiene un mejor enfoque?
Parece que, como último recurso, podría construir un objeto personalizado y pegarlo en una List
desde la que recrear / actualizar mi Dictionary
pero esto parece una forma de eludir la funcionalidad integrada de enlace de datos en lugar de utilizarla de manera efectiva.
Esto es un poco raro, pero lo hice funcionar (asumiendo que entiendo lo que quieres).
Primero creé una clase de modelo de vista:
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));
}
}
}
Y luego en el 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>
El valor de la propiedad Data
está vinculado a ItemsSource
de ListBox
. Como indica en la pregunta, esto resulta en el uso de instancias de KeyValuePair<int, string>
como los datos detrás de ListBox
. DisplayMemberPath
en Key
para que el valor de la clave se use como el valor mostrado para cada elemento en el ListBox
.
Como descubrió, no puede usar el Value
de KeyValuePair
como datos para TextBox
, ya que solo es de lectura. En cambio, TextBox
está vinculado a una propiedad en el modelo de vista que puede obtener y establecer el valor de la clave seleccionada actualmente (que se actualiza al vincular la propiedad SelectedItem
del ListBox
a otra propiedad en el modelo de vista). Tuve que hacer esta propiedad KeyValuePair
(ya que KeyValuePair
es una estructura), por lo que el código podría detectar cuando no hay selección.
En mi aplicación de prueba, esto parece dar como resultado modificaciones al TextBox
propagan al Dictionary
en el modelo de vista.
¿Es eso más o menos lo que estás buscando? Parece que debería ser un poco más limpio, pero no estoy seguro de si hay alguna manera de hacerlo.
No creo que puedas hacer lo que quieras con un diccionario.
- Porque el diccionario no implementa INotifyPropertyChanged o INotifyCollectionChanged
- Porque no va a permitir el enlace bidireccional como desees.
No estoy seguro de si se ajusta exactamente a sus requisitos, pero usaría un ObservableCollection
y una clase personalizada.
Estoy usando DataTemplate, para mostrar cómo hacer que el enlace en los valores sea bidireccional.
Por supuesto, en esta implementación no se utiliza la clave, por lo que podría usar un ObservableCollection<int>
Clase personalizada
public class MyCustomClass
{
public string Key { get; set; }
public int Value { get; set; }
}
Establecer 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>
Solo publicando lo que derivé de lo anterior. Puede colocar lo siguiente en un ObservableCollection<ObservableKvp<MyKeyObject, MyValueObject>>
Hizo que la clave sea de solo lectura, ya que probablemente la clave no debería cambiarse. Esto necesita que Prism funcione, o puede implementar su propia versión de INotifyPropertyChanged
en INotifyPropertyChanged
lugar
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;
}
}