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:
Initialisierung des Bindings
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):
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):
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:
Noch einmal das Databinding-Zeugs in seiner Gesamtheit
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:
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)
Und noch Bildchen:
Letzter Link
Steuerelemente mit integrierter Ownerdrawing-Unterstützung - erläutert das Konzept grundlegender - gilt ja auch für andere Controls
.
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:
- 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)
- Das Grid aktualisiert sich nicht von selbst - da muß nachgeholfen werden
Initialisierung des Bindings
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):
Konvertierung des Control-Wertes in einen für die Datasource tauglichen Wert (Parsing):
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
- Private _Saver As New DataSetSaver(Me, "..\..\prgDts.xml")
- Private WithEvents _Bnd As Binding
- Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
- _Bnd = TrackBar1.DataBindings.Add("Value", ADataBindingSource, "Numb", _
- False, DataSourceUpdateMode.OnPropertyChanged)
- _Saver.Load(PrgDts)
- End Sub
- Private Sub _Bnd_Format(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Format
- e.Value = CInt(DirectCast(e.Value, Single) * TrackBar1.Maximum)
- End Sub
- Private Sub _Bnd_Parse(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Parse
- e.Value = CSng(DirectCast(e.Value, Integer) / TrackBar1.Maximum)
- ADataBindingSource.EndEdit()
- 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
- Imports DgvProgressCell.PrgDts ' Klassen des typisierten Datasets importieren
- Public Class frmDgvProgressCell
- Private WithEvents _Painter As New DGVDataPaintEventArgs(Of aDataRow)
- Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
- _Painter.SetGrid(ADataDataGridView)
- End Sub
- Private Sub _Painter_Painting(ByVal e As DGVDataPaintEventArgs(Of PrgDts.aDataRow)) Handles _Painter.Painting
- If e.Column IsNot PercentColumn Then Return
- e.PaintBackground()
- Dim rct = e.CellBounds
- rct.Location += New Size(2, 2)
- rct.Size -= New Size(5, 5)
- Dim rctF As RectangleF = rct
- rctF.Width *= Math.Min(1.0F, e.Row.Numb)
- e.Graphics.FillRectangle(Brushes.LightGreen, rctF)
- rctF.X += rctF.Width
- rctF.Width = rct.Width - rctF.Width
- e.Graphics.FillRectangle(Brushes.WhiteSmoke, rctF)
- With PercentColumn.DefaultCellStyle
- TextRenderer.DrawText( _
- e.Graphics, String.Format("{0:N0}%", e.Row.Numb * 100), _
- .Font, rct, Color.Gray)
- End With
- e.Handled = True
- End Sub
- End Class
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
VB.NET-Quellcode
- Imports System.IO
- Imports DgvProgressCell.PrgDts
- Public Class frmDgvProgressCell
- Private _Saver As New DataSetSaver(Me, "..\..\prgDts.xml")
- Private WithEvents _Painter As New DGVDataPaintEventArgs(Of aDataRow)
- Private WithEvents _Bnd As Binding
- Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
- _Painter.SetGrid(ADataDataGridView)
- _Bnd = TrackBar1.DataBindings.Add("Value", ADataBindingSource, "Numb", _
- False, DataSourceUpdateMode.OnPropertyChanged)
- _Saver.Load(PrgDts)
- End Sub
- Private Sub _Bnd_Format(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Format
- e.Value = CInt(DirectCast(e.Value, Single) * TrackBar1.Maximum)
- End Sub
- Private Sub _Bnd_Parse(ByVal sender As Object, ByVal e As ConvertEventArgs) Handles _Bnd.Parse
- e.Value = CSng(DirectCast(e.Value, Integer) / TrackBar1.Maximum)
- ADataBindingSource.EndEdit()
- End Sub
- Private Sub MenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) _
- Handles ReloadToolStripMenuItem.Click, SaveToolStripMenuItem.Click, btAdd.Click
- Select Case True
- Case sender Is ReloadToolStripMenuItem
- _Saver.Load(PrgDts)
- Case sender Is SaveToolStripMenuItem
- _Saver.Save(PrgDts)
- Case sender Is btAdd
- ADataBindingSource.AddNew()
- End Select
- End Sub
- Private Sub Form_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
- _Saver.HandleFormClosing(PrgDts, e)
- End Sub
- Private Sub _Painter_Painting(ByVal e As DGVDataPaintEventArgs(Of PrgDts.aDataRow)) Handles _Painter.Painting
- If e.Column IsNot PercentColumn Then Return
- e.PaintBackground()
- Dim rct = e.CellBounds
- rct.Location += New Size(2, 2)
- rct.Size -= New Size(5, 5)
- Dim rctF As RectangleF = rct
- rctF.Width *= Math.Min(1.0F, e.Row.Numb)
- e.Graphics.FillRectangle(Brushes.LightGreen, rctF)
- rctF.X += rctF.Width
- rctF.Width = rct.Width - rctF.Width
- e.Graphics.FillRectangle(Brushes.WhiteSmoke, rctF)
- With PercentColumn.DefaultCellStyle
- TextRenderer.DrawText( _
- e.Graphics, String.Format("{0:N0}%", e.Row.Numb * 100), _
- .Font, rct, Color.Gray)
- End With
- e.Handled = True
- End Sub
- 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
.
Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von „ErfinderDesRades“ ()