ABOUT Visual Basic Programmieren Programmierung Download Downloads Tips & Tricks Tipps & Tricks Know-How Praxis VB VBA Visual Basic for Applications VBS VBScript Scripting Windows ActiveX COM OLE API ComputerPC Microsoft Office Microsoft Office 97 Office 2000 Access Word Winword Excel Outlook Addins ASP Active Server Pages COMAddIns ActiveX-Controls OCX UserControl UserDocument Komponenten DLL EXE
Diese Seite wurde zuletzt aktualisiert am 18.06.2001

Diese Seite wurde zuletzt aktualisiert am 18.06.2001
Aktuell im ABOUT Visual Basic-MagazinGrundlagenwissen und TechnologienKnow How, Tipps und Tricks rund um Visual BasicAddIns für die Visual Basic-IDE und die VBA-IDEVBA-Programmierung in MS-Office und anderen AnwendungenScripting-Praxis für den Windows Scripting Host und das Scripting-ControlTools, Komponenten und Dienstleistungen des MarktesRessourcen für Programmierer (Bücher, Job-Börse)Dies&Das...

Themen und Stichwörter im ABOUT Visual Basic-Magazin
Code, Beispiele, Komponenten, Tools im Überblick, Shareware, Freeware
Ihre Service-Seite, Termine, Job-Börse
Melden Sie sich an, um in den vollen Genuss des ABOUT Visual Basic-Magazins zu kommen!
Informationen zur AVB-Web-Site, Kontakt und Impressum

Zurück...

Radio-Aktivität

Zurück...

(-hg) mailto:hg_radioclass@aboutvb.de

Die Funktion des gegenseitigen Auslösens (wie bei Tasten zur Senderwahl an einem Radio) bieten eigentlich nur der OptionButton oder selbst geschriebene Steuerelemente auf der Basis eines UserControls. Falls Ihnen der Aufwand, ein eigenes Steuerelement zu schreiben, zu hoch ist, und Sie vorhandene Steuerelemente als Optionsgruppe behandeln wollen, bleibt Ihnen zunächst nur der mühsame Weg "zu Fuß". Wenn Sie etwa ein Steuerelementfeld (Control-Array) von Labels dazu bringen wollen, immer das zuletzt angeklickte Element fett darzustellen, könnten Sie das so erreichen (bei einem Label-Control-Array von 1 - 5):

Private mLastIndex As Integer

Private Sub Label1_Click(Index As Integer)
  If mLastIndex Then
    Label1(mLastIndex).FontBold = False
  End If
  Label1(Index).FontBold = True
  mLastIndex = Index
End Sub

Schon das gleichzeitige Setzen einer zweiten Eigenschaft, etwa der Schriftfarbe (ForeColor), wird aufwändiger:

Private mLastIndex As Integer

Private Sub Label1_Click(Index As Integer)
  If mLastIndex Then
    With Label1(mLastIndex)
      .FontBold = False
      .ForeColor = vbWindowText
    End With
  End If
  With Label1(Index)
    .FontBold = True
    .ForeColor = vbBlue
  End With
  mLastIndex = Index
End Sub

Je öfter Sie solche Quasi-Optionsgruppen verwenden, um so häufiger schreiben Sie den im Prinzip immer wieder gleichen Code. Und standardisieren lässt er sich anscheinend auch nicht, da es ja viele verschiedene Steuerelemente gibt - und ein jedes hat wieder andere Eigenschaften und Datentypen. Trotzdem bietet es sich an, für derartige Optionsgruppen eine wiederverwendbare Klasse anzulegen.

Aus beliebigen Steuerelemente oder Objekten lassen sich mit der Klasse clsRadio Optionsgruppen bilden

Aus beliebigen Steuerelemente oder Objekten lassen sich mit der Klasse clsRadio Optionsgruppen bilden

Sie werden sich fragen, wie das in Anbetracht der Verschiedenartigkeit der Steuerelemente möglich sein soll? Nun, seit Visual Basic 6 können Sie Methoden und Eigenschaften von Steuerelementen (und überhaupt von beliebigen Objekten) über Ihren Namen aufrufen - über die Funktion CallByName. Dieser übergeben Sie das betreffende Objekt, den Namen der Methode bzw. Eigenschaft, die Art des Aufrufs (Methode oder Let/Set/Get einer Eigenschaft) und gegebenenfalls Parameter bzw. den zu setzenden Wert. Zum Beispiel:

CallByName Label1, "ForeColor", vbLet, vbBlue

