none
ListView DataBinding reagiert nicht auf Änderungen RRS feed

  • Frage

  • Hallo,

    ich habe eine ListView mit 3 Spalten und möchte, dass diese die gebundenen Daten gruppiert anzeigt. Zur Gruppierung habe ich den ContainerStyle der ListView benutzt, um dort einen Expander mit Text zu platzieren.

            <ListView Name="listView">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Titel" Width="Auto" DisplayMemberBinding="{Binding FileName}" />
                        <GridViewColumn Header="Size" Width="Auto" DisplayMemberBinding="{Binding FileSize}" />
                        <GridViewColumn Header="State" Width="Auto" DisplayMemberBinding="{Binding State}"/>
                    </GridView>
                </ListView.View>
                <ListView.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.ContainerStyle>
                            <Style TargetType="{x:Type GroupItem}">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate>
                                            <Expander IsExpanded="True">
                                                <Expander.Header>
                                                    <StackPanel Orientation="Horizontal">
                                                        <TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
                                                        <TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
                                                        <TextBlock Text=" item(s)" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
                                                    </StackPanel>
                                                </Expander.Header>
                                                <ItemsPresenter />
                                            </Expander>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </GroupStyle.ContainerStyle>
                    </GroupStyle>
                </ListView.GroupStyle>
            </ListView>

    Ich habe eine Ordner und eine File Klasse, die Ordner Klasse instanziiert und verweist auf Objekte vom Typ File und die File Klasse "kennt" seinen Ordner, zu dem er gehört. Immer wenn ein Ordner erzeugt wird, wird dieser der globalen statischen ObservableCollection hinzugefügt. Die ObservableCollection, auf die die ListView zugreift, geht alle Instanzen der Klasse Ordner durch und fügt deren File-Instanzen zu der ObservableCollection zu , auf die die ListView zugreift. Der Problem ist, dass die ListView keine Daten anzeigt. habe ich einen Denkfehler?

    Grüße Patrick

    Mittwoch, 15. Oktober 2014 15:56

Antworten

