Progressbar im DatagridView - benutzerdefiniertes Databinding

    • VB.NET

      Progressbar im DatagridView - benutzerdefiniertes Databinding

      Hier ein bischen Code, welcher 2 sehr verschiedene Themen betrifft: Zum einen ein benutzerdefiniertes Databinding, zum andern ein besonderes OwnerDrawing im DatagridView: in eine Spalte wird eine Progressbar mit Prozent-Anzeige gezeichnet.

      Das Databinding
      Hier liegt ein EinzelblattView vor, d.h.: es gibt ein gebundenes ListenControl (das DatagridView) und mindestens ein EinzelControl (die Trackbar) ist an dieselbe Datasource gebunden.
      Durch das binden an dieselbe Datasource synchronisieren die Controls sich gegenseitig - wenn man also per Trackbar was eingibt, verändert sich der Wert auch im Grid, und umgekehrt.
      Allerdings habe ich die Eingabe per Grid deaktiviert, denn dort könnten ansonsten auch Werte > 1 eingegeben werden, oder sonstwas, was die Progressbar-Cell nicht darstellen kann. Stichwort Benutzerführung ;)
      Das Binding wirft zwei Probleme auf:
      1. Die Value-Eigenschaft der Trackbar ist vom Typ Integer - im Dataset ist die zu bindende Spalte aber vom Typ Single (Prozent-Werte zw. 0 und 1)
      2. Das Grid aktualisiert sich nicht von selbst - da muß nachgeholfen werden

      Initialisierung des Bindings

      VB.NET-Quellcode

      1. Private WithEvents _Bnd As Binding
      2. Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
      3. _Bnd = TrackBar1.DataBindings.Add("Value", ADataBindingSource, "Numb", _
      4. False, DataSourceUpdateMode.OnPropertyChanged)
      5. _Saver.Load(PrgDts)
      6. End Sub
      Ich habe hier das Binding mal nicht im Designer erstellt, sondern im Form_Load. Erst nach Erstellung des Bindings wird das Dataset gefüllt (_Saver.Load()) - und über das Binding stellt sich dann auch die Trackbar richtig ein.
      Besonderheit ist, dass das Binding als WithEvents-Variable klassenweit verfügbar ist. Wir verwenden nämlich die Events, um Trackbar.Value in einen Datenwert zw. 0.0 und 1.0 zu konvertieren und umgekehrt. Ansonsten würde die Trackbar ja viel zu hohe Werte in die DataSource eingeben, und viel zu niedrige bekommen (abgesehen davon, dass Integer und Single garnet zusammenpassen)
      Also Konvertierung des Datenwertes in einen für die Trackbar tauglichen Wert (Formatting):

      VB.NET-Quellcode

      1. Private Sub _Bnd_Format(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Format
      2. e.Value = CInt(DirectCast(e.Value, Single) * TrackBar1.Maximum)
      3. End Sub
      Der Datenwert - ein Single zw. 0.0 und 1.0 wird mit Trackbar.Maximum multipliziert - feddich.

      Konvertierung des Control-Wertes in einen für die Datasource tauglichen Wert (Parsing):

      VB.NET-Quellcode

      1. Private Sub _Bnd_Parse(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Parse
      2. e.Value = CSng(DirectCast(e.Value, Integer) / TrackBar1.Maximum)
      3. End Sub
      Der Trackbar-Wert - ein Integer zw. 0 und Trackbar.Maximum wird durch Trackbar.Maximum geteilt - feddich.

      Datenmäßig ist nun alles in Butter - das DatagridView (dessen Bindung im Designer eingestellt werden konnte) ist nun mit der Trackbar synchronisiert - trotzdem zickt die Darstellung noch rum - das DGV wird nicht sofort refresht.
      Das liegt nämlich daran, dass die BindingSource auf ein besonderes "Eingabe beendet" - Kommando wartet, bevor sie einen Wert in die DataTable schreibt - müssen wir ihr leider explizit geben:

      VB.NET-Quellcode

      1. Private Sub _Bnd_Parse(ByVal sender As Object, ByVal e As ConvertEventArgs) _
      2. Handles _Bnd.Parse
      3. e.Value = CSng(DirectCast(e.Value, Integer) / TrackBar1.Maximum)
      4. ADataBindingSource.EndEdit()
      5. End Sub

      Noch einmal das Databinding-Zeugs in seiner Gesamtheit

      VB.NET-Quellcode

      1. Private _Saver As New DataSetSaver(Me, "..\..\prgDts.xml")
      2. Private WithEvents _Bnd As Binding
      3. Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
      4. _Bnd = TrackBar1.DataBindings.Add("Value", ADataBindingSource, "Numb", _
      5. False, DataSourceUpdateMode.OnPropertyChanged)
      6. _Saver.Load(PrgDts)
      7. End Sub
      8. Private Sub _Bnd_Format(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Format
      9. e.Value = CInt(DirectCast(e.Value, Single) * TrackBar1.Maximum)
      10. End Sub
      11. Private Sub _Bnd_Parse(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Parse
      12. e.Value = CSng(DirectCast(e.Value, Integer) / TrackBar1.Maximum)
      13. ADataBindingSource.EndEdit()
      14. End Sub

      OwnerDrawing - Progressbar
      Das Datagridview_CellPainting - Event ist bisserl schlecht designed, sodaß man schon recht kompliziert filtern muß, bevor man ühaupt anfangen kann, die gewünschte Zelle zu zeichnen.
      Daher habich eine Wrapper-Klasse gemacht (DGVDataPaintEventArgs(Of T)), die bereits einige Vor-Filterungen durchführt, und auch noch praktischere Zeichen-Funktionen mitbringt. Die Klasse liegt bei, das Tut zeigt nur die Verwendung:

      VB.NET-Quellcode

      1. Imports DgvProgressCell.PrgDts ' Klassen des typisierten Datasets importieren
      2. Public Class frmDgvProgressCell
      3. Private WithEvents _Painter As New DGVDataPaintEventArgs(Of aDataRow)
      4. Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
      5. _Painter.SetGrid(ADataDataGridView)
      6. End Sub
      7. Private Sub _Painter_Painting(ByVal e As DGVDataPaintEventArgs(Of PrgDts.aDataRow)) Handles _Painter.Painting
      8. If e.Column IsNot PercentColumn Then Return
      9. e.PaintBackground()
      10. Dim rct = e.CellBounds
      11. rct.Location += New Size(2, 2)
      12. rct.Size -= New Size(5, 5)
      13. Dim rctF As RectangleF = rct
      14. rctF.Width *= Math.Min(1.0F, e.Row.Numb)
      15. e.Graphics.FillRectangle(Brushes.LightGreen, rctF)
      16. rctF.X += rctF.Width
      17. rctF.Width = rct.Width - rctF.Width
      18. e.Graphics.FillRectangle(Brushes.WhiteSmoke, rctF)
      19. With PercentColumn.DefaultCellStyle
      20. TextRenderer.DrawText( _
      21. e.Graphics, String.Format("{0:N0}%", e.Row.Numb * 100), _
      22. .Font, rct, Color.Gray)
      23. End With
      24. e.Handled = True
      25. End Sub
      26. End Class
      Erläuterung:
      Zunächst wird nix gepaintet, wenn e.Column nicht die PercentColumn ist (so habe ich im Designer die DGV-Column genannt, in der ich die Progressbar malen will)
      Dann wird der Standard-Hintergrund gemalt.
      Dann wird ein Rectangle innerhalb der Zelle ermittelt, welches nach oben und links einen Abstand von 2 Pixeln hat, und nach untenrechts 3 Pixel.
      Ein ebensogroßes RectangleF wird gebildet - der Unterschied ist, dass RectangleF seine Abmaße in Single bestimmt, nicht in Integer wie Rectangle. Dessen Breite wird durch Multiplikation mit dem Datenwert (der ja zw. 0.0 und 1.0 liegt) verkleinert.
      Jo, dieses RectangleF wird dann als Fortschrittsbalken gezeichnet - in LightGreen.
      Aber um den Rest der Progressbar auch zu zeichnen (also wo der Fortschrittsbalken nicht ist), wird das RectangleF nach rechts verschoben, und in der Breite so angepasst, dasses genau den Rest der PB darstellt. Und dann natürlich zeichnen - in WhiteSmoke.
      Dann wollemer den Wert aber auch als Prozentzahl formatiert reinmalen - in Gray, wenns recht ist ;).
      Ganz zum Abschluß wird dem Grid noch mitgeteilt, dasses diese Zelle nicht mehr malen muß, denn e.Handled = True

      Das komplette Form, mit Databinding und Ownerdrawing (und zusätzlich mit Abspeichern)

      VB.NET-Quellcode

      1. Imports System.IO
      2. Imports DgvProgressCell.PrgDts
      3. Public Class frmDgvProgressCell
      4. Private _Saver As New DataSetSaver(Me, "..\..\prgDts.xml")
      5. Private WithEvents _Painter As New DGVDataPaintEventArgs(Of aDataRow)
      6. Private WithEvents _Bnd As Binding
      7. Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
      8. _Painter.SetGrid(ADataDataGridView)
      9. _Bnd = TrackBar1.DataBindings.Add("Value", ADataBindingSource, "Numb", _
      10. False, DataSourceUpdateMode.OnPropertyChanged)
      11. _Saver.Load(PrgDts)
      12. End Sub
      13. Private Sub _Bnd_Format(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Format
      14. e.Value = CInt(DirectCast(e.Value, Single) * TrackBar1.Maximum)
      15. End Sub
      16. Private Sub _Bnd_Parse(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Parse
      17. e.Value = CSng(DirectCast(e.Value, Integer) / TrackBar1.Maximum)
      18. ADataBindingSource.EndEdit()
      19. End Sub
      20. Private Sub MenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) _
      21. Handles ReloadToolStripMenuItem.Click, SaveToolStripMenuItem.Click, btAdd.Click
      22. Select Case True
      23. Case sender Is ReloadToolStripMenuItem
      24. _Saver.Load(PrgDts)
      25. Case sender Is SaveToolStripMenuItem
      26. _Saver.Save(PrgDts)
      27. Case sender Is btAdd
      28. ADataBindingSource.AddNew()
      29. End Select
      30. End Sub
      31. Private Sub Form_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
      32. _Saver.HandleFormClosing(PrgDts, e)
      33. End Sub
      34. Private Sub _Painter_Painting(ByVal e As DGVDataPaintEventArgs(Of PrgDts.aDataRow)) Handles _Painter.Painting
      35. If e.Column IsNot PercentColumn Then Return
      36. e.PaintBackground()
      37. Dim rct = e.CellBounds
      38. rct.Location += New Size(2, 2)
      39. rct.Size -= New Size(5, 5)
      40. Dim rctF As RectangleF = rct
      41. rctF.Width *= Math.Min(1.0F, e.Row.Numb)
      42. e.Graphics.FillRectangle(Brushes.LightGreen, rctF)
      43. rctF.X += rctF.Width
      44. rctF.Width = rct.Width - rctF.Width
      45. e.Graphics.FillRectangle(Brushes.WhiteSmoke, rctF)
      46. With PercentColumn.DefaultCellStyle
      47. TextRenderer.DrawText( _
      48. e.Graphics, String.Format("{0:N0}%", e.Row.Numb * 100), _
      49. .Font, rct, Color.Gray)
      50. End With
      51. e.Handled = True
      52. End Sub
      53. End Class

      Und noch Bildchen:



      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 7 mal editiert, zuletzt von „ErfinderDesRades“ ()