Eine allgemeine Optionsgruppen-Klasse (nennen wir sie "clsRadio") müsste also nur wissen, welcher Eigenschaft welche Werte jeweils für den gewählten und den ungewählten Zustand zuzuweisen wären. Und sie müsste natürlich auch die Objekte der Optionsgruppe enthalten - etwa in einer Collection.

Letzteres ist eine Standard-Angelegenheit. Sie haben doch bestimmt schon einmal eine eigene Collection-Klasse angelegt, die eine VB-Collection kapselt. Die Verwaltung der Objekte einer Optionsgruppe erfolgt in der Klasse clsRadio auf gleich Weise:

Private mItems As Object

Public Property Get Count() As Long
  Count = mItems.Count
End Property

Public Property Get Item(KeyIndex As Variant) As Object
  On Error Resume Next
  Set Item = mItems(KeyIndex)
End Property

Public Sub Add(Item As Object, Optional Key As Variant, _
 Optional Before As Variant, Optional After As Variant)

  mItems.Add Item, Key, Before, After
End Sub

Public Sub Remove(KeyIndex As Variant)
  On Error Resume Next
  mItems.Remove KeyIndex
End Sub

Public Function NewEnum() As IUnknown
  Set NewEnum = mItems.[_NewEnum]
End Function

Private Sub Class_Initialize()
  Set mItems = New Collection
End Sub

Private Sub Class_Terminate()
  Set mItems = Nothing
End Sub

Sie fragen sich vielleicht, warum die interne Variable mItems zur Aufnahme der Collection nur als "As Object" und nicht direkt als "As Collection" deklariert ist? Nun, so können Sie auch bereits vorhandene Collections anderer Herkunft (etwa die ListItems-Collection eines ListView-Steuerelements) direkt aufnehmen:

Public Sub AddCollection(Items As Object)
  Set mItems = Items
End Sub

Und damit Sie die einzelnen Elemente eines Control-Arrays nicht auch einzeln einfügen müssen, erledigt die folgende Methode die Übernahme eines Control-Arrays:

Public Sub AddControlArray(CtlArray As Object)
  Dim nControl As Control
  
  Set mItems = New Collection
  With mItems
    For Each nControl In CtlArray
      .Add nControl
    Next
  End With
End Sub

Die Auswahl eines Elements der Optionsgruppe erfolgt über die Methode SelectItem. Praktischerweise können Sie ihr sowohl den Index, den Schlüssel und sogar ein Objekt der Optionsgruppe übergeben.

Public Function SelectItem(KeyIndexItem As Variant) As Object
  Dim nSelectItem As Object
  Dim l As Long
  Dim nUnselectItem As Object
  Dim nOldSelectedKeyIndex As Variant
  
  nOldSelectedKeyIndex = pSelectedKeyIndex
  If IsObject(KeyIndexItem) Then
    For l = 1 To mItems.Count
      If mItems(l) Is KeyIndexItem Then
        pSelectedKeyIndex = l
        Set nSelectItem = mItems(l)
        Exit For
      End If
    Next
  Else
    pSelectedKeyIndex = KeyIndexItem
    On Error Resume Next
    Set nSelectItem = mItems(pSelectedKeyIndex)
  End If
  If Not (nSelectItem Is Nothing) Then
    On Error Resume Next
    Set nUnselectItem = mItems(nOldSelectedKeyIndex)
    zSelect nUnselectItem, False
    zSelect nSelectItem, True
    Set SelectItem = nSelectItem
    zChanged
  End If
End Function

Wird tatsächlich ein Objekt übergeben, wird versucht, dessen Index innerhalb der Collection ausfindig zu machen. Wurde ein Index oder ein Schlüssel übergeben, wird versucht, der lokalen Variablen nSelectItem das entsprechende Objekt aus der Collection zuzuweisen. Schlägt der eine oder der andere Versuch fehl, geschieht nichts weiter (der Einfachheit halber ohne Auslösen eines Laufzeitfehlers).

Wurde ein Objekt gefunden, wird zunächst noch versucht, das zuvor gewählte Objekt zu ermitteln - anhand des Eingangs der Methode in nOldSelectedKeyIndex vorab gesicherten alten Indexes bzw. Schlüssels. Unabhängig davon, ob dieses ermittelt werden konnte, wird der Inhalt der Variablen nUnselectItem mit dem Vermerk der Abwahl ("False") an die private Prozedur zSelect übergeben (zum Code dieser Prozedur kommen wir später noch). Direkt danach wird zSelect mit dem neu zu wählenden Objekt und True als Vermerk aufgerufen. Als kleiner Service gibt die Methode auch noch das neu gewählte Objekt zurück. Und schließlich wird die private Prozedur zChanged aufgerufen (auch hierzu später mehr).

