2009-02-16 33 views
11

Mam ListBox WPF i dodałem niektóre obiekty "FooBar" jako elementy (według kodu). FooBary nie są obiektami WPF, tylko głupimi klasami z nadpisaną funkcją ToString().Aktualizacja listy WPF po zmianie pozycji

Teraz, gdy zmieniam właściwość, która wpływa na ToString, chciałbym, aby ListBox został zaktualizowany.

  1. Jak mogę to zrobić "szybko i brudno" (np. Odmalować).
  2. Czy właściwości zależności to sposób postępowania?
  3. Czy warto/zawsze zaleca się tworzenie klasy pakowania wpf dla moich FooBars?

Dzięki ...

+0

Zaktualizowałem swoją odpowiedź, aby pokazać, jak to działa. Wydaje się, że DataTemplate jest drogą do zrobienia. –

Odpowiedz

12

Twój typ powinien zaimplementować INotifyPropertyChanged, aby kolekcja mogła wykryć zmiany. Jak Sam mówi, przekazać string.Empty jako argument.

Musisz również trzeba mieć źródło danych jako kolekcję, która dostarcza powiadomienia o zmianie. Odbywa się to za pośrednictwem interfejsu INotifyCollectionChanged (lub interfejsu niezupełnie-WPF IBindingList).

Oczywiście musisz uruchomić interfejs INotifyCollectionChanged, gdy jeden z elementów użytkownika INotifyPropertyChanged wywoła swoje zdarzenie. Na szczęście istnieje kilka typów w ramach, które zapewniają tę logikę dla Ciebie. Prawdopodobnie najbardziej odpowiedni jest ObservableCollection<T>. Jeśli podłączysz swoje ListBox do ObservableCollection<FooBar>, to połączenie zdarzeń nastąpi automatycznie.

W pokrewnym notatniku nie trzeba używać metody ToString tylko po to, aby uzyskać WPF do renderowania obiektu w pożądany sposób. Można użyć DataTemplate takiego:

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

W ten sposób można sterować prezentacji obiektu, gdzie należy - w XAML.

EDYTOWANIE 1 Zauważyłem twój komentarz, że używasz kolekcji ListBox.Items jako kolekcji. Nie spowoduje to wymaganego powiązania. Lepiej rób coś w stylu:

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

_listBox.ItemsSource = collection; 

Nie sprawdziłem tego kodu pod kątem dokładności kompilacji, ale otrzymujesz istotę.

EDIT 2 Używanie podanego powyżej (edytowałem go w celu dopasowania do twojego kodu) rozwiązuje problem.

Wydaje się dziwne, że wyzwalanie PropertyChanged nie powoduje aktualizacji elementu listy, ale użycie metody ToString nie jest sposobem, w jaki program WPF miał działać.

Przy użyciu tego DataTemplate, interfejs użytkownika poprawnie wiąże się z dokładną właściwością.

Zadałem tu pytanie na jakiś czas dotyczące wykonania string formatting in a WPF binding. Może się to okazać pomocne.

EDYTOWANIE 3 Jestem zaskoczony, dlaczego to wciąż nie działa dla Ciebie. Oto pełny kod źródłowy okna, którego używam.

Kod za:

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

Dzięki za informację, spróbowałem tego, co powiedziałeś w swojej Edycji, ale bez radości. Elementy zostaną dodane do listy, ale nic się nie zaktualizuje, gdy wystrzelę PropertyChanged - nikt nie wydaje się nasłuchiwać ... – Benjol

+0

Czy Twoje elementy wystrzeliwują zdarzenie INotifyPropertyChanged, gdy zmieniają się ich właściwości? Czy możesz umieścić przykładowy kod w swoim pytaniu? –

+0

Moja wina, właśnie zmieniłem kod, a nie xaml. Spróbuję jeszcze raz jutro i Cię oznaczyłem! – Benjol

0

Jeśli obiekt kolekcja użyć do przechowywania przedmiotów jest ObservableCollection <> wtedy to jest obsługiwane dla Ciebie.

tj. Jeśli kolekcja zostanie zmieniona, wszelkie dane kontrolne do niej zostaną zaktualizowane i na odwrót.

+0

Cóż, elementy listbox IS to kolekcja: myList.Items.Add (new FooBar()); – Benjol

1

Spróbuj wprowadzić interfejs INotifyPropertyChanged na swoich obiektach FooBar. Kiedy się zmieniają, podnieście zdarzenia PropertyChanged, przekazując ciąg.Empty jako nazwę właściwości. To powinno wystarczyć.

+0

Tak, ale nie. Wydaje mi się, że nikt nie zapisał się na moje wydarzenie :( – Benjol

+0

To nie wystarczy, powinieneś również "powiedzieć" ListBoxowi, z której własności obiektu powinien otrzymać ciąg znaków do wyświetlenia. DataTemplate to nie jedyny sposób, aby to zrobić. Najprostszym sposobem jest dodanie do ListBoxa atrybutu 'DisplayMemberPath =" PropertyName "' – Lu55

3

Prawidłowe rozwiązanie tutaj jest użycie ObservableCollection<> dla nieruchomości ListBox IetmsSource. WPF automatycznie wykryje wszelkie zmiany w zawartości tej kolekcji i zmusi odpowiedni ListBox do aktualizacji, aby odzwierciedlić zmiany.

Możesz przeczytać ten artykuł MSDN, aby uzyskać więcej informacji. Został on napisany specjalnie wyjaśniać, jak obsługiwać ten scenariusz

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

+0

Całkowicie się z tobą zgadzam. – Blounty

0

Oto kod C# Mam pracę do tego:

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

Oto 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> 

Naciśnięcie przycisku powoduje aktualizację pierwszej właściwości pierwszego FooBara i wyświetlenie go w polu ListBox.

Powiązane problemy