2009-02-16 8 views
11

Ho un ListBox WPF e ho aggiunto alcuni oggetti "FooBar" come elementi (per codice). FooBar non sono oggetti WPF, solo classi stupidi con una funzione ToString() sovrascritta.Aggiornamento dell'elenco WPF quando la voce cambia

Ora, quando cambio una proprietà che influenza il ToString, mi piacerebbe ottenere il ListBox da aggiornare.

  1. Come posso fare questo 'veloce e sporco' (come il ridisegno).
  2. Le proprietà di dipendenza sono la via da seguire?
  3. Vale la pena/sempre consigliabile, creare una classe wrapper wpf per i miei FooBar?

Grazie ...

+0

Ho aggiornato la mia risposta per mostrare come è possibile ottenere questo funzionamento. Sembra che DataTemplate sia la strada da percorrere. –

risposta

12

Il tipo deve implementare INotifyPropertyChanged in modo che una raccolta possa rilevare le modifiche. Come dice Sam, passare l'argomento string.Empty.

È necessario che l'origine dati di ListBox sia una raccolta che fornisce la notifica di modifica. Ciò avviene tramite l'interfaccia INotifyCollectionChanged (o l'interfaccia non-così-WPF IBindingList).

Ovviamente, è necessario attivare l'interfaccia INotifyCollectionChanged ogni volta che uno degli elementi membri INotifyPropertyChanged attiva l'evento. Per fortuna ci sono alcuni tipi nel framework che forniscono questa logica per te. Probabilmente il più adatto è ObservableCollection<T>. Se si associa il ListBox a un ObservableCollection<FooBar>, la concatenazione dell'evento avverrà automaticamente.

Su una nota correlata, non è necessario utilizzare un metodo ToString solo per ottenere WPF per eseguire il rendering dell'oggetto nel modo desiderato. È possibile utilizzare un DataTemplate come questo:

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

In questo modo è possibile controllare la presentazione dell'oggetto a cui appartiene - in XAML.

MODIFICA 1 Ho notato il tuo commento che stai utilizzando la raccolta ListBox.Items come raccolta. Questo non farà il legame richiesto. È meglio fare qualcosa del tipo:

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

_listBox.ItemsSource = collection; 

Non ho controllato il codice per la precisione della compilazione, ma ottieni il succo.

MODIFICA 2 Utilizzando il numero DataTemplate ho dato sopra (l'ho modificato per adattarlo al vostro codice) risolve il problema.

Sembra strano che l'attivazione di PropertyChanged non causi l'aggiornamento dell'elenco, ma l'utilizzo del metodo ToString non è il modo in cui WPF è stato progettato per funzionare.

Utilizzando questo DataTemplate, l'interfaccia utente si collega correttamente alla proprietà esatta.

Ho fatto una domanda su un po 'di tempo fa su string formatting in a WPF binding. Potresti trovarlo utile.

EDIT 3 Sono sconcertato sul motivo per cui questo non funziona ancora per voi. Ecco il codice sorgente completo per la finestra che sto usando.

codice dietro:

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

Grazie per le informazioni, ho provato quello che hai detto nel tuo Edit, ma niente gioia. Gli elementi vengono aggiunti alla listbox, ma niente si aggiorna quando sparo PropertyChanged - nessuno sembra essere in ascolto ... – Benjol

+0

I tuoi elementi attivano l'evento INotifyPropertyChanged quando cambiano le loro proprietà? Puoi pubblicare qualche codice di esempio nella tua domanda? –

+0

Colpa mia, ho appena cambiato il codice, non lo xaml. Ci riproverò domani e ti segnerò! – Benjol

0

Se l'oggetto di raccolta utilizzato per memorizzare gli elementi è un ObservableCollection <> allora questo viene gestito per voi.

i.e se la raccolta viene modificata, qualsiasi canale di controllo ad esso verrà aggiornato e viceversa.

+0

Bene gli elementi della listbox È la raccolta: myList.Items.Add (new FooBar()); – Benjol

1

Provare ad implementare l'interfaccia INotifyPropertyChanged sugli oggetti FooBar. Quando cambiano, sollevano gli eventi PropertyChanged, passando string.Empty come nome della proprietà. Questo dovrebbe fare il trucco.

+0

Sì, ma no. Nessuno sembra essere iscritto al mio evento :( – Benjol

+0

Non è sufficiente. Dovresti anche "dire" a ListBox da quale proprietà dell'oggetto dovresti ottenere una stringa da visualizzare. DataTemplate non è l'unico modo per farlo. Il modo più semplice è quello di aggiungere l'attributo 'DisplayMemberPath =" PropertyName "' al ListBox. – Lu55

3

La soluzione corretta è quella di utilizzare un ObservableCollection<> per la vostra proprietà ListBox IetmsSource. WPF rileverà automaticamente qualsiasi modifica nel contenuto di questa raccolta e costringerà il ListBox corrispondente all'aggiornamento per riflettere le modifiche.

Si consiglia di leggere questo articolo MSDN per ulteriori informazioni. È stato scritto per spiegare specificamente come gestire questo scenario

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

+0

Sono completamente d'accordo con te qui. – Blounty

0

ecco il codice C# che ho lavorato per questo:

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")); 
     } 
    } 
} 

Ecco l'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> 

Premendo il pulsante, la prima proprietà del primo FooBar viene aggiornata e mostrata nel ListBox.