Sie werden sicher noch die Deklaration der Variablen pSelectedKeyIndex vermissen. Sie ist die private klassenweit gültige Variable zur Aufnahme des Indexes bzw. Schlüssels des aktuell gewählten Objekts:

Private pSelectedKeyIndex As Variant

Public Property Get SelectedKeyIndex() As Variant
  SelectedKeyIndex = pSelectedKeyIndex
End Property

Sie sehen, dass nur dieser indirekte Verweis auf das gewählte Objekt gespeichert wird. Das reicht vollkommen aus, um auch das gewählte Objekt jederzeit liefern zu können:

Public Property Get SelectedItem() As Object
  On Error Resume Next
  Set SelectedItem = mItems(pSelectedKeyIndex)
End Property

Da wir gerade noch bei den Funktionen zur Auswahl sind, schieben wir noch schnell die Methode InitItems ein, über die Sie alle eingefügten Elemente auf einen einheitlichen, nicht gewählten Stand bringen können:

Public Sub InitItems()
  Dim nItem As Object
  
  For Each nItem In mItems
    zSelect nItem, False
  Next
  pSelectedKeyIndex = Empty
End Sub

Sinnvollerweise bietet es sich an, die oben bereits gezeigten Methoden zur Übergabe einer vorhandenen Collection und eines Controls-Arrays mit einem optionalen Parameter zu versehen, der angibt, ob InitItems auch gleich nach der Übernahme der Objekte ausgeführt werden soll:

Public Sub AddCollection(Items As Object, _
 Optional ByVal InitItems As Boolean)

  Set mItems = Items
  If InitItems Then
    Me.InitItems
  End If
End Sub

Public Sub AddControlArray(CtlArray As Object, _
 Optional ByVal InitItems As Boolean)

  Dim nControl As Control
  
  Set mItems = New Collection
  With mItems
    For Each nControl In CtlArray
      .Add nControl
    Next
  End With
  If InitItems Then
    Me.InitItems
  End If
End Sub

Nähern wir uns nun etwas mehr der eigentlichen Aufgabe der Verwaltung der Eigenschaft(en), die zur Darstellung der Auswahl gesetzt werden soll. Allzuviel verrate ich Ihnen noch nicht - nur erst einmal soviel, dass die Informationen in einem Array oder einer Collection verwaltet werden und einer Eigenschaft der Klasse übergeben werden:

Private pProperties As Variant

Public Property Get Properties() As Variant
  Properties = pProperties
End Property

Public Property Let Properties(New_Properties As Variant)
  If IsObject(New_Properties) Then
    Set pProperties = New_Properties
  Else
    pProperties = New_Properties
  End If
End Property

Public Property Set Properties(New_Properties As Variant)
  Set pProperties = New_Properties
End Property

Ich habe mit verschiedenen Möglichkeiten experimentiert, ehe ich mich zu der Form der Organisation der benötigten Informationen entschlossen habe, wie Sie sie nun vorfinden werden. Mit erschien es schließlich am einfachsten, für jede zu setzende Eigenschaft des gewählten bzw. abgewählten Objekts ein Array zu definieren. Dieses Array enthält als erstes Element den Namen der Eigenschaft, als zweites Element den Wert für den nicht gewählten Zustand, und als drittes Element den Wert für den gewählten Zustand. Diese Arrays je Eigenschaft werden in einer Collection (oder einem weiteren Array) zusammengefasst, die (bzw. das) an die oben stehende Properties-Eigenschaft übergeben wird. Das Zusammenstellen der Informationen ist auf diese Weise einfach handzuhaben. Hier sehen Sie nun schon einmal ein Beispiel, wie eine Instanz der Klasse clsRadio für eine Optionsgruppe aus drei Labels initialisiert werden könnte:

Private mOptionGroup As clsRadio
' ...
Set mOptionGroup = New clsRadio
With mOptionGroup
  Set nProperties = New Collection
  With nProperties
    .Add Array("FontBold", False, True)
    .Add Array("ForeColor", vbWindowText, vbBlue)
  End With
  Set .Properties = nProperties
  .Add Label1
  .Add Label2
  .Add Label3
End With

