RadioButton-Listbox

    • VB.NET

      RadioButton-Listbox

      Hier ein Sample, wie man den Joining-Anteil eines DetailViews auch mal mit einer Listbox gestalten kann.
      Dabei ist die Listbox ownerdrawn, und zwar die ListboxItems sehen aus wie Radiobuttons.
      Datenmodell sind Person-Datensätze, und ein Feld eines Person-Datensatzes heißt "Kuerzel", und ist aus einer anderen DataTable. In der Anwendung kann man nun eine Person anwählen, und über (scheinbare) Radiobuttons bestimmen, welches Kürzel sie haben soll:

      Auf der rechten Seite - das ist keine Gruppe von Radiobuttons, sondern ist eine auf Radiobutton gestylte Listbox.
      Zunächstmal habichsie "unsichtbar" gemacht, mittels BackColor.Control und BorderStyle.None. Dann habichnoch DrawMode.OwnerDrawFixed gesetzt, und ItemHeight auf 20 erhöht, da RadioButtons etwas mehr Platz einnehmen als Standard-ListboxItems.
      Das ganze mit Dock.Fill auf eine Groupbox, um eine evidente Beschriftung zu gewährleisten.

      Code:

      VB.NET-Quellcode

      1. Imports RadioListbox.DataSet1
      2. Imports System.Windows.Forms.VisualStyles
      3. Public Class Form1
      4. Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
      5. 'SampleDaten generieren
      6. Dim rwDefaultKuerzel = DataSet1.Kuerzel.AddKuerzelRow("MD")
      7. DataSet1.Kuerzel.AddKuerzelRow("LF")
      8. DataSet1.Kuerzel.AddKuerzelRow("BK")
      9. With DataSet1.Person
      10. .AddPersonRow("Hans", rwDefaultKuerzel)
      11. .AddPersonRow("Peter", rwDefaultKuerzel)
      12. .AddPersonRow("Frank", rwDefaultKuerzel)
      13. End With
      14. End Sub
      15. Private Sub lstRadio_DrawItem(ByVal sender As Object, ByVal e As DrawItemEventArgs) _
      16. Handles lstRadio.DrawItem
      17. If e.Index < 0 Then Return
      18. Dim lb = DirectCast(sender, ListBox)
      19. 'zum Item gehörige KürzelRow holen
      20. Dim rwKuerzel = DirectCast(DirectCast( _
      21. KuerzelBindingSource(e.Index), DataRowView).Row, KuerzelRow)
      22. Dim focusRect = e.Bounds
      23. focusRect.X += focusRect.Height
      24. Dim sz = e.Graphics.MeasureString(rwKuerzel.Kuerzel, lb.Font)
      25. focusRect.Size = Size.Round(sz)
      26. Dim focused = e.Index = lb.SelectedIndex
      27. Dim glyphState = If(focused, RadioButtonState.CheckedNormal, _
      28. RadioButtonState.UncheckedNormal)
      29. focused = focused And lb.Focused
      30. RadioButtonRenderer.DrawRadioButton(e.Graphics, e.Bounds.Location, focusRect, _
      31. rwKuerzel.Kuerzel, lb.Font, TextFormatFlags.Left, focused, glyphState)
      32. End Sub
      33. Private Sub KuerzelBindingSource_CurrentChanged(ByVal sender As Object, ByVal e As EventArgs) _
      34. Handles KuerzelBindingSource.CurrentChanged
      35. lstRadio.Invalidate() 'alle Items neu zeichnen
      36. PersonBindingSource.EndEdit() 'neuen Wert gleich in die Person übernehmen
      37. End Sub
      38. End Class

      Erläuterung:
      Das Stylen der ListboxItems geschieht in lstRadio_DrawItem.
      Dort wird zunächstmal die KürzelRow am zu zeichnenden Index aus der BindingSource geholt.
      Dann wird das FocusRect berechnet - das ist nämlich nur ein Teil des gesamten ItemRect des ListboxItems, und etwas nach rechts versetzt, damit der RadioButton-Punkt ("Glyph") nicht vonne Text-Darstellung übermalt wird.
      focused wird doppelt verwendet: Zunächstmal wird festgestellt, ob der zu zeichnende Index der SelectedIndex der Listbox ist - dann muß der glyphState natürlich Checked gezeichnet wern.
      Und das FocusRect wird nur gezeichnet, wenn die Listbox den Focus auch hat (s.Zeile #32)
      Jo, und dann alles vom RadioButtonRenderer rendern lassen.

      DataBinding
      DataSource der KürzelListbox ist die KürzelBindingSource, DisplayMember ist die Spalte "Kuerzel". "Kuerzel" ist auch der ValueMember, denn die Kürzel-DataTable enthält nur diese Spalte, und die ist auch Primärschlüssel. Der SelectedValue wird in die PersonBindingSource geschrieben, nämlich in die dortige Kürzel-Spalte. Denn in der Person-DataTable gibts auch die Kürzel-Spalte - hier ist sie aber ein Fremdschlüssel, der auf die Kürzel-Tabelle verweist.
      Hiermal Bild vom Dataset, inklusive DataRelation-Konfiguration, und rechts wie das Listbox-Databinding eingerichtet ist:
      . . . .

      Die BindingSources bekommt man hingeneriert, wenn man die DataTables aus dem Datenfenster (Menü-Daten-Datenquellen) aufs Form zieht. Dabei bekommt man noch mehr hingeneriert, was man am besten gleich wieder runterschmeißt - insbesondere den total bescheuerten BindingNavigator.

      Dann muß noch dem sofortigen Vollzug des Databindings nachgeholfen werden, nämlich wenn die KürzelBindingSource den Current changed, muß die Listbox explizit aufgefordert werden, sich neu zu zeichnen, und der neue Wert soll auch sofort in die Person-BindingSource übertragen werden - sodass das neue Kürzel auch im DataGridView sichtbar wird.

      Im DataGridView ist nur die Name-Spalte nicht Readonly. Die Kürzel-Spalte ist eine ComboboxDataGridViewColumn, die ihre DataSource aus der KürzelTabelle bezieht. Aber da diese Spalte Readonly ist, ist auch der DropDownStyle dieser ComboboxColumn auf Nothing gesetzt - die Combos sollen ja nicht droppen - zur Eingabe soll ja die RadiobuttonListbox verwendet werden.
      Spasseshalber habe ich übrigens auch eine ungestylte Listbox aufs Form geschmissen, nur um zu zeigen, dass das RadioButton-Styling eigentlich ein entbehrliche Gimmik ist.

      Wie man sieht: Das "Gimmik" mit der Darstellung der ListboxItems als Radiobuttons macht die meiste Arbeit - ohne das gäbe es nur KuerzelBindingSource_CurrentChanged() mit nur der Zeile PersonBindingSource.EndEdit() - und feddich.

      Wie immer bei Dataset und Co: Voll editierbar (create, read, update, delete), und Laden und Abspeichern sind Standard-Tasks - je nach Persistier-Strategie (Datenbank oder Dataset.WriteXml) sogar auch mit Einzeilern abzuhandeln (hier weggelassen - stattdessen generiere ich paar Daten im Form_Load)

      Warum das alles?
      Warum nicht einfach ein paar RadioButtons in die Groupbox tun - dann hat man kein Theater mit OwnerDrawing?
      Ja, wäre eine denkbare Alternative. Man müsste halt die CheckedChanged_Events aller RadioButtons verarbeiten, und ihre Betextung einfach in den aktuellen Datensatz der PersonBindingSource schreiben (Personbindingsource.Current).
      Wäre halt nicht die reine datenbänkerische Lehre, weil man damit Daten gewissermaßen "festverdrahtet" ins Programm einkompiliert hätte. Sollte sich jemals etwas ändern an den vorgesehenen Kürzeln, müssteman ein Anwendungs-Update ausliefern.
      Alternativ könnte man auch nach dem Laden der Kürzeltabelle die Radiobuttons zur Laufzeit generieren und verdrahten. Letztere Vorgehensweise wäre aber mindestens(!) ebenso aufwändig wie die hier gezeigte Vorgehensweise, die die Organisation der Controls im Form-Designer belässt, und sich darauf beschränkt, "nur" die Darstellung der Daten zu modifizieren.

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