Bunter Listboxen!

Mit der Listbox ist das so ne Sache: Einerseits ist sie wirklich leicht bedienbar, andererseits erscheinen ihre Ausgabemöglichkeiten sehr eingeschränkt.

Ganz so klein sind sie aber nicht, denn man hat die Möglichkeit, jederzeit die Paint-Events der Listbox zu übernehmen und Hintergrund sowie Text ganz frei zu gestalten. Das verwirrt viele Neo-Xojoisten, die gerne so etwas wie FormattedText hätten. Den kann es nicht geben, denn die Zellen einer Listbox sind keine statischen Objekte. Sie werden gemalt, wenn die Listbox erkennt, dass sie sichtbar sind, und legen sich wieder als reine Daten zur Ruhe, wenn der Benutzer sie außer Sicht scrollt.

Es passiert also folgendes:

  • Die Listbox scrollt eine Zeile in den sichtbaren Bereich.
  • Die CellBackgroundPaint-Events der beteiligten Zellen feuern. Gibt es keine individuellen Event-Handler, die ein „True“ zurückliefern, wird anschließend der Hintergrund der Standardzelle gemalt.
  • Nach dem Hintergrund wird der Text via CellTextPaint-Event auf die gleiche Weise gefeuert und bei keinem erhaltenen „True“ die Standard-Textausgabe angeworfen.

Diese Standard-Ausgaben sind jedesmal nichts weiter als Graphics.Draw-Befehle. Entscheidet man sich also, eigene Paint-Handler zu installieren, die dann True zurückliefern, ersetzt man einfach diesen Code durch anderen. Wenn hier nicht viel berechnet wird, ist das also nicht oder nur marginal langsamer als der Standard, kann aber zur Verschönerung enorm beitragen – z.B. durch zweizeilige Zellen, Grafiken in diesen – die Beispiele in den IDE-Examples zeigen ja so einiges.

Wenn man aber doch gerne so etwas wie FormattedText hatte, um den Text möglichst einfach mit Auszeichnungen wie Fettschrift, Schriftgröße, Font oder Farbe auszustatten? So wie hier – oder gerne auch in schön:

Dann mag ich dafür die FormattedTextListbox empfehlen. An den Feature-Reichtum von FormattedText kommt sie zwar nicht ganz ran, aber für die meisten Fälle dürfte es reichen.

Die FormattedTextListbox

ist eine eigene Unterklasse der Listbox, die einen vordefinierten CellTextPaint-Eventhandler besitzt, der einen CellTag analysiert, der mit ein paar Methoden beim AddRow definiert werden kann. Dieser CellTag muss ein Array of Xojo.Core.Dictionary sein, und jedes einzelne dieser Dictionaries folgt den Konventionen:

  • mit den Keys „Start“ und „End“ werden Anfangs- und Endzeichen im Ausgabetext definiert, für die die dann geltenden Werte als ParamArray von Auto folgen. Start und End erwarten Integers basierend auf 0 – ein Gültigkeitsgebiet für das erste Zeichen wäre also (0,0). Ausnahme ist End = -1: Bis zum Ende des Texts.
  • Es können beliebig viele Key/Value-Paare für die Auszeichnung folgen. Die Keys sind definiert als
    • Bold: Value As Boolean
    • Color: Value as Color
    • Font: Value As Text/String
    • FontSize: Value As Single
    • Italic: Value As Boolean
    • Offset: Value As integer = vertikaler Zeichenversatz in y-Richtung.
    • Spacing: Value as Integer. Wird aktuell von Xojo offensichtlich ignoriert.
    • Underline: Value As Boolean
  • Die Dictionaries müssen die Zeichengebiete in aufsteigender Position aufführen.
  • Gebiete dürfen sich nicht überlappen.

Um die Erstellung einfacher zu machen, gibt es eine gesharte Methode

Public Shared Function MakeCellDictEntry(StartPos as Integer, Endpos as Integer, paramarray Values() as auto) as Xojo.Core.Dictionary
 Dim CellDict As New Xojo.Core.Dictionary
 CellDict.Value("Start")= StartPos
 CellDict.value("End") = Endpos
 For q As Integer = 0 To values.Ubound Step 2
  Dim type As Text = values(q)
  Select Case Type
  Case "Color", "FontSize", "Font", "Bold", "Italic", "Spacing", "Underline", "Offset"
   CellDict.Value(Type) = Values(q+1)
  Else
   MsgBox "Unknown Type: "+type
   break
  End Select
 Next
 Return CellDict
End Function

Um das noch bequemer zu machen, gibt es eine Methode

