2009-02-16 22 views
11

Tengo un WPF ListBox, y he agregado algunos objetos 'FooBar' como elementos (por código). FooBars no son objetos WPF, solo una clase tonta con una función ToString() sobrescrita.Actualización de la lista WPF cuando el artículo cambia

Ahora, cuando cambio una propiedad que influye en el ToString, me gustaría que el ListBox se actualice.

  1. ¿Cómo puedo hacer esto 'rápido y sucio' (como volver a pintar).
  2. ¿Las propiedades de dependencia son el camino a seguir en esto?
  3. ¿Merece la pena/siempre es aconsejable crear una clase de envoltura wpf para mis FooBars?

Gracias ...

+0

He actualizado mi respuesta para mostrar cómo puede hacer que funcione. Parece que DataTemplate es el camino a seguir. –

Respuesta

12

Su tipo debe implementar INotifyPropertyChanged para que una colección pueda detectar los cambios. Como dice Sam, pase string.Empty como argumento.

Usted también necesita tener la fuente de datos ListBox ser una colección que proporciona notificación de cambio. Esto se hace a través de la interfaz INotifyCollectionChanged (o la interfaz no tan WPF IBindingList).

Por supuesto, necesita la interfaz INotifyCollectionChanged para disparar cada vez que uno de los elementos INotifyPropertyChanged activa su evento. Afortunadamente hay algunos tipos en el marco que proporcionan esta lógica para usted. Probablemente el más adecuado es ObservableCollection<T>. Si vincula su ListBox a un ObservableCollection<FooBar>, el encadenamiento de eventos se realizará automáticamente.

En una nota relacionada, no tiene que usar un método ToString solo para que WPF renderice el objeto de la manera que desee. Puede utilizar un DataTemplate así:

<ListBox x:Name="listBox1"> 
    <ListBox.Resources> 
     <DataTemplate DataType="{x:Type local:FooBar}"> 
      <TextBlock Text="{Binding Path=Property}"/> 
     </DataTemplate> 
    </ListBox.Resources> 
</ListBox> 

De esta manera se puede controlar la presentación del objeto donde pertenece - en el XAML.

EDIT 1 Me di cuenta de que está utilizando la colección ListBox.Items como su colección. Esto no hará el enlace requerido. Es mejor que hacer algo como:

var collection = new ObservableCollection<FooBar>(); 
collection.Add(fooBar1); 

_listBox.ItemsSource = collection; 

No he comprobado que codifican para la compilación exactitud, pero se obtiene la esencia.

EDIT 2 Usando el DataTemplate di más arriba (lo edité para adaptarlo a su código) corrige el problema.

Parece extraño que disparar PropertyChanged no haga que el elemento de la lista se actualice, pero luego usar el método ToString no es la manera en que se pretendía que funcionara WPF.

Al usar este DataTemplate, la UI se une correctamente a la propiedad exacta.

Hice una pregunta aquí hace un tiempo sobre hacer string formatting in a WPF binding. Es posible que lo encuentre útil.

EDIT 3 Estoy desconcertado de por qué esto todavía no funciona para usted. Aquí está el código fuente completo de la ventana que estoy usando.

Código atrás:

using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.Windows; 

namespace StackOverflow.ListBoxBindingExample 
{ 
    public partial class Window1 
    { 
     private readonly FooBar _fooBar; 

     public Window1() 
     { 
      InitializeComponent(); 

      _fooBar = new FooBar("Original value"); 

      listBox1.ItemsSource = new ObservableCollection<FooBar> { _fooBar }; 
     } 

     private void button1_Click(object sender, RoutedEventArgs e) 
     { 
      _fooBar.Property = "Changed value"; 
     } 
    } 

    public sealed class FooBar : INotifyPropertyChanged 
    { 
     public event PropertyChangedEventHandler PropertyChanged; 

     private string m_Property; 

     public FooBar(string initval) 
     { 
      m_Property = initval; 
     } 

     public string Property 
     { 
      get { return m_Property; } 
      set 
      { 
       m_Property = value; 
       OnPropertyChanged("Property"); 
      } 
     } 

