Code zu Steuerelementen in .NET
- Behandeln mehrerer Ereignisse in einer Ereignisbehandlungsprozedur
- Erstellen „unsichtbarer“ Steuerelemente
- Besitzen Steuerelementeigenschaften ihre Standardwerte?
- Ansprechen von Steuerelementen über ihren Namen
- Hinzufügen knotenspezifischer Kontextmenüs in einem TreeView-Steuerelement
- Erstellen eines horizontalen Splitter-Steuerelements
- Deaktivieren von Schaltflächen im Druckvorschaudialog
- Definieren des Interpolationsmodus eines PictureBox-Steuerelements
- Ermitteln der Zeilennummer der Einfügemarke
- Sichtbares Niederdrücken von Schaltflächen
- Automatisches Scrollen beim ListView-Steuerelement
- Binden von Daten an die Einträge eines ComboBox-Steuerelements
- Markieren des Inhalt eines TextBox-Steuerelements bei Fokuserhalt
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
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
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
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
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})
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
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
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
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
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
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())
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
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
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
TextBox
um die Eigenschaft AutoSelect
.