Nun bin ich endlich soweit, dass ich Ihnen die Prozedur zSelect zeigen kann, in der die Darstellung der Auswahl bzw. der Nicht-Auswahl erfolgt.

Private Sub zSelect(Item As Object, ByVal Selected As Boolean)
  Dim nProperty As Variant
  Dim i As Integer
  Dim nPropertyName As Variant
  Dim nValue As Variant
  
  Const kPropertyName = 0
  Const kUnselectedValue = 1
  Const kSelectedValue = 2
  
  If Item Is Nothing Then
    Exit Sub
  End If
  For Each nProperty In pProperties
    nPropertyName = Trim$(nProperty(kPropertyName))
    Select Case Selected
      Case False
        If IsObject(nProperty(kUnselectedValue)) Then
          Set nValue = nProperty(kUnselectedValue)
        Else
          nValue = nProperty(kUnselectedValue)
        End If
      Case True
        If IsObject(nProperty(kSelectedValue)) Then
          Set nValue = nProperty(kSelectedValue)
        Else
          nValue = nProperty(kSelectedValue)
        End If
    End Select
    On Error Resume Next
    If Len(nPropertyName) Then
      If IsObject(nValue) Then
        CallByName Item, nPropertyName, VbSet, nValue
      Else
        CallByName Item, nPropertyName, VbLet, nValue
      End If
    Else
      If IsObject(nValue) Then
        Set Item = nValue
      Else
        Item = nValue
      End If
    End If
  Next 'i
End Sub

Eigentlich passiert hier nichts Besonderes. Die Liste der für jede zu setzende Eigenschaft vorhandenen Arrays wird durchlaufen (per For...Each können sowohl Collections als auch Arrays bearbeitet werden), der Name der Eigenschaft wird aus dem gerade bearbeiteten Array ausgelesen, und je nach Wert des Selected-Parameters der Wert für den gewählten oder den nicht gewählten Zustand. Dann wird CallByName mit dem im Item-Parameter übergebenen Objekt, dem Namen der Eigenschaft und mit dem zu setzenden Wert als Parameter aufgerufen. Die Art des Aufrufs (VbLet oder VbSet) ergibt sich aus dem Datentyp des zu setzenden Wertes - Sie können hier einer Eigenschaft auch Objekte zuweisen - etwa Picture- oder Font-Objekte.

In der ebenfalls erwähnten privaten Prozedur zChange passiert auch nicht allzuviel - hier wird lediglich ein Ereignis ausgelöst, das Sie beispielsweise ein einem Form über die Änderung der Auswahl informiert:

Public Event Changed()

Private Sub zChanged()
  Dim nSyncObject As Object
  
  RaiseEvent Changed
  If TypeOf pSyncObject Is clsRadio Then
    pSyncObject.SelectItem pSelectedKeyIndex
  Else
    For Each nSyncObject In pSyncObject
      If TypeOf nSyncObject Is clsRadio Then
        nSyncObject.SelectItem pSelectedKeyIndex
      End If
    Next
  End If
End Sub

Ach so, ja, dieses SyncObject kennen Sie ja noch gar nicht. Nun, da wir ansonsten die eigentliche Funktionalität der Klasse hinter uns gebracht haben, rüsten wir sie noch mit ein paar komfortablen Features auf - einfach aus Spaß an der Freude...

Die Eigenschaft SyncObject der Klasse clsRadio kann entweder einen Verweis auf eine einzelne weitere Instanz von clsRadio enthalten, oder eine Collection (oder auch hier ein Array) mehrerer Instanzen. Damit können Sie auf einfache Weise beliebig viele Instanzen synchronisieren - etwa Kombinationen aus Labels und Image-Steuerelementen: Eine clsRadio-Instanz nimmt die Labels auf, eine andere die Image-Steuerelemente.

Private pSyncObject As Variant

Public Property Get SyncObject() As Variant
  Set SyncObject = pSyncObject
End Property

Public Property Let SyncObject(New_SyncObject As Variant)
  If IsObject(New_SyncObject) Then
    Set pSyncObject = New_SyncObject
  Else
    pSyncObject = New_SyncObject
  End If
End Property

Public Property Set SyncObject(New_SyncObject As Variant)
  Set pSyncObject = New_SyncObject
End Property

Falls Sie per Code oder vielleicht von einem Timer gesteuert die Auswahl in der Optionsgruppe von einem Objekt zum nächsten verschieben möchten, werden Sie die Methoden FirstItem, NextItem, PrevItem und LastItem sicher recht nützlich finden. Die Eigenschaft Cycle gibt dazu an, ob bei Erreichen des letzten bzw. ersten Objekts die Iteration umlaufen soll.