Public Sub AddCellTag(Column as integer, Paramarray Dicts() as Xojo.Core.Dictionary)
 Dim arr() As Xojo.Core.Dictionary
 For q As Integer = 0 To dicts.Ubound
  arr.Append dicts(q)
 Next
 Me.CellTag(Me.LastIndex, column) = arr
End Sub

die das Anhängen eines Tags an die letzte Zeile der Listbox in einer definierten Spalte vereinfacht.

Nach einem AddRow also einfach

Me.AddCellTag (0, Me.makeCellDictEntry (5, 8, "Color", Color.Red))

(wahlweise um beliebige weitere mit makeCellDictEntry erzeugte Dictionaries mit Komma getrennt zu erweitern), und das sechste bis neunte Zeichen der Spalte 0 werden in Rot erscheinen.

Und im CellTextPaint-Event werden diese Tags dann analysiert. Es werden jeweils Bereiche des Textes ausgeschnippelt, das Graphics-Objekt wird entsprechend eingestellt, der Text per DrawString gezeichnet, und zum Schluss wird das Graphics-Objekt auf die Originalwerte zurückgestellt. Das habe ich im Code auf Englisch und Deutsch dokumentiert, deshalb hier nur die grobe Übersicht:

  • Zunächst feuert die Listbox eine Kopie des CellTextPaint-Events. Hier könnte man individuelle Anpassungen des Graphics-Objekts vornehmen und mit False beenden, um die Textdarstellung-Methode ausführen zu lassen. Oder man überschreibt wie üblich die ganze Textausgabe (z.B. für zweizeiligen Text, der aber durchaus auf den Dictionaries aufbauen kann) und gibt ein True zurück.
  • Dann werden die unterstützten Properties des Graphics-Objekts gespeichert, um sie am Ende wieder zurücksetzen zu können.
  • In der Schleife werden die Dictionaries der Reihe nach ausgewertet und Variablen zugewiesen.
  • Gibt es Text vor der Startposition des aktuellen Auszeichnungsbereichs, wird dieser mittels DrawString ausgegeben.
  • Mit der Methode Graphics.StringWidth(aString) wird die Point-Breite bestimmt, die diese Textausgabe in Anspruch nahm. Wir müssen ja wissen, wo der nächste Teilstring ausgegeben werden soll, um nahtlos anzusetzen. Die lokale Property TextWidth kümmert sich darum.
  • Dann werden die Graphics-Parameter auf die ausgelesenen Werte gesetzt und der mit ihnen auszuzeichnende Textbereich wird ausgegeben.
  • Nach Rücksetzen der Properties von g wird der Gesamttext um den bereits gemalten Text gekürzt.
  • Die Property Offset wird um die ausgegebenen Zeichen vergrößert, damit Start- und Endwert des nächsten Bereichs auch die wirklich gemeinten Zeichen adressieren – der Text wurde ja eingekürzt.
  • Das nächste Dictionary wird abgearbeitet, bzw. sollte am Ende noch Text übrig sein, wird dieser wieder mit den Standardeinstellungen ausgegeben.

Man sollte allerdings nicht allzu sehr aus dem Vollen schöpfen: Auch wenn das Graphics-Objekt flink ist: Die Ausführung komplexer Dictionaries braucht ihre Zeit. Das kann sich bei sehr bunten Zeilen durch eine kleine Verzögerung bemerkbar machen. Weniger ist mehr, wie so oft.

Während eines DebugBuilds ist eine (wegen der Limitation von Xojo.Core.Date unter Windows allerdings dort nicht hilfreiche) Zeitmessung aktiv, die in den Debuglog ausgegeben wird. Richtig, richtig bunte Zeilen können schon das Hundertfache einer einfachen schwarzen Wald-und-Wiesenzeilenmalzeit in Anspruch nehmen. Gutes Design sieht ja meistens auch anders aus …

Wer mutig ist, kann durch Auskommentierung dreier Zeilen im CellTextPaint-Eventhandler der Klasse noch ein bisschen mehr Geschwindigkeit für Builds herauskitzeln. Wunder sollte man sich damit aber nicht versprechen. Und sollte das Programm sehr sang- und klanglos abstürzen, wäre der Fehler bevorzugt hier zu suchen, da die Pragmas diverse Fehlerüberprüfungsroutinen ausschalten.

Das Projekt finden Sie hier. Sie können es gerne kostenfrei nutzen; über einen Beitrag bei Gefallen freue ich mich wie üblich nichtsdestotrotz.

 

 

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

w

Verbinde mit %s