Die Community zu .NET und Classic VB.
Menü

Eingabemasken mit vbRichClient erstellen

 von 

Danksagung 

Der vbRichClient ist aus Sicht eines VB-Entwicklers ein phantastisches Produkt. Weil ich glaube, dass damit VB eine Zukunftsperspektive bekommt, habe ich mich entschlossen diese Kolumne zu starten, um so das Framework im deutschsprachigen Raum bekannter zu machen.

Das Framework unterliegt ständigen Verbesserungen, manchmal werden öfter am Tag neue Releases auf GitHub publiziert. Deshalb ist meine Arbeit an dieser Kolumne mehr als nur Schreibarbeit. Ich muss oft in die Interna des Frameworks abtauchen und komme manchmal nicht weiter. Dank Olaf Schmidt, dem Autor der Frameworks, der mir in solchen Situationen beratend und nicht selten mit fertigem Code weiterhilft, geht es dann wieder weiter. An dieser Stelle, ein dickes Dankeschön an dich Olaf, mach weiter wie bisher!

Auch möchte ich den Jungs auf ActiveVB dafür danken, dass sie mir ihre Plattform für die Publikation dieser Kolumne anbieten. Ich hoffe, dass meine Artikel für die Besucher der Seite einigermaßen leserlich und interessant sind.

Widgets  

Mit den visuellen Programmierumgebungen sind Steuerelemente populär geworden. Für VB-Entwickler sind Steuerelemente, in VB auch Komponenten genannt, der Werkstoff schlechthin für die Softwareentwicklung. Umso befremdlicher mag es klingen, dass vbRichClient-Fenster keine VB-Steuerelemente mehr unterstützen. In der Klassenbibliothek vom vbRichClient findet sich in der Tat kein einziges Steuerelement, lediglich eine Klasse cWidgetBase deutet auf ein Interaktionselement für das GUI hin. Wikipedia meint, dass die Artikel Widget und Steuerelement sich thematisch überschneiden. Widgets sind also auch Steuerelemente. Warum aber jetzt neumodisch Widgets und nicht in alter bewährter Form Steuerelemente? Nun, ich kann an dieser Stelle nur spekulieren. Der vbRichClient-Autor Olaf Schmidt wollte vielleicht damit verdeutlichen, dass seine Widgets eben keine VB-Steuerelemente sind, so wie wir sie kennen.

Wer von Ihnen die beiden ersten Kolumnen zum vbRichClient gelesen hat, dürfte diese Aussage nicht überraschen. Wir haben hier bereits Cairo-Fenster als nicht-VB-Fenster kennengelernt. Die beeindruckenden neuen grafischen und funktionalen Möglichkeiten der Fenster finden sich auch in den Steuerelementen, pardon, Widgets wieder. Bevor ich mich diesen widme, will ich erst mal die Abgrenzung zu VB-Steuerelementen hervorheben.

Widgets sind keine VB-Steuerelemente und auch keine VB-Benutzersteuerelemente (UserControls). Sie können Widgets nicht direkt auf VB-Fenster positionieren. Wenn Sie das machen wollen, dann können Sie jedoch ein (generisches) VB-Benutzersteuerelement (*.ctl) als Wrapper für das Widget verwenden. Für den Übergang vom klassischen VB zum RichClient-VB kann das von Vorteil sein. Wenn Sie in Ihrer Anwendung auf alte VB-Steuerelemente angewiesen sind, weil es dafür noch keine alternativen Widgets gibt, können Sie so VB-Steuerelemente und Widgets auf einem VB-Fenster kombinieren.

Widgets sind auch keine OCX-Controls. Ein Widget ist in einer normalen VB-Klasse gekapselt, die Sie sehr wohl in eine Active-X-Dll auslagern können, das ist dann aber keine OCX-Bibliothek (OLE custom controls).

Widgets können Sie nicht in der VB-Toolleiste ablegen und auch nicht wie gewohnt mit der Maus auf der Oberfläche eines Fensters aufziehen. Widgets werden aus Widget-Klassen im Code instanziiert und in der Regel per .Add-Methode einem Fenster zugefügt. Über die Methodenparameter x, y, dx und dy bestimmen Sie deren Position und Außenabmessungen.