Private pCycle As Boolean

Public Property Get Cycle() As Boolean
  Cycle = pCycle
End Property

Public Property Let Cycle(New_Cycle As Boolean)
  pCycle = New_Cycle
End Property

Public Sub FirstItem()
  Dim nOldSelectedItem As Object
  Dim nSelectItem As Object
  
  If mItems.Count Then
    On Error Resume Next
    Set nOldSelectedItem = mItems(pSelectedKeyIndex)
    pSelectedKeyIndex = 1
    Set nSelectItem = mItems(pSelectedKeyIndex)
    If Not (nOldSelectedItem Is nSelectItem) Then
      zSelect nOldSelectedItem, False
      zSelect nSelectItem, True
      zChanged
    End If
  End If
End Sub

Public Sub LastItem()
  Dim nOldSelectedItem As Object
  Dim nSelectItem As Object
  
  With mItems
    If .Count Then
      On Error Resume Next
      Set nOldSelectedItem = mItems(pSelectedKeyIndex)
      pSelectedKeyIndex = .Count
      Set nSelectItem = mItems(pSelectedKeyIndex)
      If Not (nOldSelectedItem Is nSelectItem) Then
        zSelect nOldSelectedItem, False
        zSelect nSelectItem, True
        zChanged
      End If
    End If
  End With
End Sub

Public Sub NextItem()
  Dim nOldSelectedItem As Object
  Dim nSelectItem As Object
  
  On Error Resume Next
  Set nOldSelectedItem = mItems(pSelectedKeyIndex)
  If IsNumeric(pSelectedKeyIndex) Then
    pSelectedKeyIndex = pSelectedKeyIndex + 1
    With mItems
      If .Count Then
        Select Case pCycle
          Case False
            If pSelectedKeyIndex > .Count Then
              pSelectedKeyIndex = .Count
            End If
          Case True
            If pSelectedKeyIndex > .Count Then
              pSelectedKeyIndex = 1
            End If
        End Select
      Else
        Exit Sub
      End If
    End With
  Else
    pSelectedKeyIndex = 1
  End If
  Set nSelectItem = mItems(pSelectedKeyIndex)
  If Not (nOldSelectedItem Is nSelectItem) Then
    zSelect nOldSelectedItem, False
    zSelect nSelectItem, True
    zChanged
  End If
End Sub

Public Sub PrevItem()
  Dim nOldSelectedItem As Object
  Dim nSelectItem As Object
  
  Set nOldSelectedItem = mItems(pSelectedKeyIndex)
  If IsNumeric(pSelectedKeyIndex) Then
    pSelectedKeyIndex = pSelectedKeyIndex - 1
    With mItems
      If .Count Then
        Select Case pCycle
          Case False
            If pSelectedKeyIndex < 1 Then
              pSelectedKeyIndex = 1
            End If
          Case True
            If pSelectedKeyIndex < 1 Then
              pSelectedKeyIndex = .Count
            End If
        End Select
      Else
        Exit Sub
      End If
    End With
  Else
    pSelectedKeyIndex = 1
  End If
  Set nSelectItem = mItems(pSelectedKeyIndex)
  If Not (nOldSelectedItem Is nSelectItem) Then
    zSelect nOldSelectedItem, False
    zSelect nSelectItem, True
    zChanged
  End If
End Sub

Diese Iterations-Möglichkeiten sind übrigens auch ein Grund dafür, dass lediglich der Index bzw. der Schlüssel des gewählten Objekts gespeichert wird, und nicht ein Verweis auf das Objekt.

Ach ja, ehe ich es vergesse: Sie können auch darauf verzichten, die Properties-Eigenschaft mit Informationen zu füllen und damit die Darstellung der Objekte einer Optionsgruppe zu ändern. Die Funktionalität, dass immer nur ein Element der Gruppe gewählt ist, haben Sie trotzdem zur Verfügung. Und Sie können die Optionsgruppe auch mit beliebigen anderen Objekten als Steuerelementen füttern - und über den Properties-Mechanismus diese Objekte auf die Auswahl reagieren lassen.


Beispiel-Projekt und Klasse clsRadio (radioclass.zip - ca. 7,2 KB)



Komponenten-Übersicht

Schnellsuche




Zum Seitenanfang

Copyright © 1999 - 2023 Harald M. Genauck, ip-pro gmbh  /  Impressum

Zum Seitenanfang

Zurück...

Zurück...