Code zu Steuerelementen in .NET

Behandeln mehrerer Ereignisse in einer Ereignisbehandlungsprozedur

Visual Basic .NET unterstützt das deklarative Behandeln von Ereignissen mehrerer Steuerelemente in einer gemeinsamen Ereignisbehandlungsprozedur. Dazu werden hinter dem Schlüsselwort Handles Ereignisse mehrerer Steuerelemente aufgelistet. Die Auswertung, welches Steuerelement ein Ereignis ausgelöst hat, kann über den Parameter sender der Ereignisbehandlungsprozedur erfolgen. Es ist zu beachten, daß auf diese Weise nur Ereignisse gleicher Signatur behandelt werden können:

Private Sub Control_Event( _
    ByVal sender As Object, _
    ByVal e As EventArgs _
) _
    Handles _
        Button1.Click, _
        ListBox1.Click, _
        CheckBox1.CheckStateChanged

    Dim s As String
    Select Case True
        Case sender Is Me.Button1
            s = "Die Schaltfläche wurde geklickt!"
        Case sender Is Me.ListBox1
            s = _
                "Der Eintrag mit dem Titel """ & _
                CStr(DirectCast(sender, ListBox).SelectedItem) & _
                """ wurde im ListBox-Steuerelement gewählt!"
        Case sender Is Me.CheckBox1
            s = "Der Status des CheckBox-Steuerelements wurde verändert!"
    End Select
    MsgBox(s)
End Sub
Behandeln mehrerer Ereignisse in einer Prozedur.

Erstellen „unsichtbarer“ Steuerelemente

Der Werkzeugkasten der Entwicklungsumgebung enthält Steuerelemente, die nicht grafisch auf einem Formular dargestellt werden, diesem aber trotzdem hinzugefügt werden können. Dazu zählt etwa die Timer-Komponente. Diese Komponenten werden in Visual Studio .NET in einem eigenen Bereich dargestellt, wenn sie auf einem Formular plaziert werden. Will man eine solche Komponente schreiben, darf nicht von UserControl abgeleitet werden; stattdessen erbt die Komponente von der Klasse Component aus dem Namensraum System.ComponentModel.

Besitzen Steuerelementeigenschaften ihre Standardwerte?

Bei der Verwendung von Steuerelementen (bzw. allgemeiner Klassen) ist es ab und zu sinnvoll, die umgestellten Eigenschaften auf ihre Standardwerte zurückzusetzen. In den Windows Forms bieten die meisten Steuerelemente dazu entsprechende Eigenschaften an. Die Eigenschaft DefaultForeColor eines Buttons gibt beispielsweise die Standardfarbe zurück, die von Buttons zur Darstellung des Vordergrundes (der darauf angezeigte Text) verwendet wird. Diese kann in der Eigenschaft ForeColor eingestellt werden. Der folgende Code prüft, ob bei einem Steuerelement die ForeColor-Eigenschaft von ihrem Standardwert abweicht:

Private Sub Button1_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs _
) Handles Button1.Click
    If _
        Not Me.Button1.ForeColor.Equals( _
            Me.Button1.DefaultForeColor _
        ) _
    Then
        MsgBox("Die Vordergrundfarbe wurde umgestellt!")
    Else
        MsgBox("Die Vordergrundfarbe wurde nicht umgestellt!")
    End If
End Sub
Weicht der Wert einer Eigenschaft von ihrem Standardwert ab?

Bei der Entwicklung eigener Steuerelemente und Klassen sollte man nach Möglichkeit entsprechende Eigenschaften bereitstellen, welche die Standardwerte der Eigenschaften zurückgeben. Dabei handelt es sich um ReadOnly-Eigenschaften, deren Name sich aus Default gefolgt vom Namen der Eigenschaft, deren Standardwert zurückgegeben wird, zusammensetzt.

Ansprechen von Steuerelementen über ihren Namen

Manchmal hat man von einem Steuerelement nur den Namen in Form einer Zeichenfolge und möchte auf das Steuerelement zugreifen. Um das Steuerelement auf einem Formular zu lokalisieren, kann die im Folgenden angegebene Funktion FindControl benutzt werden. FindControl durchsucht alle Steuerelemente (also auch Container, die weitere Steuerelemente enthalten) auf dem Formular rekursiv nach dem Steuerelement mit dem gewünschten Namen und gibt einen Verweis darauf zurück. Wird kein passendes Steuerelement gefunden, dann gibt FindControl einen Verweis auf Nothing zurück:

Private Function FindControl( _
    ByVal ControlName As String, _
    ByVal CurrentControl As Control _
) As Control
    For Each ctr As Control In CurrentControl.Controls
        If ctr.Name = ControlName Then
            Return ctr
        Else
            ctr = FindControl(ControlName, ctr)
            If Not ctr Is Nothing Then
                Return ctr
            End If
        End If
    Next ctr
End Function
Rekursive Suche nach einem Steuerelement.

Die Funktion könnte beispielsweise mit MsgBox(FindControl("Button2", Me).Name) aufgerufen werden, um das Steuerelement Button2 im aktuellen Formular zu finden. In ASP.NET-Anwendungen steht bereits eine Methode FindControl zur Verfügung. Wenn mehrfach auf mehrere Steuerelemente andhand ihres Namens zugegriffen werden muß, dann empfiehlt es sich, diese in einem Hashtable-Objekt abzulegen, wobei als Schlüssel der Einträge deren Namen benutzt werden.

Hinzufügen knotenspezifischer Kontextmenüs in einem TreeView-Steuerelement

Das TreeView-Steuerelement bietet keine direkte Unterstützung für das Anzeigen eines knotenspezifischen Kontextmenüs. Zudem besitzt dieses Steuerelement die Eigenheit, daß der Knoten unter dem Mauszeiger bei einem Rechtsklick auf das Steuerelement nicht ausgewählt wird. Dadurch kann auch kein dem gewählten Knoten angepaßtes Kontextmenü angezeigt werden. Der Kniff liegt nun darin, bei Eintreten des MouseUp-Ereignisses des Kontextmenüs den Knoten unter dem Mauszeiger zu bestimmten und auszuwählen. Zusätzlich kann noch ein Kontextmenü eingeblendet werden:

Private Sub TreeView1_MouseUp( _
    ByVal sender As Object, _
    ByVal e As MouseEventArgs _
) Handles TreeView1.MouseUp
    If e.Button = MouseButtons.Right Then
        Dim n As TreeNode = Me.TreeView1.GetNodeAt(e.X, e.Y)
        If Not n Is Nothing Then
            Me.TreeView1.SelectedNode = n
            Me.MenuItem1.Text = n.Text
        Else
            Me.MenuItem1.Text = "(no item selected)"
        End If
        Me.ContextMenu1.Show(Me.TreeView1, New Point(e.X, e.Y))
    End If
End Sub
Knotenspezifische Kontextmenüs in einem TreeView-Steuerelement.

Der Übersichtlichkeit wegen wird hier auf die Definition verschiedener Kontextmenüs verzichtet. Weiters sollte bei Verwendung des obigen Codes in eigenen Anwendungen Berücksichtigung finden, daß das Kontextmenü auch durch Drücken der Kontextmenütaste angezeigt werden können sollen.

Erstellen eines horizontalen Splitter-Steuerelements

Das Splitter-Steuerelement aus den Windows Forms ist standardmäßig ein vertikaler Teiler. Will man allerdings einen horizontalen Splitter verwenden, wird man in der Dokumentation keinerlei Informationen finden. Es existiert keine Eigenschaft, mittels derer man einstellen kann, ob das Splitter-Steuerelement vertikal oder horizontal aufteilen soll. Die Entscheidung, ob es sich um einen vertikalen oder horizontalen Teiler handelt, wird danach getroffen, wo er gedockt wird. Die Anweisung Splitter1.Dock = DockStyle.Top bewirkt in diesem Fall, daß das Splitter-Steuerelement horizontal verläuft:


' Beispielsteuerelemente erstellen.
Dim TreeView1 As New TreeView()
Dim ListView1 As New ListView()
Dim Splitter1 As New Splitter()

TreeView1.Dock = DockStyle.Top

' Splitter-Steuerelement soll am unteren Rand des TreeView-Steuerelements docken.
Splitter1.Dock = DockStyle.Top

' ListView-Steuerelement soll unteren Bereich füllen.
ListView1.Dock = DockStyle.Fill

' Beispieleinträge hinzufügen.
TreeView1.Nodes.Add("TreeView Node")
ListView1.Items.Add("ListView Item")

' Steuerelemente im umgekehrter Reihenfolge dem Formular hinzufügen, um korrekte
' Positionierung sicherzustellen.
Me.Controls.AddRange(New Control() {ListView1, Splitter1, TreeView1})
Erstellen eines horizontalen Splitter-Steuerelements.

Deaktivieren von Schaltflächen im Druckvorschaudialog

Der Vorschaudialog der PrintPreviewDialog-Komponente enthält eine Schaltfläche, über die der Ausdruck gestartet werden kann. Manchmal soll der Benutzer aber nur eine Voransicht des Ausdrucks machen und nichts drucken können. Eine Möglichkeit, den Ausdruck aus dem Dialog heraus zu unterbinden, ist das Deaktivieren des der Schaltfläche. Der im nachstehenden Listing gezeigten Vorgehensweise folgend können auch andere Steuerelemente des Dialogs manipuliert werden:

Dim ppdlg As New PrintPreviewDialog()
With ppdlg

    ' Der Druckvorschau das Dokument zuweisen.
    .Document = m_pd

    ' Die Druckvorschau soll maximiert gezeigt werden.
    .WindowState = FormWindowState.Maximized

    ' "Drucken"-Schaltfläche deaktivieren.
    DirectCast(.Controls(1), ToolBar).Buttons(0).Enabled = False

    ' Druckvorschau anzeigen.
    .ShowDialog()
    .Dispose()
End With
Deaktivieren der Drucken-Schaltfläche im Druckvorschaudialog.

Definieren des Interpolationsmodus eines PictureBox-Steuerelements

Beim PictureBox-Steuerelement kann durch Setzen der Eigenschaft StretchMode auf den Wert StretchImage erwirkt werden, daß das in die Image-Eigenschaft geladene Bild in der Größe jener des Steuerelements angeglichen wird. Nachteilig dabei ist, daß es nicht möglich ist, den Interpolationsmodus anzugeben, der zum Vergrößern bzw. Verkleinern der Grafik benutzt wird. Durch Ableiten einer Klasse von PictureBox kann das PictureBox-Steuerelement um eine Eigenschaft InterpolationMode erweitert werden. Folgendes Listing zeigt den Quellcode der Klasse ExtendedPictureBox, die man anstelle des normalen PictureBox-Steuerelements benutzen kann, wenn man den Interpolationsmodus einstellen will:

Imports System
Imports System.ComponentModel
Imports System.Drawing.Drawing2D
Imports System.Windows.Forms

''' <summary>
'''   Erweitert das PictureBox-Steuerelement um die  Möglichkeit, den
'''   Interpolationsmodus anzugeben, der angewendet werden soll, wenn die
'''   Eigenschaft <c>SizeMode</c> auf <c>StretchImage</c> eingestellt ist.
''' </summary>
Public Class ExtendedPictureBox
    Inherits PictureBox

    Private m_InterpolationMode As InterpolationMode

    Public Sub New()
        MyBase.New()
        Me.InterpolationMode = InterpolationMode.Default
    End Sub

    Protected Overrides Sub OnPaint( _
        ByVal e As PaintEventArgs _
    )
        Try
            e.Graphics.InterpolationMode = Me.InterpolationMode
        Catch

            ' Hier kann es zu einem Fehler kommen, wenn es sich bei
            ' der Grafik um eine Bitmap mit indizierten Farben handelt.
        End Try
        MyBase.OnPaint(e)
    End Sub

    ''' <summary>
    '''   Gibt den Interpolationsmodus, der verwendet werden soll, wenn die
    '''   Eigenschaft <c>SizeMode</c> auf <c>StretchImage</c> eingestellt ist,
    '''   an oder gibt ihn zurück.
    ''' </summary>
    ''' <value>Interpolatiosmodus für das Strecken des Inhalts.</value>
    < _
        Category("Behavior"), _
        Description("Gibt den Interpolationsmodus an oder gibt ihn zurück.") _
    > _
    Public Property InterpolationMode() As InterpolationMode
        Get
            Return m_InterpolationMode
        End Get
        Set(ByVal Value As InterpolationMode)
            m_InterpolationMode = Value
            Me.Invalidate()
        End Set
    End Property
End Class
Erweiterung der Klasse PictureBox um die Eigenschaft InterpolationMode.

Ermitteln der Zeilennummer der Einfügemarke

Neben dem Mauszeiger gibt es bei TextBox-Steuerelementen auch eine Einfügemarke. Dabei handelt es sich um den blinkenden Balken, der an der aktuellen Schreibposition angezeigt wird. Um die Nummer jener Zeile zu ermitteln, in der sich die Einfügemarke gerade befindet, kann die Windows-API-Funktion SendMessage in Verbindung mit der Nachricht EM_LINEFROMCHAR herangezogen werden. Das nachstehende Beispiel schreibt die Gesamtanzahl der Zeilen des TextBox-Steuerelements sowie die Zeilennummer, in der die Einfügemarke positioniert ist, in die Titelleiste des Formulars:

Private Declare Auto Function SendMessage Lib "user32.dll" ( _
    ByVal hWnd As IntPtr, _
    ByVal wMsg As Int32, _
    ByVal wParam As IntPtr, _
    ByVal lParam As IntPtr _
) As IntPtr

Private Const EM_GETLINECOUNT As Int32 = &HBA
Private Const EM_LINEFROMCHAR As Int32 = &HC9

Private Sub TextInfo()
    Dim n As Int32 = _
        SendMessage(Me.TextBox1.Handle, EM_GETLINECOUNT, IntPtr.Zero, IntPtr.Zero)
    Dim m As Int32 = _
        SendMessage(Me.TextBox1.Handle, EM_LINEFROMCHAR, New IntPtr(-1), IntPtr.Zero) + 1
    Me.Text = _
        n.ToString() & " Zeilen, " & _
        "Aktuelle Zeile: " & m.ToString()
End Sub

Private Sub Form1_Load( _
    ByVal sender As Object, _
    ByVal e As EventArgs _
) Handles MyBase.Load
    With Me.TextBox1
        .Multiline = True
        .Size = New Size(100, 150)
        .Text = ""
    End With
    For i As Integer = 1 To 100
        Me.TextBox1.AppendText( _
            "Line " & i.ToString() & ControlChars.NewLine _
        )
    Next i
End Sub

Private Sub TextBox1_KeyUp( _
    ByVal sender As Object, _
    ByVal e As KeyEventArgs _
) Handles TextBox1.KeyUp
    TextInfo()
End Sub

Private Sub TextBox1_TextChanged( _
    ByVal sender As Object, _
    ByVal e As EventArgs _
) Handles TextBox1.TextChanged
    TextInfo()
End Sub

Private Sub TextBox1_MouseUp( _
    ByVal sender As Object, _
    ByVal e As MouseEventArgs _
) Handles TextBox1.MouseUp
    TextInfo()
End Sub
Ermitteln der Zeile, in der sich die Einfügemarke befindet.

Sichtbares Niederdrücken von Schaltflächen

Zu Demonstrationszwecken ist es ab und zu erforderlich, eine Schaltfläche niedergedrückt darzustellen, ohne dabei aber ein Ereignis auszulösen. Damit folgendes Beispiel funktioniert, muß auf dem Formular eine Schaltfläche Button1 plaziert und eine Timer-Komponente mit dem Namen Timer1 installiert sein. Die Timer-Komponente sollte ein Intervall von ca. einer Sekunde aufweisen und aktiviert sein. Der nachfolgend angegebene Code bewirkt, daß die Schaltfläche in regelmäßigen Abständen gedrückt und wieder losgelassen wird. Die Eigenschaft FlatStyle der Schaltfläche muß dabei auf System festgelegt sein:

Private Declare Auto Function SendMessage Lib "user32.dll" ( _
    ByVal hWnd As IntPtr, _
    ByVal wMsg As Int32, _
    ByVal wParam As IntPtr, _
    ByVal lParam As IntPtr _
) As IntPtr

Private Const BM_SETSTATE As Int32 = &HF3
Private Const BM_GETSTATE As Int32 = &HF2

Private m_Down As Boolean

Private Sub Timer1_Tick( _
    ByVal sender As Object, _
    ByVal e As EventArgs _
) Handles Timer1.Tick
    If m_Down Then
        SendMessage(Me.Button1.Handle, BM_SETSTATE, New IntPtr(CInt(False)), IntPtr.Zero)
    Else
        SendMessage(Me.Button1.Handle, BM_SETSTATE, New IntPtr(CInt(True)), IntPtr.Zero)
    End If
    m_Down = Not m_Down
End Sub
Visuelles Niederdrücken und Loslassen einer Schaltfläche.

Automatisches Scrollen beim ListView-Steuerelement

Ab und zu werden größere Datenmengen in einem ListView-Steuerelement dargestellt. Bei Änderungen ist es eventuell sinnvoll, zum gerade hinzugefügten Eintrag zu scrollen, um den Benutzer darauf aufmerksam zu machen. Zu diesem Zweck verfügen Einträge in einem ListView-Steuerelement, bei denen es sich um Instanzen der Klasse ListViewItem handelt, über die Methode EnsureVisible. Ein Aufruf der Methode EnsureVisible bewirkt, daß der Inhalt des enthaltenden ListView-Steuerelements so bewegt wird, daß der betreffende Eintrag in den sichtbaren Bereich rückt. Dieses Verhalten ist allerdings dann störend, wenn der Benutzer gerade mit der Maus innerhalb des ListView-Steuerelements Elemente manipuliert.

Der folgende Code benutzt einen Timer Timer1, der bei jedem Aufruf des Ereignisses Tick einen neuen Eintrag dem ListView-Steuerelement ListView1 hinzufügt. Befindet sich der Mauszeiger über dem Steuerelement oder hat dieses den Fokus, wird nicht zum zuletzt eingefügten Eintrag bewegt, im anderen Fall schon:

Private Sub Timer1_Tick( _
    ByVal sender As Object, _
    ByVal e As EventArgs _
) Handles Timer1.Tick
    Dim lvi As ListViewItem = _
        Me.ListView1.Items.Add( _
            "Element " & _
            (Me.ListView1.Items.Count + 1).ToString() _
        )
    If _
        Not Me.ListView1.Focused AndAlso _
        Not Me.RectangleToScreen( _
            New Rectangle( _
                Me.ListView1.Location, Me.ListView1.Size _
            ) _
        ).Contains(Me.ListView1.MousePosition) _
    Then
        lvi.EnsureVisible()
    End If
End Sub
Automatisches Scrollen eines ListView-Steuerelements zu einem neu eingefügten Eintrag.

Binden von Daten an die Einträge eines ComboBox-Steuerelements

Die Steuerelemente ComboBox und ListBox dienen dazu, dem Benutzer eine Liste von Einträgen zur Auswahl bereitzustellen. Jeder Eintrag ist durch eine Beschriftung beschrieben. Wenn nun der Benutzer einen der Einträge wählt, dann sollen eventuell Änderungen an den Daten des hinter dem Eintrag liegenden Objekts durchgeführt oder Steuerelemente mit den Daten der aktuellen Auswahl angepaßt werden.

Um es dem Programmierer leichter zu machen, besitzen Einträge eines ComboBox-Steuerelements in der Auflistung der Einträge den Datentyp Object. Woher nimmt sich aber .NET den Text für die Beschriftung des Eintrags in der Liste? Die Lösung ist einfacher, als man denken würde: .NET ruft die Methode ToString des Objekts auf, das als Eintrag zugewiesen wurde. Folgendes Listing zeigt, wie ein Person-Objekt instanziert und einem ComboBox-Steuerelement hinzugefügt wird:


' Definieren eines Datenobjekts.
Dim p As New Person()
p.Name = "Pink Panther"
p.Age = 22

' Eintrag dem ComboBox-Steuerelement hinzufügen.
Me.ComboBox1.Items.Add(p)

' Auslesen eines Eintrags zu Testzwecken.
MsgBox(DirectCast(Me.ComboBox1.Items(0), Person).ToString())
Binden von Daten an Einträge eines ComboBox-Steuerelements.

Das nächste Listing zeigt die Implementierung der Klasse Person. In der Methode ToString wird eine Zeichenfolge zusammengesetzt, welche die Daten des Objekts repräsentiert. In unserem Beispiel werden dazu Name und Alter der Person herangezogen:

Public Class Person
    Private m_Name As String
    Private m_Age As Integer

    Public Property Name() As String
        Get
            Return m_Name
        End Get
        Set(ByVal Value As String)
            m_Name = Value
        End Set
    End Property

    Public Property Age() As Integer
        Get
            Return m_Age
        End Get
        Set(ByVal Value As Integer)
            m_Age = Value
        End Set
    End Property

    Public Overrides Function ToString() As String
        Return Me.Name & " (" & Me.Age.ToString() & ")"
    End Function
End Class
Klasse für einen Eintrag eines ComboBox-Steuerelements.

Es ist möglich, einem ListBox-Steuerelement Einträge unterschiedlichen Datentyps hinzuzufügen. Um auf die Mitglieder des entsprechenden Eintrags zugreifen zu können, muß dessen Typ mittels DirectCast in den passenden Datentyp umgewandelt werden.

Eine alternative Möglichkeit besteht darin, das Steuerelement an Daten zu binden. Die Implementierung der Klasse Person lassen wir gleich. In der Eigenschaft DataSource wird die Datenquelle angegeben, in unserem Beispiel handelt es sich dabei um eine Sammlung von Person-Objekten. DisplayMember gibt an, welches Mitglied der gebundenen Objekte als Eintragsbeschriftung angezeigt werden soll:

With Me.ListBox1
    .DataSource = Database.FindPeople(…)
    .DisplayMember = "Name"
    .ValueMember = "Age"
End With
Binden von Daten an ein ComboBox-Steuerelement.

Markieren des Inhalt eines TextBox-Steuerelements bei Fokuserhalt

Der Text eines TextBox-Steuerelements soll automatisch markiert werden, wenn diese den Fokus erhält. Am Einfachsten ist dies sicher durch Hinzufügen einer von mehreren TextBox-Steuerelementen geteilten Ereignisbehandlungsprozedur für das Enter-Ereignis zu bewerkstelligen. Diese Lösung hat allerdings den Nachteil, daß sie schwer erweiterbar ist und den Selektionsvorgang im Quellcode nicht transparent macht.

Ein wesentlich ausgereifterer Ansatz liegt in der Entwicklung eines Steuerelements, das von System.Windows.Forms.TextBox ableitet und das TextBox-Steuerelement um die gewünschte Funktionalität erweitert. In unserem Beispiel erhält das TextBox-Steuerelement eine zusätzliche Eigenschaft AutoSelect, in der eingestellt werden kann, ob der Inhalt bei Fokuserhalt markiert werden soll oder nicht. Die Klasse ExtendedTextBox kann somit als vollwertiger Ersatz des normalen TextBox-Steuerelements verwendet werden:

Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Windows.Forms

Public Class ExtendedTextBox
    Inherits TextBox

    Private m_AutoSelect As Boolean

    Public Sub New()
        MyBase.New()
        Me.AutoSelect = False
    End Sub

    < _
        Category("Behavior"), _
        Description( _
            "Gibt an, ob der Inhalt bei Fokuserhalt markiert wird oder " & _
            "gibt dies zurück." _
        ) _
    > _
    Public Property AutoSelect() As Boolean
        Get
            Return m_AutoSelect
        End Get
        Set(ByVal Value As Boolean)
            m_AutoSelect = Value
        End Set
    End Property

    Private Overrides Sub OnEnter( _
        ByVal e As EventArgs _
    )
        MyBase.OnEnter(e)
        If Me.AutoSelect Then
            Me.SelectAll()
        End If
    End Sub
End Class
Erweiterung der Klasse TextBox um die Eigenschaft AutoSelect.