Was sind nun aber Widgets? Wenn Sie ein neues Widget erstellen, also selbst programmieren wollen, dann fügen Sie ihrem VB-Projekt ein neues Klassenmodul hinzu und befüllen das mit Ihrem Widget-Code. Das werden Sie in Zukunft vielleicht öfter machen. Und zwar deshalb, weil die Widget-Programmierung schöner, komfortabler und mächtiger ist als das was Sie in der UC-Programmierung von VB kennen. Vielleicht irre ich mich aber auch: Vielleicht werden Sie keine Widgets programmieren, weil Sie demnächst viele aus dem Internet frei herunterladen können. Olaf Schmidt hat seit einiger Zeit begonnen Widgets zu programmieren und diese inkl. Quellcode auf GitHub bereitzustellen. Wer das englische Forum vbForums.com ab und zu besucht, wird das große Interesse an dieser Technologie nicht übersehen. Neben Code und Screenshots werden hier ganze Beispielprojekte im Quellcode angeboten. Und ich verspreche nicht zu viel wenn ich behaupte, dass manch VB-Veteran bei diesem Anblick leuchtende Augen bekommt. Wollen wir hoffen, dass diese Idee auch hier in Deutschland bald mehr Aufmerksamkeit erhält.

Widgets verwenden  

Schauen wir uns den Code eines einfachen Widgets (cwWidget.cls) mal an:

Option Explicit
Public WithEvents Widget As cWidgetBase
Public Event Click()

Private Sub Class_Initialize()
  Set Widget = Cairo.WidgetBase
End Sub

Public Property Get Widgets() As cWidgets
  Set Widgets = Widget.Widgets
End Property

Private Sub Widget_Paint(CC As cCairoContext, _
ByVal xAbs As Single, ByVal yAbs As Single, _
ByVal dx_Aligned As Single, ByVal dy_Aligned As Single, _
UserObj As Object)

CC.DrawText 10, 10, dx_Aligned, dy_Aligned, "Klick mich!"
End Sub

Private Sub Widget_Click()
  RaiseEvent Click
End Sub

So, nun brauchen wir noch ein Fenster auf dem wir dieses Widget einsetzen können. Basierend auf den Fensterbeispielen der letzten Artikel, erstellen wir uns die minimalistische Fenster-Klasse cfMain.cls wie folgt:

Option Explicit
Private WithEvents mForm As cWidgetForm
Private WithEvents myWidget As cwWidget

...

