noch ein coloriertes Datagridview

    • VB.NET

      noch ein coloriertes Datagridview

      Hi!

      Heute habich wieder ein Datagridview coloriert. Prinzipiell wie in coloriertes DatagridView, aber die Solution zeigt noch paar Sachen mehr.
      • Vergleich der Befüllung von Datagridview und Listview Das DGV ist so designed, dasses von der Listview kaum unterscheidbar ist.
        Ausser natürlich seine überlegene Mächtigkeit an Funktionalität
        1. editieren aller Zellen, zufügen/löschen von Datensätzen
        2. sortieren + filtern (s. DataExpressions)
        3. verwendung typisierter Daten
        4. complex Databinding - etwa Comboboxen in DGV-Zellen (hier nicht gezeigt)
        5. Datenabhängige Colorierung (ownerdrawing)
        Es zeigt sich, dass Listview ca. 10mal langsamer ist. Selbst wenn man die Befüllung der Listview stark optimiert, isse immer noch 4 - 5 mal langsamer.
        Und der Befüll-Code ist auch komplizierter.

        VB.NET-Quellcode

        1. Private Sub FillListView()
        2. Dim SW As Stopwatch = Stopwatch.StartNew()
        3. Me.ListView1.BeginUpdate()
        4. Me.ListView1.Items.Clear()
        5. For i As Integer = 0 To RowCount - 1
        6. Dim lvi As ListViewItem = Me.ListView1.Items.Add((i).ToString())
        7. lvi.UseItemStyleForSubItems = False
        8. lvi.ImageIndex = i Mod Me.ImageList1.Images.Count
        9. Dim lvsi As ListViewItem.ListViewSubItem = lvi.SubItems.Add((i + 1).ToString())
        10. lvsi.BackColor = Color.Red
        11. lvsi = lvi.SubItems.Add((i + 2).ToString())
        12. lvsi.BackColor = Color.Blue
        13. lvsi = lvi.SubItems.Add((i + 3).ToString())
        14. lvsi.BackColor = Color.Violet
        15. lvsi = lvi.SubItems.Add((i + 4).ToString())
        16. lvsi = lvi.SubItems.Add((i + 5).ToString())
        17. Next
        18. Me.ListView1.EndUpdate()
        19. Me.ToolStripStatusLabel1.Text = String.Format( _
        20. "filled in {0} Milliseconds", SW.ElapsedMilliseconds)
        21. End Sub
        vergleiche mit:

        VB.NET-Quellcode

        1. Private Sub FillGrid()
        2. Dim SW As Stopwatch = Stopwatch.StartNew()
        3. Dim tb As DataSet1.NumbDataTable = DataSet1.Numb
        4. NumbBindingSource.ListChangedEnabled(False)
        5. tb.BeginLoadData()
        6. tb.Clear()
        7. For i As Integer = 0 To RowCount - 1
        8. tb.AddNumbRow(i Mod Me.ImageList1.Images.Count, i, i + 1, i + 2, i + 3, i + 4, i + 5)
        9. Next
        10. tb.EndLoadData()
        11. NumbBindingSource.ListChangedEnabled(True)
        12. Me.ToolStripStatusLabel1.Text = String.Format( _
        13. "filled in {0} Milliseconds", SW.ElapsedMilliseconds)
        14. End Sub


      • Colorierung - OwnerDrawing Vergleichbar zum ListView, wo man die BackColor am einzelnen ListViewSubitem einstellen kann, kann man beim DGV Einstellungen an den Cellstyles vornehmen.
        Noch mächtiger ist aber die Unterstützung von OwnerDrawing der einzelnen Zellen. Dazu muß man das DGV_CellPainting abonnieren, und dort die Eventargs auswerten, und entsprechend die Zeichnung der Zelle vollständig oder auch nur teilweise selbst vornehmen. Auf diese Weise kann man in Abhängigkeit der aktuellen Datenwerte zB. verschiedene BackColors anzeigen, und wenn der User einen neuen Wert eingibt, stellt sich die BackColor automatisch drauf ein.
        Ich habe hier ein _CellPainting implementiert, wo als Besonderheit für die erste Zelle ein Image mit hineingezeichnet wird, damits wirklich wie ListView aussieht. In der 3.Spalte habe ich außerdem eine Wert-Abhängige Colorierung eingebaut, sodaß der Wert 11 immer gelb hinterlegt wird:

        VB.NET-Quellcode

        1. Private Sub DataGridView1_CellPainting(ByVal sender As Object, _
        2. ByVal e As DataGridViewCellPaintingEventArgs) Handles DataGridView1.CellPainting
        3. If e.RowIndex < 0 OrElse e.RowIndex >= Me.NumbBindingSource.Count Then Return
        4. Dim drv = DirectCast(DataGridView1.Rows(e.RowIndex).DataBoundItem, DataRowView)
        5. Dim rwNumb = DirectCast(drv.Row, DataSet1.NumbRow)
        6. Dim isSelected = (e.State And DataGridViewElementStates.Selected) <> 0
        7. Select Case DataGridView1.Columns(e.ColumnIndex).DataPropertyName
        8. Case DataSet1.Numb.NumbWithIconColumn.ColumnName
        9. e.PaintBackground(e.CellBounds, isSelected)
        10. Dim Img As Image = _Images(rwNumb.ImageIndex)
        11. Dim Y As Integer = e.CellBounds.Top + (e.CellBounds.Height - Img.Height) \ 2
        12. e.Graphics.DrawImageUnscaled(Img, e.CellBounds.X + 4, Y)
        13. Case DataSet1.Numb.Numb4Column.ColumnName
        14. If isSelected OrElse rwNumb.Numb4 <> 11 Then Return
        15. e.Graphics.FillRectangle(Brushes.Yellow, e.CellBounds)
        16. Case Else : Return
        17. End Select
        18. e.PaintContent(e.CellBounds)
        19. e.Handled = True
        20. End Sub

      Das ist natürlich ziemlich komplex, so ein _CellPainting. Zunächst muß -1 als RowIndex und ColumnIndex ausgeschlossen werden (ein RowIndex von -1 indiziert, das ein ColumnHeader zu zeichnen wäre). Ist Zufügen von Zeilen erlaubt muß zusätzlich verhindert werden, dass die Zufügezeile gezeichnet wird, also e.RowIndex > grid.RowCount-2 ist zusätzlich abzufangen. Denn fürs Daten-abhängige Zeichnen der Zufügezeile steht gar kein Datensatz bereit.
      Dann muß die zu zeichnende typisierte Datarow ermittelt werden. Dann, ob die Zelle selectiert ist (dann ist sie ja auch anners zu zeichnen). Schließlich noch ein Select, der checkt, ob die Zelle ühaupt in einer Spalte liegt, die selbst gezeichnet werden soll.
      Interessant sind die EventArgs dieses Events, die enthalten nämlich 3 leistungsfähige Zeichenfunktionen: PaintBackground, PaintContent und Paint, wobei man bei Paint per Flags sehr differenziert angeben kann, welches Zell-Element gezeichnet wern soll:


      DGV-Behavior Man sieht: son _CellPainting erfordert eine ziemlich komplexe Logik. Deshalb habich eine Art DGV-Behavior geschrieben, welches ein ganz ähnliches Event wirft, aber mit noch praktischeren EventArgs. Also derselbe OwnerDrawing-Code sieht bei Verwendung meines EventArgs so aus:

      VB.NET-Quellcode

      1. Private Sub _DataPainter_Painting(ByVal e As DGVDataPaintEventArgs(Of DataSet1.NumbRow)) _
      2. Handles _DataPainter.Painting
      3. Select Case True
      4. Case e.Column Is DataSet1.Numb.NumbWithIconColumn
      5. e.PaintBackground()
      6. Dim Img As Image = _Images(e.Row.ImageIndex)
      7. Dim Y As Integer = e.CellBounds.Top + (e.CellBounds.Height - Img.Height) \ 2
      8. e.Graphics.DrawImageUnscaled(Img, e.CellBounds.X + 4, Y)
      9. Case e.Column Is DataSet1.Numb.Numb4Column
      10. e.PaintBackground(If(e.Row.Numb4 = 11, Color.Yellow, Color.Empty))
      11. Case Else : Return
      12. End Select
      13. e.Handled = True
      14. e.PaintContent()
      15. End Sub
      Man hantiert also mit weniger Objekten, braucht nicht die Begrenzungen der Indizees abzuprüfen, und das Grid und die betroffene DataRow bekommt man auch gleich mitgeliefert.
      Der Code gerät also ein Stück mehr straight forward als unter Verwendung der normalen PaintEventArgs.


      Wie gesagt: Weitere Möglichkeiten des OwnerDrawings sind in coloriertes DatagridView gezeigt, nämlich u.a. wie man nach Werten eines übergeordneten Datensatzes Zellen coloriert.
      Oder das hier ist auch nett: DatagridViewProgressCell


      Letzter Link
      Steuerelemente mit integrierter Ownerdrawing-Unterstützung - erläutert das Konzept grundlegender - gilt ja auch für andere Controls
      .
      Dateien

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