Alle Antworten

  • Hallo,
    zunächst ist wichtig, dass du ItemsSource richtig zuweist:

    ICollectionView view = CollectionViewSource.GetDefaultView(this.Files);
    
    view.GroupDescriptions.Add(new PropertyGroupDescription("Folder"));
    
    listView.ItemsSource = view;

    Files ist dabei die ObservableCollection<File> mit den ganzen Dateinamen. Diese Liste solltest du nach möglichkeit nur beim Starten neu erstellen und später maximal noch mit Clear() wieder leeren. Nur so kann die ListView auf Änderungen reagieren.
    Folder ist die Eigenschaft in der File-Klasse, welche auf den Ordner verweist. An dieser Eigenschaft wird gruppiert.

    Sonst scheint dein Code zu funktionieren.

    Wenn es trotzdem noch nicht funktionieren sollte, zeige bitte auch noch den relevanten Code vom Codebehind.


    Tom Lambert - C# MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets


    Mittwoch, 15. Oktober 2014 17:15
    Moderator
  • Danke für die schnelle Antwort. Leider klappt es immer noch nicht, deswegen hier die Klassen:

    Zunächst das ViewModel der Ordner (statt Folder UploadPackage, ist prinzipiell das Selbe)

        /// <summary>
        /// ViewModel der Klasse UploadPackage
        /// </summary>
        public class UploadPackageViewModel : INotifyPropertyChanged
        {
            /// <summary>
            /// WPF-Liste die alle Instanzen der Klasse UploadPackage enthält
            /// </summary>
            public ObservableCollection<UploadPackage> UploadPackages { get; set; }
    
            /// <summary>
            /// Erzeugt eine neue Instanz der ViewMode-Klasse UploadPackageViewModel
            /// </summary>
            public UploadPackageViewModel()
            {
                UploadPackages = new ObservableCollection<UploadPackage>();
            }
    
            /// <summary>
            /// Lädt alle Instanzen der Klasse UploadPackage abhängig vom FilterModus
            /// </summary>
            /// <param name="filterMode">Das Kriterium, nach das gefiltert werden soll</param>
            public ObservableCollection<UploadPackage> LoadUploadPackages(FilterMode filterMode)
            {
                UploadPackages.Clear();
    
                switch (filterMode)
                {
                    case FilterMode.All:
                        UploadPackages = UploadPackage.AllUploadPackages;
                        break;
    
                    case FilterMode.CurrentUploads:
                        ObservableCollection<UploadPackage> currentUploads = new ObservableCollection<UploadPackage>();
                        foreach (UploadPackage ulPackage in UploadPackages)
                        {
                            if (ulPackage.State == State.Uploading)
                                currentUploads.Add(ulPackage);
                        }
                        UploadPackages = currentUploads;
                        break;
    
                    case FilterMode.InQueue:
                        ObservableCollection<UploadPackage> inQueue = new ObservableCollection<UploadPackage>();
                        foreach (UploadPackage ulPackage in UploadPackages)
                        {
                            if (ulPackage.State == State.Waiting || ulPackage.State == State.Down)
                                inQueue.Add(ulPackage);
                        }
                        UploadPackages = inQueue;
                        break;
    
                    case FilterMode.Down:
                        ObservableCollection<UploadPackage> currentlyDown = new ObservableCollection<UploadPackage>();
                        foreach (UploadPackage ulPackage in UploadPackages)
                        {
                            if (ulPackage.State == State.Down)
                                currentlyDown.Add(ulPackage);
                        }
                        UploadPackages = currentlyDown;
                        break;
    
                    case FilterMode.Uploaded:
                        ObservableCollection<UploadPackage> currentlyUp = new ObservableCollection<UploadPackage>();
                        foreach (UploadPackage ulPackage in UploadPackages)
                        {
                            if (ulPackage.State == State.Uploaded)
                                currentlyUp.Add(ulPackage);
                        }
                        UploadPackages = currentlyUp;
                        break;
                }
                return UploadPackages;
            }
    
            /// <summary>
            /// Das PropertyChanged-Event
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
    
            /// <summary>
            /// Löst das PropertyChanged-Event aus und sorgt somit
            /// für die Synchronisierung der Daten mit der GUI
            /// </summary>
            /// <param name="propertyName"></param>
            private void RaisePropertyChanged(string propertyName)
            {
                // Eine Kopie des Events erstellen, um Threading-Problemen vorzubeugen
                PropertyChangedEventHandler PropertyChangedHandler = PropertyChanged;
                if (PropertyChangedHandler != null)
                {
                    // Auslösen des Events (Listener warten darauf)
                    PropertyChangedHandler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

    Über die Methode LoadUploadPackages werden alle Instanzen der Klasse UploadPackage in die ObservableCollection UploadPackages geladen. Diese Liste wird iteriert, ebenso wie die ObservableCollection von einem UploadPackage, die die File-Instanzen beinhaltet, die zu diesen Packet (/Ordner) gehören.

    Hier der Code meines FileViewModels:

        public class FileViewModel
        {
            /// <summary>
            /// Referenz auf ein UploadPackageViewModel
            /// </summary>
            private UploadPackageViewModel ulPackageViewModel { get; set; }
    
            /// <summary>
            /// Auflistung aller File-Instanzen abhängig vom Filter
            /// </summary>
            public ObservableCollection<File> Files { get; set; }
    
            /// <summary>
            /// Erzeugt eine neue Instanz der Klasse FileViewModel
            /// </summary>
            public FileViewModel()
            {
                ulPackageViewModel = new UploadPackageViewModel();
                Files = new ObservableCollection<File>();
                LoadFiles(FilterMode.All);
            }
    
            /// <summary>
            /// Lädt alle Instanzen der Klasse File in die Files-Liste abhängig vom Filter
            /// </summary>
            /// <param name="filterMode">Das Kriterium, nach dem gefiltert werden soll</param>
            /// <returns></returns>
            public ObservableCollection<File> LoadFiles(FilterMode filterMode)
            {
                Files.Clear();
    
                foreach (UploadPackage ulPack in ulPackageViewModel.LoadUploadPackages(filterMode))
                {
                    foreach (File file in ulPack.Files)
                    {
                        Files.Add(file);
                    }
                }
                return Files;
            }
    
            /// <summary>
            /// Das PropertyChanged-Event
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
    
            /// <summary>
            /// Löst das PropertyChanged-Event aus und sorgt somit
            /// für die Synchronisierung der Daten mit der GUI
            /// </summary>
            /// <param name="propertyName"></param>
            private void RaisePropertyChanged(string propertyName)
            {
                // Eine Kopie des Events erstellen, um Threading-Problemen vorzubeugen
                PropertyChangedEventHandler PropertyChangedHandler = PropertyChanged;
                if (PropertyChangedHandler != null)
                {
                    // Auslösen des Events (Listener warten darauf)
                    PropertyChangedHandler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

    Ich debugge gerade schrittweise und merke, dass UploadPackageViewMode.LoadUploadPackages() nur eine leere Liste zurückgibt, obwohl es die statische ObservableCollection AllUploadPackages zurückgeben sollte, bei der immer bei der Erzeugung eines neuen UploadPackages dieses hinzugefügt wird.

    Im Konstruktor von UploadPackage steht:

            /// <summary>
            /// Erzeugt eine neue Instanz eines UploadPackages
            /// </summary>
            /// <param name="Files">Die Dateien, die zu diesen Upload-Packet gehören</param>
            public UploadPackage(List<System.IO.FileInfo> FileList)
            {
                Files = new ObservableCollection<File>();
                foreach (System.IO.FileInfo fileInfo in FileList)
                {
                    Files.Add(new File(fileInfo.FullName, this));
                }
                AllUploadPackages.Add(this);
            }

    Und AllUploadPackages ist wiefolgt definiert:

            /// <summary>
            /// Liste die alle Instanzen der UploadPackage-Klasse beinhaltet
            /// </summary>
            public static ObservableCollection<UploadPackage> AllUploadPackages = new ObservableCollection<UploadPackage>();


    • Bearbeitet Patrick-K Donnerstag, 16. Oktober 2014 10:06
    Mittwoch, 15. Oktober 2014 21:08
  • Was mir noch fehlt ist die Stelle, an der du die ListView mit den Daten befüllst. Irgendwo muss schließlich die Datenbindung aufgebaut werden. Und dort dazwischen findet die eigentliche Gruppierung statt.

    Wenn LoadUploadPackages nichts zurück liefert, kann natürlich auch nichts in der ListView landen. Das dürfte also der schwerwiegenste Fehler sein.
    Die Methode selbst hast duz nicht gezeigt, darum kann ich da keine weitere Aussage zu machen.

    Grundsätzlich gilt jedoch das man eigentlich auf statische Eigenschaften weitestgehend verzichtet. Denn diese können im gesamten Programm nur einen einzigen Wert annehmen.
    Soll es später mal mehrere geben könnte der Umbau ziehmlich komplex werden.


    Tom Lambert - C# MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    • Als Antwort markiert Patrick-K Donnerstag, 16. Oktober 2014 10:18
    Mittwoch, 15. Oktober 2014 22:10
    Moderator
  • Sorry, ich habe statt das ViewModel zu kopieren, das Model kopiert, jetzt sollte es richtig sein. LoadUploadPackages gibt bei FilterMode.All einfach nur die statische ObservableCollection zurück.

    Hier erstelle ich ein neues Objekt vom Typ File/UploadPackage und binde es an die ListView:

                Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
                dialog.ShowDialog();
                List<System.IO.FileInfo> fileInfo = new List<System.IO.FileInfo>();
                fileInfo.Add(new System.IO.FileInfo(dialog.FileName));
                // In seinen Konstruktor erzeugt UploadPackage Instanzen von der Klasse File,
                UploadPackage ulPackage = new UploadPackage(fileInfo);
                fileViewModel.LoadFiles(FilterMode.All);
                CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(fileViewModel.Files);
                // Testweise die Gruppierung nach den Dateinamen
                view.GroupDescriptions.Add(new PropertyGroupDescription("FileName"));
                uploadList.listView.ItemsSource = view;
                fileViewModel.LoadFiles(FilterMode.All);

    Ich glaube der grund, warum die statische Liste immer leer war, war, weil ich sie direkt initialisiert habe mit der Deklaration. Jetzt funkioniert nicht nur das, sondern die Einträge sind auch in der ListView gruppiert sichtbar, Danke! Allerdings funktioniert es nur ein Objekt hinzuzufügen, weil AllUploadPackages (die statische ObservableCollection) über keine Einträge mehr verfügt, nach dem ersten Aufruf von LoadUploadPackages()/LoadFiles().

    // Edit:

    Gefixt: Alles funktioniert wie es sein sollte!




    • Bearbeitet Patrick-K Donnerstag, 16. Oktober 2014 12:14
    Donnerstag, 16. Oktober 2014 10:17