2009-04-28 36 views
22

Me gustaría enlazar un Dictionary<string, int> a un ListView en WPF. Me gustaría hacer esto de tal manera que el Values en el Dictionary se actualice a través del mecanismo de enlace de datos. No quiero cambiar el Keys solo el Values. Tampoco me importa agregar nuevas asignaciones al Dictionary. Solo quiero actualizar los existentes.Enlace de datos bidireccional con un diccionario en WPF

Al establecer el diccionario como ItemsSource del ListView no se logra esto. No funciona porque el ListView usa el Enumerador para acceder al contenido del Dictionary y los elementos de esa enumeración son objetos inmutables KeyValuePair.

Mi actual línea de investigación intenta utilizar la propiedad Keys. Asigno esto a la propiedad ItemsSource de mi ListView. Esto me permite mostrar el Keys pero no sé lo suficiente sobre el mecanismo de enlace de datos de WPF para acceder al Values en el Dictionary.

He encontrado esta pregunta: Access codebehind variable in XAML pero todavía parece que no puede cerrar la brecha.

¿Alguno de ustedes sabe cómo hacer que este enfoque funcione? ¿Alguien tiene un mejor enfoque?

Parece que, como último recurso, que podría construir un objeto personalizado y pegarlo en un List de la que volver a crear/actualizar mi Dictionary pero esto parece una manera de eludir la funcionalidad integrada de enlace de datos en lugar de efectivamente utilizarlo.

+1

¿No sería mejor usar algo como un ObservableDictionary con WPF? – Rauhotz

+0

¿Cómo vas a hacer las dos cosas? ¿Se muestran los valores en un cuadro de texto? – Ray

+0

@Ray Tengo un editor personalizado para mis valores. Si tuviera un ejemplo con un TextBox, estoy seguro de que podría hacer que se ajuste a mis necesidades. –

Respuesta

17

No creo que puedas hacer lo que quieras con un diccionario.

  1. Debido a que el diccionario no implementa INotifyPropertyChanged o INotifyCollectionChanged
  2. Debido a que no va a permitir la unión de dos vías como usted desee.

No estoy seguro de si se ajusta exactamente a sus requisitos, pero usaría un ObservableCollection y una clase personalizada.

Estoy usando un DataTemplate, para mostrar cómo hacer que el enlace en los valores sea bidireccional.

Por supuesto, en esta Clave aplicación no se utiliza, por lo que sólo podría utilizar un ObservableCollection<int>

clase personalizada

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

Conjunto 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> 
+1

En realidad, estoy menos preocupado por la propagación de cambios del diccionario en el ListView que por la propagación de cambios del ListView al Diccionario. Las interfaces de INotify parecen estar relacionadas principalmente con la primera. –

+0

Sí, eso es correcto. Sin embargo, intenté enlazar correctamente un diccionario y no pude hacer que funcionara, ya que no se podía. Veremos si alguien más tiene alguna sugerencia. – Ray

+0

MyCustomClass necesita implementar INotifyPropertyChanged – MikeT

6

Esto es un poco hacky, pero lo tengo para trabajar (suponiendo que entiendo lo que quieres).

primera vez que crea una clase de vista del modelo:

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 se une a la ItemsSource del ListBox. Como indica en la pregunta, esto da como resultado el uso de instancias de KeyValuePair<int, string> como datos detrás del ListBox. Establecí DisplayMemberPath en Key para que el valor de la clave se use como el valor mostrado para cada elemento en el ListBox.

Como has encontrado, no puedes usar el Value del KeyValuePair como datos para el TextBox, ya que es de solo lectura. En su lugar, el 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 vinculando la propiedad SelectedItem del ListBox a otra propiedad en el modelo de vista). Tuve que hacer esta propiedad nullable (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, este parece ser el resultado de las modificaciones a la TextBox que se propagan a la Dictionary en el modelo de vista.

¿Es eso más o menos lo que está buscando? Parece que debería ser un poco más limpio, pero no estoy seguro de si hay alguna manera de hacerlo.

+0

mi situación es ligeramente diferente.Quiero mostrar los elementos de Dictionary como varios pares de Label y Textbox. ¿Tiene alguna idea? – YukiSakura

+0

@YukiSakura Comenzaría con un ItemsControl encontrado en el diccionario, y establecería la plantilla del elemento para tener una etiqueta y un cuadro de texto vinculados a las propiedades apropiadas. – Andy

+0

Lo intenté, pero hubo un error en tiempo de ejecución diciendo que 'no puedo usar TwoWay para un solo valor de KeyValuePair ...' – YukiSakura

0

En lugar de

Dictionary<string, int> 

, que puede tener un

Dictionary<string, IntWrapperClass>? 

Tienes IntWrapperClass implementar INotifyPropertyChanged. Entonces puede hacer que Listview/Listbox se vinculen bidireccionalmente a los elementos en el diccionario.

0

Acaba de publicar lo que he derivado de lo anterior. Puede colocar el siguiente en un ObservableCollection<ObservableKvp<MyKeyObject, MyValueObject>>

Hecho la clave de solo lectura ya que la clave probablemente no debería modificarse. Esto necesita trabajar Prisma - o se puede poner en práctica su propia versión de 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; 
    } 
} 
Cuestiones relacionadas