List(of T) an DataGridView binden

  • VB.NET

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    List(of T) an DataGridView binden

    Wie man grundsätzlich eine Liste an ein DataGridView bindet, ist mir bekannt

    Quellcode

    1. Private myList as List(of T)
    2. ....
    3. Me.DataGridView.DataSource = myList


    Das klappt ja grundsätzlich recht gut. Jedoch wenn sich die Liste im Hintergrund verändert (zB neue Elemente kommen hinzu - myList.Add), dann wird das DataGridView nicht aktualisiert.

    Momentan löse ich das über einen Timer der

    Quellcode

    1. Me.DataGridView.DataSource = Nothing
    2. Me.DataGridView.DataSource = myList

    die DataSource kurzfristig entfernt und wieder hinzufügt. Aber selbst mir als Laien kommt das nicht sonderlich elegant vor. Gibt es da keine schönere Möglichkeit.

    Auch wenn ich über eine BindingSource gehe, wird nichts aktualisiert

    Quellcode

    1. Private bs as BindingSource = New BindingSource
    2. ....
    3. bs.DataSource = myList
    4. ME.DataGridView.DataSource = bs


    Danke für alle zweckdienlichen Hinweise.

    ErfinderDesRades schrieb:

    nimm statt der List(Of T) eine BindingList(Of T)

    Das hatte ich auch schon probiert, da ich es in irgend einem Forum schon gelesen hatte.

    Quellcode

    1. Imports System.Component
    2. Private myList as New BindingList(of T)
    3. ...
    4. Me.DataGridView.DataSource = myList



    funktioniert leider ebenso nicht. Sofern es zweckdienlich ist als Liste werden Klassen geführt, die für Hintergrundprozesse stehen. Also eigentlich hätte ich schreiben müssen

    Quellcode

    1. Private myList as New BindingList(of Class1)


    Und nun möchte ich in einem DataGridView den Status der Prozesse ermitteln, der als Public ReadOnlyProperty zur Verfügung steht. Wie bereits gesagt, wenn ich mittels Me.DataGridView.DataSource = Nothing die DataSource kurzfristig lösche, funktioniert es tadellos, aber schön ist bestimmt was anderes.
    Schalte ein BindingSource dazwischen und füge dein Objekt in das BindingSource ein anstatt in die List (Of T)
    Beispiel:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim mylist As New List(Of Element)
    3. Dim bs As New BindingSource
    4. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    5. bs.DataSource = mylist
    6. DataGridView1.DataSource = bs
    7. End Sub
    8. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    9. ' Element wird über BindingSource hinzugefügt, anstatt in die List (Of Element)
    10. bs.Add(New Element(TextBox1.Text, TextBox2.Text))
    11. End Sub
    12. Private Class Element
    13. Private _nname As String
    14. Private _vname As String
    15. Public Sub New(ByVal nname As String, ByVal vname As String)
    16. _nname = nachname
    17. _vname = vorname
    18. End Sub
    19. Public ReadOnly Property Nachname() As String
    20. Get
    21. Return _nname
    22. End Get
    23. End Property
    24. Public ReadOnly Property Vorname() As String
    25. Get
    26. Return _vname
    27. End Get
    28. End Property
    29. End Class
    30. End Class
    Das klappt so leider auch nicht. Offensichtlich muss ich da noch ein klein wenig mehr dazu ausführen. Unter Berücksichtigung deiner Anweisungen, sofern ich sie richtig verstanden habe, sieht das nun so aus.

    Formular1 enthält den DataGridView, dass den aktuellen Status von laufenden Prozessen widerspiegel soll. Darauf befindet sich ein Button. Dadurch öffnet sich ein neues Formular, an das ich nun (ByRef bs as BindingSource) übergebe. Dort wird nach Ausfüllen von ein paar Textfeldern ein neuer Thread gestartet.

    Formular1

    Quellcode

    1. Dim bs as new BindingSource
    2. Dim myList as List(of Class1)
    3. Private Sub Form1_Load(...)
    4. bs.DataSource = myList
    5. DataGridView.DataSource = bs
    6. End Sub
    7. Private Sub ToolStripMenuItem_Click(...)
    8. Dim frm as Formular2 = New Formular2(bs)
    9. frm.Show
    10. End Sub


    Formular2

    Quellcode

    1. ...
    2. Public Sub New(ByRef bs as BindingSource)
    3. InitialiseComponent()
    4. End Sub
    5. Private Sub Start_Click()
    6. Dim newClass as New Class1(param1,param2)
    7. bs.Add(newClass)
    8. Dim newThread as New Thread(AddressOf newClass.Start)
    9. ...
    10. 'Formular2 schließen
    11. End Sub

    Chr78 schrieb:

    BindingList hatte ich auch schon probiert, da ich es in irgend einem Forum schon gelesen hatte.

    Dann liegt das Problem glaub woanders. Wenn du einer BindingList ein Item zufügst, updated sich das DGV.

    Oder willst du ein Databinding, bei dem sich auch einzelne Zellen updaten, wenn eine Property der class1 sich ändert?

    Dafür müsste class1 INotifyPropertyChanged implementieren, oder du steigst gleich auf typisiertes Dataset um, um in den Genuss vollständiger DataBindingFähigkeit zu kommen.

    ErfinderDesRades schrieb:


    Oder willst du ein Databinding, bei dem sich auch einzelne Zellen updaten, wenn eine Property der class1 sich ändert?

    Dafür müsste class1 INotifyPropertyChanged implementieren, oder du steigst gleich auf typisiertes Dataset um, um in den Genuss vollständiger DataBindingFähigkeit zu kommen.
    Danke, das wird wohl des Rätsels Lösung sein. Ich wusste nicht, das man zwischen neuen DS und Änderungen an bestehenden DS unterscheiden muss. Denn neue Zeilen kommen zwar mit neuen Prozessen hinzu, aber die sind meist Leer, da zu Beginn die Propertys ja alle noch leer sind, und sich erst im Laufe der Zeit und Fortschritt des Prozesses befüllen. Dann werde ich mich mal mit den INotifyPropertyChanged herumschlagen und recherchieren, was das denn überhaupt ist. Vielen Dank.

    ErfinderDesRades schrieb:

    Ich empfehle dir, gleich auf Dataset zu setzen. Da kriegst du allerlei Features gratis dazu, nicht unwahrscheinlich, dass du das eine oder andere davon (ZB Sortierbarkeit? Filter?) irgendwann in Anspruch nehmen möchtest.

    gugge DatasetOnly
    Das hatte ich auch in einer früheren Version. Einen DataTable in dem für jeden neuen Thread eine neue Row erstellt wurde, und wenn sich eine Eigenschaft des Threads geändert hatte, wurde das im DataTable weitergereicht und dort aktualisiert. Vielleicht tue ich mir auch an, und mach das nochmals so.

    ErfinderDesRades schrieb:

    Dafür müsste class1 INotifyPropertyChanged implementieren, oder du steigst gleich auf typisiertes Dataset um, um in den Genuss vollständiger DataBindingFähigkeit zu kommen.




    Ich hätte da noch ein grundsätzliche Frage zum INotifyPropertyChanged. Ich habe bisher nur Beispiele gefunden, in denen das gebundene Control Änderungen an die Klasse meldet, indem ich es im Set der jeweiligen Property implementiere. siehe zB msdn.microsoft.com/de-de/library/ms229614(v=vs.85).aspx. Das funktioniert ja auch hervorragend, aber wie implementiere ich es, wenn die Klasse das Property selbst verändert. Ich dachte, ich füge einfach nach dem Ändern der Property ein NotifyPropertyChanged("Name_der_Property") hinzu, anstelle im Set, da meine Properties ja nur Readonly sind.

    Quellcode

    1. Implements INotifyPropertyChanged
    2. Private _myPublicProperty1 as string = string.Empty
    3. ...
    4. Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    5. Private Sub NotifyPropertyChanged(ByVal info As String)
    6. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
    7. End Sub
    8. ....
    9. Public Sub Start()
    10. .....
    11. _myPublicProperty1 = "test"
    12. NotifyPropertyChanged("myPublicProperty1")
    13. End Sub
    14. Public ReadOnly Property myPublicProperty1() As String
    15. Get
    16. Return Me._myPublicProperty1
    17. End Get
    18. End Property


    Dann bekomme ich eine InvalidOperationException. "ungültiger threadübergreifender Vorgang."

    Auch wenn ich höchstwahrscheinlich ein typisiertes Dataset einführen werde, würde mich das interessieren, insbesondere, da ich viel mit Hintergrund Threads arbeite, deren Properties ich gerne an Formulare binde.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Chr78“ ()

    Was du zeigst, sieht mir soweit richtig aus (ma abgesehen von dem doofen Layout - ist das wirklich so mühsam, das lesbar zu machen?)

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Public Class Class1
    3. Implements INotifyPropertyChanged
    4. Private _myPublicProperty1 As String = String.Empty
    5. '...
    6. Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
    7. Private Sub NotifyPropertyChanged(ByVal info As String)
    8. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
    9. End Sub
    10. '....
    11. Public Sub Start()
    12. '.....
    13. _myPublicProperty1 = "test"
    14. NotifyPropertyChanged("myPublicProperty1")
    15. End Sub
    16. Public ReadOnly Property myPublicProperty1() As String
    17. Get
    18. Return Me._myPublicProperty1
    19. End Get
    20. End Property
    21. End Class
    Der Fehler wird also in den '...' liegen, die du nicht zeigst ;)

    StackOverflow dürfte kommen, wenn irgendwo im Zusammenhang mittm myPublicProperty1-Getter das Event nochmal geraist wird.

    Guck dirmal die AufrufeListe beim Fehler an, da müssten dieselben Methoden tausende Male immer wieder auftauchen.

    ErfinderDesRades schrieb:


    Was du zeigst, sieht mir soweit richtig aus (ma abgesehen von dem doofen Layout - ist das wirklich so mühsam, das lesbar zu machen?)


    StackOverflow dürfte kommen, wenn irgendwo im Zusammenhang mittm myPublicProperty1-Getter das Event nochmal geraist wird.



    @Layout: leider zerschießt mein Browser immer bei der Vorschau die schönen Hereinrückungen. IE ist nur mal wirklich schrott, und Opera - den ich normalerweise benutze - stützt beim Posten immer ab.

    @ Overflow

    Die Sache mit den Strack Overflow, hab ich mittlerweile wegbekommen, das war mein Fehler. Nun bekomme ich eine InvalidOperationException.
    genaue Fehlermeldung: InvalidOperationException - Ungültiger Vorgang: Der Zugriff auf das Steuerelement erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde. Direkt nach dem ersten "NotifyPropertyChanged("XY")" bleibt er beim ersten

    Quellcode

    1. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
    hängen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Chr78“ ()

    Hat sich wohl erübrigt, in einem neuen Projekt klappt es hervorragend, daher wird der Fehler wohl irgendwo anders liegen. Vorerst mal vielen Dank.

    Falls es jemanden interessiert (wie es theoretisch funktioniert) , Projektdatei angehängt.
    Dateien

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Chr78“ ()

    ist nicht ganz unriskant, aus einem NebenThread NotifyPropertyChanged zu feuern, weil du damit indirekt auf Controls zugreifst.

    Auch Notifizierst du eine Property "Zaehler", dies in Class1 garnet gibt

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.Threading
    3. Public Class Class1: Implements INotifyPropertyChanged
    4. Private _myPublicProperty1 As Integer = 0
    5. Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) _
    6. Implements INotifyPropertyChanged.PropertyChanged
    7. Private Sub NotifyPropertyChanged(ByVal info As String)
    8. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
    9. End Sub
    10. '....
    11. Public Sub Ausfuehren()
    12. Debug.Print("gestartet")
    13. For i As Integer = 0 To 5000 Step 1
    14. Debug.Print("Erhöhe Zähler auf " & i)
    15. _myPublicProperty1 = i
    16. NotifyPropertyChanged("Zaehler")
    17. Thread.Sleep(1000)
    18. Next
    19. End Sub
    20. Public ReadOnly Property myPublicProperty1() As Integer
    21. Get
    22. Return Me._myPublicProperty1
    23. End Get
    24. End Property
    25. End Class

    Erstaunlich gutmütig eigentlich, dein DatagridView ;)

    Lustiger wäre übrigens, wenn du Class1 noch eine Property spendierst, mit der du die SleepTime einstellen kannst - dann sieht man, wie die Zähler tatsächlich in ganz verschiedenen Intervallen hochzählen.

    Und wenn SleepTime auch noch NotifyPropertyChanged feuern würde, könnte man die Geschwindigkeit glaub sogar on the fly verändern :)