     private void OnPropertyChanged(string propertyName) 
     { 
      var handler = PropertyChanged; 
      if (handler != null) 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
} 

XAML:

<Window x:Class="StackOverflow.ListBoxBindingExample.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:StackOverflow.ListBoxBindingExample" 
    Title="Window1" Height="300" Width="300"> 
    <DockPanel LastChildFill="True"> 
     <Button Click="button1_Click" DockPanel.Dock="Top">Click Me!</Button> 
     <ListBox x:Name="listBox1"> 
      <ListBox.Resources> 
       <DataTemplate DataType="{x:Type local:FooBar}"> 
        <TextBlock Text="{Binding Path=Property}"/> 
       </DataTemplate> 
      </ListBox.Resources> 
     </ListBox> 
    </DockPanel> 
</Window> 
+0

Gracias por la información, intenté con lo que dijiste en tu edición, pero no me alegré. Los elementos se agregaron al cuadro de lista, pero no se actualiza cuando disparo PropertyChanged - nadie parece estar escuchando ... – Benjol

+0

¿Tus elementos activan el evento INotifyPropertyChanged cuando cambian sus propiedades? ¿Puedes publicar algún código de muestra en tu pregunta? –

+0

Mi error, acabo de cambiar el código, no el xaml. ¡Intentaré de nuevo mañana y te marcaré! – Benjol

0

Si el objeto de colección que se utiliza para almacenar los artículos es una ObservableCollection <> a continuación, esto se maneja para usted.

es decir, si se cambia la colección, se actualizarán los controles de datos que estén enlazados a él y viceversa.

+0

Bueno, los elementos de la lista son la colección: myList.Items.Add (new FooBar()); – Benjol

1

Intente implementar la interfaz INotifyPropertyChanged en sus objetos FooBar. Cuando cambian, sube eventos PropertyChanged, pasando string.Empty como el nombre de la propiedad. Eso debería hacer el truco.

+0

Sí, pero no. Nadie parece estar registrado en mi evento :( – Benjol

+0

No es suficiente. También debe "decirle" a ListBox cuál es la propiedad de su objeto para que aparezca una cadena. DataTemplate no es la única forma de hacerlo. La forma más simple es agregar el atributo 'DisplayMemberPath =" PropertyName "' al ListBox. – Lu55

3

La solución correcta es utilizar un ObservableCollection<> para su propiedad ListBox IetmsSource. WPF detectará automáticamente cualquier cambio en el contenido de esta colección y forzará que el ListBox correspondiente se actualice para reflejar los cambios.

Le recomendamos leer este artículo de MSDN para obtener más información. Fue escrito para explicar específicamente cómo manejar este escenario

http://msdn.microsoft.com/en-us/magazine/dd252944.aspx?pr=blog

+0

Estoy completamente de acuerdo con usted aquí. – Blounty

0

Este es el código C# He trabajando para esto:

using System; 
using System.Collections.Generic; 
using System.Windows; 
using System.Windows.Controls; 
using System.ComponentModel; 
using System.Collections.ObjectModel; 
namespace ListboxOfFoobar 
{ 
    /// <summary> 
    /// Interaction logic for Window1.xaml 
    /// </summary> 
    public partial class Window1 : Window 
    { 
     public Window1() 
     { 
      InitializeComponent(); 
     } 

     private void Button_Click(object sender, RoutedEventArgs e) 
     { 
      ObservableCollection<FooBar> all = (ObservableCollection<FooBar>)FindResource("foobars"); 
      all[0].P1 = all[0].P1 + "1"; 
     } 
    } 
    public class FooBar : INotifyPropertyChanged 
    { 
     public FooBar(string a1, string a2, string a3, string a4) 
     { 
      P1 = a1; 
      P2 = a2; 
      P3 = a3; 
      P4 = a4; 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 
     private void NotifyPropertyChanged(String info) 
     { 
      if (PropertyChanged != null) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(info)); 
      } 
     } 

     private String p1; 
     public string P1 
     { 
      get { return p1; } 
      set 
      { 
       if (value != this.p1) 
       { 
        this.p1 = value; 
        NotifyPropertyChanged("P1"); 
       } 
      } 
     } 
     private String p2; 
     public string P2 
     { 
      get { return p2; } 
      set 
      { 
       if (value != this.p2) 
       { 
        this.p2 = value; 
        NotifyPropertyChanged("P2"); 
       } 
      } 
     } 
     private String p3; 
     public string P3 
     { 
      get { return p3; } 
      set 
      { 
       if (value != this.p3) 
       { 
        this.p3 = value; 
        NotifyPropertyChanged("P3"); 
       } 
      } 
     } 
     private String p4; 
     public string P4 
     { 
      get { return p4; } 
      set 
      { 
       if (value != this.p4) 
       { 
        this.p4 = value; 
        NotifyPropertyChanged("P4"); 
       } 
      } 
     } 
     public string X 
     { 
      get { return "Foooooo"; } 
     } 
    } 
    public class Foos : ObservableCollection<FooBar> 
    { 
     public Foos() 
     { 
      this.Add(new FooBar("a", "b", "c", "d")); 
      this.Add(new FooBar("e", "f", "g", "h")); 
      this.Add(new FooBar("i", "j", "k", "l")); 
      this.Add(new FooBar("m", "n", "o", "p")); 
     } 
    } 
} 

Aquí está el XAML:

<Window x:Class="ListboxOfFoobar.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:ListboxOfFoobar" 
    xmlns:debug="clr-namespace:System.Diagnostics;assembly=System" 

    Title="Window1" Height="300" Width="300"   
     > 
    <Window.Resources> 
     <local:Foos x:Key="foobars" /> 
     <DataTemplate x:Key="itemTemplate"> 
      <StackPanel Orientation="Horizontal"> 
       <TextBlock MinWidth="80" Text="{Binding Path=P1}"/> 
       <TextBlock MinWidth="80" Text="{Binding Path=P2}"/> 
       <TextBlock MinWidth="80" Text="{Binding Path=P3}"/> 
       <TextBlock MinWidth="80" Text="{Binding Path=P4}"/> 
      </StackPanel> 
     </DataTemplate> 

    </Window.Resources> 

    <DockPanel> 
     <ListBox DockPanel.Dock="Top" 
     ItemsSource="{StaticResource foobars}" 
     ItemTemplate="{StaticResource itemTemplate}" Height="229" /> 
     <Button Content="Modify FooBar" Click="Button_Click" DockPanel.Dock="Bottom" /> 
    </DockPanel> 
</Window> 

Al presionar el botón, se actualiza la primera propiedad del primer FooBar y se muestra en el ListBox.

Cuestiones relacionadas