CollectionViewSource View updaten wenn in .GetDefaultView festgelegte Quelle sich ändert

  • WPF MVVM
  • .NET (FX) 4.5–4.8

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von kafffee.

    CollectionViewSource View updaten wenn in .GetDefaultView festgelegte Quelle sich ändert

    Hallo,

    ich habe ein kleines Problem:

    Ich habe eine

    Public Property InhaltGesamt As ObservableCollection(Of Model.MP3FileInfo) = New ObservableCollection(Of Model.MP3FileInfo)

    in einem Modul in meinem ViewModel. Diese verwende ich als Source in der CollectionVIewSource.GetDefaultView-Methode:

    VB.NET-Quellcode

    1. Public Sub ErstelleDefault()
    2. _AnzuzeigendeInterpreten = CollectionViewSource.GetDefaultView(MainModule.InhaltGesamt.OrderBy(Function(o) o.Interpret).GroupBy(Function(f) f.Interpret).Select(Function(g) g.First()).ToArray())
    3. _AnzuzeigendeInterpreten.Filter = AddressOf AnzuzeigendeInterpreten_Filter
    4. _AnzuzeigendeInterpreten.SortDescriptions.Add(New SortDescription(NameOf(MP3FileInfo.Interpret), ListSortDirection.Ascending))​
    5. End Sub


    Nun muss ich, wenn sich InhaltGesamt ändert, die View updaten. Mit .Refresh() ist es nicht getan, das habe ich bereits versucht. OK, ich kann jetzt auch ErstelleDefault() aufrufen, dies wird aber nicht gehen, weil diese Methode vom aufrufenden ViewModel (einem Dialog) nicht zugreifbar ist.

    Also meine Frage:

    Bietet CollectionViewSource eine Eigenschaft oder Methode, die festlegt, dass wenn sich die Source ändert, automatisch GetDefaultView() neu aufgerufen wird?

    In den MS Docs habe ich das gefunden:

    https://docs.microsoft.com/en-us/dotnet/api/system.windows.data.collectionviewsource.onsourcechanged?view=windowsdesktop-6.0

    Da wird aber leider kein Beispiel angeführt. Man kann hier als Argument OldSource und NewSource übergeben. Kann ich da also einfach, bevor die Änderungen gemacht werden, InhaltGesamt in einer Variablen zwischenspeichern und dann als OldSource übergeben, und als NewSource dann das geänderte InhaltGesamt?
    @Akanel

    Ja das InhaltGesamt ist ja auch in einem Modul.

    Hab mir inzwischen einen Workaround ausgedacht, der funktionieren sollte, muss ich aber noch probieren.

    Ich dachte bloss, dass es vielleicht einen Weg gibt, dass die CollectionViewSource ihre Quelle überwacht und wenn diese sich ändert, automatisch sich die View updatet... Dann könnte ich so einiges einsparen...
    Also wenn ich das Problem richtig verstanden habe brauchst du doch nur da wo sich die view ändert RaisePropertyChanged aufrufen.

    MainModul ist doch ein Viewmodel oder nicht?

    Ich würde dir gerne ein Beispiel machen wie ich es meine aber zuhause ist das Internet tot. Telekom halt.
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    @Akanel

    Ne ich glaub du hast mich falsch verstanden. Das Modul ist nur eine statische Klasse im Namespace ViewModel.

    Da hab ich alle Variablen, Properties etc. reingepackt, die von allen(!) ViewModels leicht zu erreichen sein müssen und die vor dem Laden der anderen VMs (Sub New()) initialisiert werden müssen.

    Nur leider funktioniert das nicht andersherum, ich kann bei meinem jetzigen Setup die ViewModels nicht vom Modul aus erreichen...

    Mir fällt gerade auf: Dass InhaltGesamt eine Property As OC(of Class) ist kann ich mir glaube ich sogar schenken und einfach eine List(Of Class) machen. Das stammt noch aus der Zeit wo ich das Modul noch nicht hatte und InhaltGesamt in einem VM platziert war...

    Background ist folgender:
    InhaltGesamt ist eine OC, die Informationen über Mp3 Dateien enthält. Nun werden da von verschiedenen VMs (die zum Teil dynamisch erzeugt werden) zur Laufzeit die Daten geändert oder etwas hinzugefügt/entfernt. Diese Daten sollen dann von einem anderen ViewModel (da wo die Collectionviewsource drin sitzt) aufbereitet bzw. gefiltert und angezeigt werden.

    Jetzt müsstest du wissen wie ich meine ;)

    kafffee schrieb:

    Ne ich glaub du hast mich falsch verstanden. Das Modul ist nur eine statische Klasse im Namespace ViewModel.

    Erstens gibts es für solches Vorhaben Services und zweitens hindert dich auch ein Modul nicht daran INotifyPropertyChanged zu implementieren.
    Du bist leider wiedermal daran einen Workaround zu finden anstatt korrekt zu arbeiten.
    Erstelle deine ViewModel Klassen wie es sich gehört und deine "Updateprobleme" sind Geschichte.

    JEDES Property und JEDE Klasse sollte mittels INotifiPropertyChanged über Änderungen bescheid geben. Das ist der SInn von Binding.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    kafffee schrieb:

    Bietet CollectionViewSource eine Eigenschaft oder Methode, die festlegt, dass wenn sich die Source ändert, automatisch GetDefaultView() neu aufgerufen wird?
    Ja, eigentlich passiert das sogar von Haus aus. Aber deine Source ist ja ein anonymess Objekt, was nirgends nichtmal in eine Variable eingefüllt wurde:

    kafffee schrieb:

    VB.NET-Quellcode

    1. _AnzuzeigendeInterpreten = CollectionViewSource.GetDefaultView(MainModule.InhaltGesamt.OrderBy(Function(o) o.Interpret).GroupBy(Function(f) f.Interpret).Select(Function(g) g.First()).ToArray())

    Nofear23m schrieb:

    hindert dich auch ein Modul nicht daran INotifyPropertyChanged zu implementieren.


    OK das wusste ich nicht. Ich glaube du redest von so ner Art Singleton? Hab mal probiert das Ganze in nem kleinen Testprogramm nachzustellen und in dem Modul INotifyPropertyChanged zu implementieren. Ich scheitere aber daran: Wenn ich dann auf meine MusikDaten zugreifen will aus dem VM, dann sagt er an: "Der Verweis auf einen nicht freigegebenen Member erfordert einen Objektverweis." Sieht so aus (für diejenigen dich sich das Testprogramm nicht extra runterladen wollen):

    VB.NET-Quellcode

    1. Option Strict On
    2. Imports System.Collections.ObjectModel
    3. Imports System.ComponentModel
    4. Module MainModul
    5. Public Class MeineDaten
    6. Inherits ViewModel.Instrastructure.ViewModelBase
    7. Implements INotifyPropertyChanged
    8. Private _MusikDaten As ObservableCollection(Of Model.Daten)
    9. Public Property MusikDaten As ObservableCollection(Of Model.Daten)
    10. Get
    11. Return _MusikDaten
    12. End Get
    13. Set(value As ObservableCollection(Of Model.Daten))
    14. _MusikDaten = value
    15. RaisePropertyChanged()
    16. End Set
    17. End Property
    18. End Class
    19. End Module


    Bezüglich Services werd ich mir deinen Artikel auch nochmal durchlesen (ist schon ne Weile her)...
    Dateien
    Eine Klasse in einem Modul?

    Vergiss bitte Module. Bitte.
    Meiner Meinung nach sind die Böse.

    Erstell dir eine Klasse nach deinen Wünschen. (Musste ja nur das Modul zur Klasse machen)
    Packe eine Instanz dieser Klasse in deinen Servicecontainer und schon hast du immer Zugriff auf diese Instanz.

    Für Services ist das Kapitel ja auch fertig im meiner Tutorialreihe. Sonst kannst ja gerne im Supportthread nachfragen.
    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Also ich hab mir den Thread jetzt nicht ganz durchgelesen aber von Modul hab ich da jetzt nix gelesen. Nur von Singleton Klasse, aber eine Singleton ist kein Modul. Auch wenn es gern damit verglichen wird. Zumindest nach meinem Empfinden.

    Und was du mit einem Service unter MVVM erreichst ist ja quasi ein Singleton da du immer diese eine Instanz zurückbekommst. Und was anderes macht eine Singleton ja im Endeffekt auch nicht.

    Grüße
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Nofear23m schrieb:

    aber von Modul hab ich da jetzt nix gelesen


    Ja ich weiss, in dem Thread wird meistens von "Statischer Klasse" gesprochen...

    Ich hab mal ein Testprojekt gemacht und anstatt des Moduls einen Service hergenommen. Das Problem mit dem "automatischen Updaten" klappt fast, wie ich mir das vorstell, aber nur fast:

    Das Adden und Verändern von Datensätzen wird ohne Probleme angezeigt. Bloss wenn ich einen Datensatz verändere, scheint dieser nicht mehr durch den Filter gejagt werden. Beim blossen Adden wird aber gefiltert.

    ErfinderDesRades schrieb:

    Aber deine Source ist ja ein anonymess Objekt, was nirgends nichtmal in eine Variable eingefüllt wurde

    Kannst du das mal näher erläutern, das Problem liegt bestimmt daran... Wie würdest du das machen?

    PS: Das Testprogramm ist lauffähig und kommt ohne Verweise aus...
    Dateien

    kafffee schrieb:

    VB.NET-Quellcode

    1. _AnzuzeigendeInterpreten = CollectionViewSource.GetDefaultView(MainModule.InhaltGesamt.OrderBy(Function(o) o.Interpret).GroupBy(Function(f) f.Interpret).Select(Function(g) g.First()).ToArray())

    Was ist da die Source der CollectionView?
    dieses: MainModule.InhaltGesamt.OrderBy(Function(o) o.Interpret).GroupBy(Function(f) f.Interpret).Select(Function(g) g.First()).ToArray()
    Das ist ein Array.
    Wenn du dieses Array änderst, das merkt die CollectionView.
    aber erstens ists ein Array - also Elemente zufügen oder wegnehmen isnich. da wäre eien ObservableCollection(of T) besser.
    Zum zweiten kannste das ja nur ändern, wenn du eine Variable hast, wo es drin ist.
    Ja, in _AnzuzeigendeInterpreten isses drin - das könnteste sogar erwischen:

    VB.NET-Quellcode

    1. dim src=directcast(_AnzuzeigendeInterpreten.ItemsSource,ObservableCollection(of T))
    so ähnlich

    ErfinderDesRades schrieb:

    Ja, in _AnzuzeigendeInterpreten isses drin - das könnteste sogar erwischen:
    VB.NET-Quellcode
    dim src=directcast(_AnzuzeigendeInterpreten.ItemsSource,ObservableCollection(of T))
    so ähnlich


    ;( Aber nur so ähnlich... Auf was willst du hinaus?

    Mein ViewModel sieht jetzt so aus:

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.Windows.Data
    3. Imports System.Windows.Input
    4. Imports CollectionViewSourceTest.ViewModel.Instrastructure
    5. Public Class MainViewModel
    6. Inherits ViewModel.Instrastructure.ViewModelBase
    7. Private MeineZentraleKlasse As Services.IZentraleKlasse = Services.ServiceContainer.GetService(Of Services.IZentraleKlasse)
    8. Public Sub New()
    9. MeineZentraleKlasse.InhaltGesamt.Add(New ViewModel.DatenViewModel("Michael Jackson", "HIStory", "Billie Jean"))
    10. MeineZentraleKlasse.InhaltGesamt.Add(New ViewModel.DatenViewModel("Green Day", "21st Century Breakdown", "21 Guns"))
    11. MeineZentraleKlasse.InhaltGesamt.Add(New ViewModel.DatenViewModel("Cypress Hill", "IV", "Tequila Sunrise"))
    12. MeineZentraleKlasse.InhaltGesamt.Add(New ViewModel.DatenViewModel("Nelly", "Sweat Suit", "My Place"))
    13. AnzuzeigendeDaten = CollectionViewSource.GetDefaultView(MeineZentraleKlasse.InhaltGesamt)
    14. AnzuzeigendeDaten.Filter = AddressOf Filter
    15. End Sub
    16. Private Function Filter(obj As Object) As Boolean
    17. Dim Objekt As ViewModel.DatenViewModel = DirectCast(obj, ViewModel.DatenViewModel)
    18. If Objekt.Interpret = "Michael Jackson" Then
    19. Return False
    20. Else
    21. Return True
    22. End If
    23. End Function
    24. Private _AnzuzeigendeDaten As ICollectionView
    25. Public Property AnzuzeigendeDaten As ICollectionView
    26. Get
    27. Return _AnzuzeigendeDaten
    28. End Get
    29. Set(value As ICollectionView)
    30. _AnzuzeigendeDaten = value
    31. AnzuzeigendeDaten.Refresh()
    32. RaisePropertyChanged()
    33. End Set
    34. End Property
    35. Private _DatenSatzZufuegen As ICommand
    36. Public ReadOnly Property DatenSatzZufuegen() As ICommand
    37. Get
    38. If _DatenSatzZufuegen Is Nothing Then _DatenSatzZufuegen = New RelayCommand(AddressOf DatenSatzZufuegen_Execute, Function(o) True)
    39. Return _DatenSatzZufuegen
    40. End Get
    41. End Property
    42. Private Sub DatenSatzZufuegen_Execute(obj As Object)
    43. MeineZentraleKlasse.InhaltGesamt.Add(New ViewModel.DatenViewModel("Michael Jackson", "Sweat Suit", "My Place"))
    44. End Sub
    45. Private _DatenSatzAendern As ICommand
    46. Public ReadOnly Property DatenSatzAendern() As ICommand
    47. Get
    48. If _DatenSatzAendern Is Nothing Then _DatenSatzAendern = New RelayCommand(AddressOf DatenSatzAendern_Execute, Function(o) True)
    49. Return _DatenSatzAendern
    50. End Get
    51. End Property
    52. Private Sub DatenSatzAendern_Execute(obj As Object)
    53. MeineZentraleKlasse.InhaltGesamt(1).Interpret = "Michael Jackson"
    54. AnzuzeigendeDaten.Refresh()
    55. End Sub
    56. End Class


    Aber ohne das AnzuzeigendeDaten.Refresh() in Zeile 64 wird nicht gefiltert. Wenn ich es weglasse, dann erscheint die Änderung in der View, obwohl die Filterfunktion "Michael Jackson" das dann aus der Liste streichen sollte.
    Mein Model Model.Daten hab ich natürlich schon abstrahiert im ViewModel.DatenViewModel und entsprechend die Eigenschaften mit RaisePropertyChanged versorgt.

    Muss ich dann also mein AnzuzeigendenDaten auch in den Service packen? Wenn, ja, wie binde ich die View daran? Oder kann man das .Refresh() auch irgendwie automatisieren bzw. vom Service aus darauf zugreifen?

    Ich weiss es klingt bissle verworren aber ich hoffe ihr versteht wie ich meine...

    Testprogramm ist ja in meinem letzten Post... :)

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „kafffee“ ()

    hmm - ich seh in deim Datenmodel jetzt überhaupt kein _AnzuzeigendeInterpreten - worüber reden wir nu eiglich?

    kafffee schrieb:

    Muss ich dann also mein AnzuzeigendenDaten auch in den Service packen?
    Ich blick bei deim Service-Brimborium nicht im Detail durch.
    Jedenfalls ists notwendig, dass die Anwendung nur ein einziges Datenmodel hat - wenn deine Servicerei dein Instrument dafür ist, dann muss wohl.
    Warum das dann ein Problem beim Binding ist - wie gesagt: blick ich nicht durch.



    Deine Testsolution habich nun downloaded und zeigt mir 3 Datensätze an.
    Was ist deine Frage dazu, und was muss ich damit tun, um dein gefragtes Problem zu reproduzieren?

    ErfinderDesRades schrieb:

    hmm - ich seh in deim Datenmodel jetzt überhaupt kein _AnzuzeigendeInterpreten - worüber reden wir nu eiglich?


    Vergiss am besten meinen Post #1. Ich beziehe mich auf das zweite Testprojekt.
    Das Model beinhaltet eine Klasse Daten. Diese habe ich abstrahiert im ViewModel DatenViewModel, damit ich ein RaisePropertyChanged ausgelöst bekomme, nicht nur, wenn Items geaddet werden, sondern auch wenn einzelne Properties eines Items geändert werden. Ich glaube jedenfalls das ist im Sinne des Erfinders...

    Insofern habe ich, jedenfalls versteh ich das so, nur ein Model. Der Service ist ja nur dafür da, dass ich von überall aus der Applikation drauf zugreifen kann und RaisePropertyChanged auch darin implemetieren kann...

    Eine kleine Änderung habe ich am Testprojekt vorgenommen, welcher aber erst in meinem Post #14 sichtbar ist.
    Ich habe die ReadOnly Property AnzuzeigendeDaten in eine ohne ReadOnly verwandelt und diese dann direkt für .GetDefaultView und .Filter verwendet, das wollte ich noch erwähnen. Hat aber keinen Einfluss auf das Ergebnis

    Edit:
    @ErfinderDesRades

    Klicke auf "Datensatz ändern". Wenn dann der Datensatz mit dem Index 1 auf Michael Jackson als Interpret geändert wird, wird es so angezeigt und nicht rausgelöscht, wie es sein sollte mit dem implementierten Filter.

    Nur wenn ich in der vorvorletzten Zeile .Refresh aufrufe funktioniert das so wie ich es will...

    Stellt aber ein Problem dar, weil ich das Refresh von einem anderen VM aufrufen müsste in meinem Hauptprojekt...

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „kafffee“ ()