Private Sub Form_Load()
  Set myWidget = mForm.Widgets.Add(New cwWidget, _
                 "meinWidget", 10, 10, 80, 30
End Sub

Private Sub myWidget_Click()
    MsgBox "Hallo Widget!"
End Sub

Im Form-Load-Ereignis wird eine Instanz unseres Widgets erzeugt. Die Parameter x, y, dx und dy positionieren das Widget auf der Form. Da wir das Widget mit dem Schlüsselwort WithEvents deklariert haben, können wir im Click-Ereignis eine MessageBox anzeigen lassen. An diesem Code ist nichts Aufregendes. Wer in VB schon die Methode Controls.Add verwendet hat, kennt dieses Prinzip der Steuerelementinstanziierung.

Interessanter ist der Code des Widgets. Wie Sie sehen, beinhaltet das Widget eine Instanz der Klasse cWidgetBase. Diese Klasse ist Teil des vbRichClients, wir sind ihr am Anfang des Artikels schon mal begegnet. Wenn ich nun schreibe, dass jedes Widget von dieser Klasse "erben" muss, werden mich die Puristen der Objektorientierung schelten, weil das keine "echte" Vererbung ist. Nennen Sie es wie Sie wollen, Fakt ist: Jedes Widget muss ein Objekt der Klasse cWidgetBase implementieren.

Des Weiteren braucht ein Widget eine Public-Property Widgets vom Typ cWidgets (cWidgets ist auch ein Klassentyp aus dem vbRichClient der es ermöglicht, jedwedes Widget auch als Container für weitere "Kind-Widgets" zu verwenden - per .Widgets.Add). Hier hören die Pflicht-Vorgaben schon auf, der restliche Code ist optional. Am häufigsten werden Sie jedoch das Widget_Paint-Ereignis behandeln. Dieses Ereignis gibt Ihnen die Möglichkeit Code für das Zeichnen des Widgets zu implementieren. Dafür bietet die Methode mit CC einen Zeichnen-Kontext im Parameter cCairoContext. Auf diesem Kontext können Sie die Cairo-Grafik-Methoden anwenden und Texte ausgeben.

So viel zum internen Aufbau eines Widgets. Wir wollen in diesem Artikel keine weiteren Widgets entwerfen, sondern zunächst mal vorhandene Widgets auf unsere Fenster bringen. Wer mehr über das Thema Widget-Entwicklung erfahren möchte, kann sich die Tutorials von der vbRichClient.com-Seite herunterladen.

vbWidgets.dll  

Die vbWidgets.dll bietet eine Sammlung von aktuell 36 Widget-Klassen. Die DLL wird von Olaf Schmidt entwickelt und steht als SourceCode-Repository auf GitHub.com zur Verfügung. Hier finden Sie also neben der aktuellen DLL auch den kompletten Quellcode der enthaltenen Widgets. Ebenso typisch für GitHub ist bequemer Zugriff auf die neuesten Änderungen an der Code-Basis (inkl. Diff-Views usw.).

Die heruntergeladene DLL speichern Sie am besten im gleichen Ordner, in dem auch die drei vbRichClient-DLLs abgelegt sind. Die vbWidgets.dll sollte auf Ihrem Entwicklungsrechner registriert werden. Das macht die Arbeit für Sie als Entwickler komfortabler, weil Sie so in den Genuss des IDE-Intellisense kommen. Bei Ihren Anwendern gilt das gleiche Prinzip wie beim vbRichClient: Es ist keine Registrierung erforderlich. Ihre Anwendung muss lediglich den Pfad zur DLL kennen, deshalb legen Sie diese am besten im Anwendungspfad (App.Path) oder in einen Bin-Ordner unterhalb des Anwendungspfades ab.

Im vbRichClient-Ordner finden Sie auch ein kleines RegisterVBWidgetsInPlace.vbs-Skript, dass Sie bei der Registrierung der DLL unterstützt. Alternativ können Sie auch selbst das Registrierungstool regsvr32 anwenden. Sie sollten dafür Administrator-Rechte haben.

Regsvr32 C:\RC5\vbWidgets.dll

Starten Sie ein neues VB-Exe-Projekt und setzen Sie einen Verweis (Menü Projekt/Verweise) auf vbRichClient5 und vbWidgets. Vielleicht haben Sie sich bereits eine Vorlage für vbRichClient-Projekte erstellt. Das wäre nun ein guter Zeitpunkt um diese Vorlage um den neuen Verweis zu erweitern. Ebenso empfehle ich das mMain.bas Modul mit der folgenden neuen Funktion zu erweitern:

Public Function New_w(ClassName As String) As Object
  If App.LogMode = 0 Then
    Set New_w = VBA.CreateObject( _
        "vbWidgets." & ClassName)
  Else
    Set New_w = New_c.RegFree.GetInstanceEx( _
        AppPath & "vbWidgets.dll", ClassName)
  End If
End Function

Mit dieser Funktion erstellen wir uns eine eigene Factory für Widgets. Diese unterscheidet zwischen IDE-Ausführung und Exe-Kompilat und liefert die Widget-Instanzen per CreateObject oder regfree mittels RegFree-Objekt aus den vbRichClient.

Sollten Sie eine eigene Widget-DLL erstellt haben, so können Sie die enthaltenen Widgets ebenfalls hier instanziieren. Sie müssen ggf. Ihren Klassen ein eigenes Namenskürzel voranstellen, um diese von den vbWidgets zu unterscheiden.

If Left$(ClassName, 4) = "myWg" Then
    Set New_w = New_c.RegFree.GetInstanceEx( _
        AppPath & "myWidgets.dll", ClassName)
Else
    Set New_w = New_c.RegFree.GetInstanceEx( _
        AppPath & "vbWidgets.dll", ClassName)
End If

Damit haben wir alle Vorbereitungen getroffen, um mit der Entwicklung einer vbRichClient-Anwendung mit Cairo-Fenstern und Cairo-Widgets zu starten. Also los!

Eine Eingabemaske mit vbRichClient und vbWidgets erstellen  

Kaum ein Programm kommt ohne Eingabemasken aus. Diese brauchen Sie um Daten zu erfassen und verwalten, Programmeinstellungen bearbeiten, Parameter für Auswertungen festlegen usw. Die typische Eingabemaske ist ein Windows-Fenster auf dem verschiedene Steuerelemente angeordnet sind. In der Regel finden wir hier Labels, Textboxen, Listboxen oder Combo-Boxen, Kontrollkästchen und Schaltflächen. So eine typische Eingabemaske möchte ich hier auch vorstellen. Damit wir eine visuelle Vorlage haben, habe ich diese erst mal mit klassischen VB-Mitteln erstellt.


Abbildung 1: Eingabemaske mit VB

Ehrlich gesagt, ich habe für diese Maske im visuellen Designer einige Minuten gebraucht. In VB kann man zwar Steuerelemente recht gut mit dem Mauszeiger auf dem Fenster positionieren. Manche Dinge sind aber dennoch mühselig. Zum Beispiel ein Label und eine Textbox so zu positionieren, dass die Basislinien der Texte auf gleicher Ebene sind. Ein Ärgernis ist auch, dass die Höhe einer Drop-Down-Liste nicht einstellbar ist. Sie passen entweder die Höhe der Textboxen an die Höhe der Drop-Down-Liste an oder leben mit dem Unterschied. Das Positionieren der Steuerelemente unter Windows 8 ist auch nicht mehr so komfortabel wie früher. Wenn Sie z. B. mehrere Steuerelemente markieren (Strg-Taste halten), zeigt Ihnen Windows die Markier-Kästchen nicht mehr an. Das sind einfach nur kleine Ärgernisse mit denen Sie leben müssen. VB ist halt nicht mehr so frisch und jung wie früher. Schlimmer wird es allerdings, wenn Sie Unicode in Ihren Controls brauchen. Hier müssen die VB-Steuerelemente passen, Sie müssen auf Fremdkomponenten ausweichen.

Versuchen wir nun das gleiche Fenster mit dem vbRichClient zu erstellen. Eine gute Stelle um Widgets zu instanziieren ist die Form_Load-Ereignisprozedur. Wir haben bereits im letzten Beispiel gesehen, wie man ein Widget auf eine Cairo-Form bringt. Die Position und die Abmessungen des Widgets werden der Widgets.Add-Methode bei der Instanziierung übergeben. Das kann mitunter aufwendig sein, zumindest muss man davor etwas Planungsarbeit investieren um diese Werte zu ermitteln. Einfacher geht es mit einer kleinen Behelfsklasse.

Option Explicit

Dim mRows() As Single
Dim mCols() As Single

Public Sub CreateRaster(ByVal Left As Integer, Top As Integer, ByVal Rows As Integer, ByVal Cols As Integer, ByVal RowHeight As Integer, ByVal ColWidth As Integer)
Dim i As Integer
    Cols = Cols - 1
    Rows = Rows - 1
    ReDim mRows(Rows)
    ReDim mCols(Cols)
    For i = 0 To Rows
        mRows(i) = (i * RowHeight) + Top
    Next i
    
    For i = 0 To Cols
        mCols(i) = (i * ColWidth) + Left
    Next i
End Sub

Public Property Get X(Col As Integer) As Single
    X = mCols(Col - 1)
End Property

Public Property Get Y(Row As Integer) As Single
    Y = mRows(Row - 1)
End Property

Die Klasse erzeugt Arrays mit Rasterpunkten. Damit kann man sein Fenster in unsichtbare Zeilen und Spalten einteilen und später die Widgets an den Kreuzungspunkten ausrichten.


Abbildung 2: Raster-Zeilen und -Spalten

With New cFormRaster
        .CreateRaster Left:=10, Top:=10, _
                      Rows:=15, Cols:=32, _
                      RowHeight:=40, ColWidth:=10
        mForm.Widgets.Add(New_w("cwFrame"), "fraKategorie", _
            .X(1), .Y(1), 390, 50).Caption = "Kategorie"
        Set btn = mForm.Widgets.Add(New_w("cwButton"), _
            "optInteressent", .X(2), .Y(1) + 20, 100, 23)
            btn.ButtonStyle = OptionBox
            btn.Caption = "Interessent"
            btn.Value = True
        ....
        Set lbl = mForm.Widgets.Add(New_w("cwLabel"), "lblName", _
            .X(1), .Y(3), 100, 23)
            lbl.Caption = "Name:"
            lbl.BorderWidth = 0
        mForm.Widgets.Add(New_w("cwTextBox"), "txtName", _
            .X(20), .Y(3), 200, 23).MaxLength = 50
        ....
        Set btn = mForm.Widgets.Add(New_w("cwButton"), _
            "chkAktiv", .X(20), .Y(9), 200, 23)
            btn.ButtonStyle = CheckBox
            btn.Caption = "Aktiv"
        Set txt = mForm.Widgets.Add(New_w("cwTextBox"), _
            "txtBemerkungen", .X(1), .Y(10), 390, 100)
            txt.MaxLength = 512
            txt.MultiLine = True
            txt.ScrollBars = vbVertical
        Set btn = mForm.Widgets.Add(New_w("cwButton"), _
            "btnAbbrechen", .X(20), .Y(13), 90, 23)
            btn.Caption = "Abbrechen"

Wir erzeugen zuerst eine Instanz von cFormRaster. Das Schlüsselwort With sorgt dafür, dass wir ohne Objektvariable die Rasterpunkte .X() und .Y() abrufen können. Wir platzieren so alle Labels auf die Spalte .X(1), das entspricht 10 Pixel von links. Für die Textfelder habe ich die Spalte 20, also .X(20) gewählt. Sollte ich nun später der Meinung sein, dass meine Ausrichtung nicht optimal ist, reicht es die Raster-Vorgaben für die cFormRaster zu ändern.

Das hier ist lediglich ein Vorschlag. Sie können sich die cFormRaster natürlich beliebig erweitern, anders aufbauen oder auch ganz darauf verzichten und die Koordinaten direkt festlegen oder dafür Konstanten definieren.

Die meisten Widgets bieten auch bekannte und neue Events. Wenn Sie darauf reagieren wollen, müssen Sie auf Modulebene eine Objektvariable mit dem Schlüsselwort WithEvents deklarieren. Hier ein Beispiel:

Private WithEvents btnSave As cwButton

Private Sub mForm_Load()
  ...
  Set btn = mForm.Widgets.Add(New_w("cwButton"), _
      "btnSpeichern", .X(31), .Y(13), 90, 23)
      btn.Caption = "Speichern"
End Sub

Private Sub btnSave_Click()
  'tue was
End Sub

Es geht aber auch anders und das ist neu in der Cairo-Form und -Widget-Welt. Die Cairo-Form bietet uns ein sogenanntes Bubbling-Ereignis. Hier schlagen sämtliche Ereignisse aller Form-Widgets auf, unabhängig davon, wie tief diese in weiteren Container-Widgets verschachtelt sind. Das ist sehr praktisch. Einerseits kann man so Ereignisse nach den Kriterien Sender und EventName (das sind beides Parameter der Ereignisprozedur) zentralisiert verarbeiten. Das ersetzt die frühere Ereignis-Verarbeitung für Steuerelement-Felder, die es in der neuen vbRichClient-Welt so nicht mehr gibt. Andererseits kommen Sie so auch an Ereignisse von Widgets für die Sie keine Modul-Variable deklariert haben. Um z. B. das Click-Event der Abbrechen-Schaltfläche zu verarbeiten reicht folgender Code:

Private Sub mForm_BubblingEvent(Sender As Object, _
                                EventName As String, ...)
  If Sender.Widget.Key = "btnAbbrechen" And EventName "W_Click" Then
    mForm.Unload
  End If
End Sub

Damit dürfte auch klar sein wofür der Parameter Key in der Methode Widgets.Add dienlich ist. Über diesen Key identifizieren Sie in der Prozedur die Sender der Ereignisse.

Über diesen zentralisierten BubblingEvent-Mechanismus (und die Auswertung der jeweiligen Sender.Widget.Key-Strings) sind ebenfalls Szenarien abdeckbar, für die man früher VBs "Control-Arrays" eingesetzt hat.

Es gibt aber auch Neues im Bereich der Steuerelement-Eigenschaften. So gibt es z. B. in der Textbox eine neue Property "Filter". Mit dieser Eigenschaft können Sie festlegen, welche Zeichen in der Textbox erlaubt sind. Im Fall der Postleitzahl können wir folgenden Filter verwenden:

txt.Filter = cfpNumber

Dieser Wert beschränkt die Eingabe auf Zahlen. Weitere anwendbare Konstanten wären cfpBlank für das Leerzeichen, cfpDecimal und cfpThousand für das Dezimaltrennzeichen und Tausender-Trennzeichen ihres Betriebssystems, cfpLowercase für Klein- und cfpUpercase für Großbuchstaben. Möchten Sie weitere Sonderzeichen oder länderspezifische Zeichen (z. B. Umlaute) einschließen, dann verwenden Sie die Eigenschaft FilterAllowExtraChars. Diese ist vom Typ String und kann direkt mit den gewünschten Zeichen befüllt werden.

Weil die neuen Widgets mit Cairo gezeichnet werden, ergeben sich vielfältige Gestaltungsmöglichkeiten. Ich empfehle einen Blick in den VB-Objektkatalog mit gesetzten Filter auf vbWidgets. Da ist vieles dabei, was zum Ausprobieren und Experimentieren einlädt.

Bleibt zum Schluss noch das Fenster-Menü. Anders als in VB sind im RC5 Menüs auch Widgets und werden genauso behandelt wie alle anderen Steuerelemente. Da wir die Menü-Events empfangen wollen, deklarieren wir eine Modulvariable und instanziieren diese an gleicher Stelle wie die anderen Widgets:

Private WithEvents MenuBar As cwMenuBar
...
Set MenuBar = mForm.Widgets.Add( _
  New cwMenuBar, "MenuBar", 0, 0, mForm.ScaleWidth, 25)
Set MenuBar.DataSource = Cairo.CreateMenuItemFromJSON( _
  MainMenuAsJSONString)

Die Menübar ist erstellt, allerdings fehlen diesem Menü noch die Menü-Items. Um diese zu konfigurieren besitzt das Menü-Widget die Eigenschaft DataSource. Diese Eigenschaft erwartet einen JSON-String mit den Menü-Items, deren Namen, Keys usw. JSON steht für JavaScript Object Notation, und ist ein lesbares (textbasiertes) Datenformat. Genaugenommen ist JSON JavaScript-(Serialisierungs-)Code und weil er viel kompakter als XML ausfällt, wurde er zum Zweck des Datenaustauschs oder Konfiguration immer populärer. Im RC5 werden Sie öfter auf dieses Datenformat stoßen.

Widgets mit Daten befüllen  

Einige VB-Steuerelemente kann man bereits zur Entwurfszeit mit Daten befüllen. In unserer Eingabemaske wäre das die Drop-Down-Liste mit den Bundesländern. Im Eigenschaften-Fenster des Steuerelementes können wir die Eigenschaft List editieren und hier die Bundesländer einfügen.

Bei den Widgets gibt es das nicht mehr. Daten werden nur noch zur Laufzeit hinzugefügt. Das ist nicht verwunderlich wenn Sie bedenken, dass Widgets auch erst zur Laufzeit erzeugt werden. Dieser vermeintliche Nachteil wird aber durch viele Vorteile kompensiert. So haben manche Widgets eine DataSource-Eigenschaft, der Sie eine Collection oder ein Recordset übergeben können. Sie werden spätestens bei der (vergeblichen) Suche nach der ItemData-Eigenschaft diese neue Datenbindung schätzen lernen. In der Collection, die Sie als DataSource festlegen, können Sie nämlich beliebige andere Objekte oder Daten speichern. Dagegen sieht der Integer der alten ItemData wahrlich mickrig aus. Sehen wir uns das mal am Beispiel der Bundesländer an. Wir erzeugen in Form_Load eine Collection, wie folgt:

Dim bundeslaender As cCollection
  Set bundeslaender = New_c.Collection()
  With bundeslaender
    .Add "BW", "Baden-Württemberg"
    .Add "BY", "Bayern"
    ...
  End With

Weiter unten im Code instanziieren wir die DropDownList:

Set ddl = mForm.Widgets.Add(New_w("cwDropDownList"), _
    "ddlBundesland", .X(20), .Y(8), 200, 23)
ddl.SetDataSource bundeslaender, "Bundesländer"
ddl.DataSource.Sort = "Key Asc"

Das war's schon. Statt einer Collection können Sie auch ein Recordset verwenden, welches Sie vorab mit Daten aus einer Datenbank befüllen. Im Click-Ereignis der DropDownList können Sie nun wahlweise auf den Key oder den Value der Collection zurückgreifen, bei einem Recordset entsprechend auf eine bestimmte Spalte. Der RowIndex der DataSource steht immer bereits richtig, darum kümmert sich die DropDownList.

Private Sub ddl_Click()
    Debug.Print ddl.DataSource.FieldValue(0)
    Debug.Print ddl.DataSource.FieldValue(1)
End Sub

Was uns noch fehlt, ist der JSON-String mit der Menü-Definition. Diesen könnten Sie per Ressource zur Verfügung stellen. Der hierarchische Aufbau sieht wie folgt aus:

{
    "Key" : "menuBarMain",
    "Caption" : "",
    "IconKey" : "",
    "Enabled" : true,
    "Items" : {
        "mnuDatei" : {
            "Key" : "mnuDatei",
            "Caption" : "&Datei",
            ...
        },
        "mnuHilfe" : {
            "Key" : "mnuHilfe",
            "Caption" : "&Hilfe",
            "IconKey" : "",
            "Enabled" : true,
            "Items" : {
                "mnuHilfeInhalt" : {
                    "Key" : "mnuHilfeInhalt",
                    "Caption" : "&Inhalt",
                    "IconKey" : "",
                    "Enabled" : true
                ...                
}

Im RC5 finden Sie alternativ Behelfsklassen, die Sie bei dem Aufbau der JSON-Menüs unterstützen. Hier ein Beispiel:

Private Function MainMenuAsJSONString() As String
  With Cairo.CreateMenuItemRoot("menuBarMain", "")
    With .AddSubItem("mnuDatei", "&Datei")
        .AddSubItem "mnuBeenden", "&Beenden"
    End With
    With .AddSubItem("mnuAnsicht", "&Ansicht")
      With .AddSubItem("Zoom", "&Zoom")
          .AddSubItem "mnuZoom1", "100%", , , True
          .AddSubItem("mnuZoom2", "125%").IsCheckable = True
          .AddSubItem("mnuZoom3", "150%").IsCheckable = True
      End With
    End With
    With .AddSubItem("mnuHilfe", "&Hilfe")
      .AddSubItem "mnuHilfeInhalt", "&Inhalt"
      .AddSubItem "mnuHilfeIndex", "Inde&x"
      .AddSubItem "mnuHilfeSuche", "&Suche"
      .AddSubItem "mnuHilfeSep", "-"
      .AddSubItem "mnuHilfeInfo", "Inf&o..."
    End With
    MainMenuAsJSONString = .ToJSONString
  End With
End Function

Die Cairo-Methode CreateMenuItemRoot erzeugt ein cMenuItem-Objekt. Dieses wiederum kann per AddSubItem eigene Kind-Items erzeugen, die auch cMenuItem-Objekte sind. Auf diese Weise iterieren Sie durch die gewünschte Menüstruktur. Optionale Parameter Enabled und Checked sind selbsterklärend. Am Schluss erhalten Sie den gewünschten JSON-String über die Methode ToJSONString des RootMenuItems. Den String verwenden Sie als DataSource für Ihr Menü. Vollständigkeitshalber sei erwähnt, dass Sie die Menüstruktur auch nachträglich mit den Methoden Add und Remove ergänzen oder ändern können.

Sind Ihnen im Menü Ansicht die Zoom-Untermenüs aufgefallen? Was es damit auf sich hat zeigt der Ereignis-Code dieser Menüelemente:

With mForm
  If CurMenuItemPath = "mnuAnsicht>Zoom>mnuZoom2" Then
    .WidgetRoot.Zoom = 1.25
  ElseIf CurMenuItemPath = "mnuAnsicht>Zoom>mnuZoom3" Then
    .WidgetRoot.Zoom = 1.5
  Else
    .WidgetRoot.Zoom = 1
  End If
  .Width = FORM_WIDTH * .WidgetRoot.Zoom
  .Height = FORM_HEIGHT * .WidgetRoot.Zoom
  .CenterOn .WidgetRoot.CurrentMonitor
End With

Mehr braucht es in der Tat nicht, um Widgets und deren Abstand, Schriften, Rahmen, Bilder, Menüs, usw. linear zu vergrößern. Das Schreiben von DPI-unabhängigen Anwendungen, z. B. für hochauflösende Displays, ist dementsprechend einfach. Aber auch sehbehinderte Anwender werden sich über diese äußeren Gestaltungsmöglichkeiten freuen.

Popup-Menüs  

Eigentlich ist unsere Eingabemaske bereits fertig. Wenn wir auf der Basis des bisherigen Codes die Anwendung starten, sehen wir ein voll funktionsfähiges Fenster mit allen Widgets. Die Optik zeigt bereits jetzt den wesentlich moderneren Cairo-Look. Und das sogar ohne Aktivierung der XP-Styles, die Sie übrigens nicht mehr brauchen. Dennoch fehlt noch was. Spätestens wenn Sie mit der rechten Maustaste auf eine Textbox klicken, werden Sie das Kontextmenü der Textbox vermissen. Ok, dieses Kontextmenü ist nicht mehr dabei und das ist gut so. Warum? Weil wir es selbst nachrüsten werden und dabei alle Freiheiten im Aussehen und Funktionalität genießen. Stellen Sie sich vor, in einer Textbox steht eine Artikelnummer. Wäre es nicht schön neben den Standard-Menuitems noch weitere, kontextbezogene Menüeinträge anzubieten? Kein Problem, beginnen wir mit dem Standard-Menü. Das Erzeugen von erweiterten Menueitems sollte danach für Sie kein Problem mehr sein. Und Hand aufs Herz, wenn Ihnen dieser Aufwand zu groß ist, dann erstellen Sie sich ein eigenes wiederverwendbares Widget, bei dem das Standard-Kontextmenü bereits enthalten ist. Der Aufwand dafür ist nicht besonders groß.

Also legen wir los. Unseren Code ergänzen wir mit:

Private WithEvents PopUpMenu As cwMenu

Private Sub mForm_Load()
  ...
  Set PopUpMenu = New_w("cwMenu")
End Sub
Private Sub mForm_BubblingEvent(Sender As Object, _
                  EventName As String, P1 As Variant, ... )
  If TypeOf Sender Is cwTextBox Then And _
            EventName "W_MouseDown" Then
    If P1 = vbRightButton Then 
      PopUpMenu.InitAndShow Sender.Widget, _
                            CreatePopUpForTextBox(Sender), _
                            PopupBehaviourMousePosExpandRight
    End If    
  End If
End Sub

Damit haben wir eine Instanz von cwMenu in Form_Load erzeugt. Im bereits bekannten Bubbling-Event reagieren wir auf alle MouseDown-Events in Textboxen. Wenn diese auch noch mit der rechten Maustaste erfolgen rufen wir die Methode InitAndShow des Menüs auf. Da fehlt aber noch was: Der zweite Parameter der InitAndShow-Methode erwartet ein cMenuItem. Dieses Objekt erzeugen wir selbst in der Funktion CreatePopUpForTextBox. Dieser Funktion geben wir den Sender, also die auslösende Textbox als Parameter mit. Das ist erforderlich, weil wir in Abhängigkeit vom Sender bestimmte Menüs ein- oder ausblenden, aktivieren oder deaktivieren oder vielleicht erst gar nicht erzeugen. Ab hier sind wir die Herrscher der Menüs:

Public Function CreatePopUpForTextBox(TB As cwTextBox) As cMenuItem
Dim HasSelection As Boolean
Dim ClipBoardHasText As Boolean
Dim CompletelySelected As Boolean
    HasSelection = TB.SelLength
    ClipBoardHasText = Len(New_c.Clipboard.GetText)
    CompletelySelected = (TB.SelLength = Len(TB.Text))
    
    Set CreatePopUpForTextBox = Cairo.CreateMenuItemRoot( _
         "TxtBoxRoot", "TxtBoxRoot")
    
    With CreatePopUpForTextBox
        .AddSubItem "puCut", "&Ausschneiden", , HasSelection
        .AddSubItem "puCopy", "&Kopieren", , HasSelection
        .AddSubItem "puPaste", "&Einfügen", , ClipBoardHasText
        .AddSubItem "puDelete", "&Löschen", , HasSelection
        .AddSubItem "puSep1", "-"
        .AddSubItem "puSelectAll", _ 
                    "&Alles auswählen", , Not CompletelySelected
    End With
    
End Function

Zuerst werden einige Eigenschaften der Textbox ausgewertet, wie z. B. ob in der Textbox selektierter Text vorhanden ist. In Abhängigkeit davon werden bestimmte Menüs aktiviert oder auch nicht. Das passiert im vierten Parameter der AddSubItem-Methode. Der erste Parameter bekommt wieder einen Key zugewiesen, mit dem später im Click-Ereignis das angeklickte MenuItem identifiziert werden kann.

Was fällt noch positiv auf? Wir haben hier mit konstanten Menütexten gearbeitet. Das muss aber so nicht sein. Wenn Sie Ihre Anwendung mehrsprachig anbieten wollen, können Sie die Texte für die Menüs aus einer alternativen Quelle, Ressource oder Datenbank beziehen. Vielleicht ist ihnen der dritte fehlende Parameter aufgefallen. An dieser Stelle erwartet die Methode einen eindeutigen Bezeichner (Key) für ein Icon in der zentralen Cairo-ImageList. Sie erinnern sich? Letztes Tutorial? Da haben wir bereits die ImageList verwendet. Im nächsten Tutorial werde ich auf diese ImageList näher eingehen und zeigen wie einfach es ist, diverse Widgets mit Icons auszustatten. Zunächst aber möchte ich Ihnen noch den Screenshot unserer Eingabemaske vorstellen, hier ist er:


Abbildung 3: Eingabemaske mit RC5 / Cairo

Ihre Meinung  

Falls Sie Fragen zu diesem Artikel haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.