Datagridview-Performance

    • VB.NET
    • .NET (FX) 4.0

      Datagridview-Performance

      Das Laden vieler Datensätze in eine an ein DGV gebundene DataTable dauert oft unerträglich lange.
      Üblicherweise untersucht man dann langwierig den Befüll-Vorgang - findet aber nichts.
      Weil das Problem hat mit der DataTable nichts zu tun, sondern Übeltäter ist das gebundene DGV, nämlich wenn seine Spaltenbreiten sich automatisch anpassen (DataGridViewAutoSizeColumnsMode.AllCells/AllCellsExceptHeaders).
      Der schnellste BugFix wäre also, DataGridViewAutoSizeColumnsMode.None einzustellen.
      Aber an den Inhalt angepasste Spaltenbreiten sind eigentlich ein unverzichtbares Feature.

      Also habe ich das Feature in optimierter Weise nachprogrammiert, und bin damit mehr als 10 mal schneller:

      VB.NET-Quellcode

      1. Private Sub AllCellsExceptHeader(dgv As DataGridView)
      2. 'Durchläuft für jede Spalte alle zeilen und sucht die sechs längsten Texte. Nur diese werden ausgemessen, und das Maximum als ColumnWidth gesetzt
      3. 'Annahme: alle Zellen der Spalte haben denselben Font. Keine Image-Cells.
      4. 'Zuschläge müsste man noch einführen Für Button- oder Combobox-Columns, sowie für GridLines, und vmtl. noch weiteren Schnickschnack.
      5. Dim fillingCols = dgv.Columns.Cast(Of DataGridViewColumn).ToLookup(Function(c) c.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill)
      6. fillingCols(True).ForEach(Sub(c) c.AutoSizeMode = DataGridViewAutoSizeColumnMode.None) ' alle AutoSize.Fill deaktivieren
      7. For Each col In fillingCols(False) ' alle Columns mit AutoSizeMode <> Fill
      8. Dim q = New Queue(Of String)(" ".Split) '6 mal ""
      9. Dim maxLen = 0
      10. Dim x = col.Index
      11. For y = 0 To dgv.Rows.Count - 1
      12. Dim s = dgv(x, y).FormattedValue.ToString
      13. If s.Length > maxLen Then
      14. q.Dequeue()
      15. q.Enqueue(s)
      16. maxLen = s.Length
      17. End If
      18. Next
      19. col.Width = q.Max(Function(s) TextRenderer.MeasureText(s, col.DefaultCellStyle.Font).Width)
      20. Next
      21. fillingCols(True).ForEach(Sub(c) c.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill) ' alle AutoSize.Fill reaktivieren
      22. End Sub
      Die Methode ist nicht perfekt - eben eine typische Optimierung: Erkauft wird die Geschwindigkeit durch gewisse Einschränkungen der Gestaltungsfreiheit des DGVs.
      Auch ist einiges noch nicht berücksichtigt - für gewisse Settings müsste man evtl. nachbessern.

      Edit: Ja, unter Umständen wird man stark nachbessern müssen. Bislang nicht bedacht habe ich, dass DGV-Spalten einen Format-String haben können, um etwa Datumse oder Zahlen ansprechend zu präsentieren.
      Ich vermute, in diesen Spalten wird dgv(x, y).FormattedValue.ToString deutlich mehr Zeit beanspruchen. Gleichzeitig sind die Spaltenbreiten derlei Spalten üblicherweise total unproblematisch. Vielleicht lässt man diese Spalten einfach aus, und traut dem User zu, deren Breite händisch festzulegen.
      Weiteres OptimierungsPotential läge in Parallelisierung. Denkbar ist, das Durchkämmen aller Zeilen an Paralel.For zu delegieren, und so alle Kernel beaufschlagen.
      Es sind ja nur Lese-Zugriffe - daher vermutlich threadübergreifend zulässig (hoffe ich, probiert hab ichs nicht).

      Anbei eine TestAnwendung, die auch sehr hübsch die Zeiten verschiedener Vorgänge (laden, verschiedenerlei AutoSizeModi setzen) loggt.
      Dateien

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „ErfinderDesRades“ ()