Kombinieren mehrerer Steuerelemente zu einem Steuerelement

 

Duncan Mackenzie
Microsoft Developer Network

Mai 2002

Zusammenfassung: Veranschaulicht, wie sie ein neues Steuerelement erstellen, indem mehrere Microsoft Windows Forms-Steuerelemente kombiniert werden. Eines einer Reihe von Beispielen zur Entwicklung von Windows-Steuerelementen, die in Verbindung mit dem zugehörigen Übersichtsartikel gelesen werden sollen. (10 gedruckte Seiten)

Laden Sie WinFormControls.exeherunter.

Dieser Artikel ist der dritte in einer Fünf-Artikel-Reihe zum Entwickeln von Steuerelementen in Microsoft® .NET:

Inhalte

Einführung
Hinzufügen der konstituierenden Steuerelemente
Handhabung der Größenänderung
Damit es funktioniert
Letzte Berührungen
Zusammenfassung

Einführung

In diesem Artikel werden zwei wichtige Konzepte zur Steuerungsentwicklung behandelt: das Erstellen von benutzerdefinierten Steuerelementen durch Kombinieren mehrerer vorhandener Steuerelemente und die Unterstützung der Datenbindung an Ihr benutzerdefiniertes Steuerelement. Als Beispiel werde ich ein Umfrageschaltflächen-Steuerelement erstellen (siehe Abbildung 1), das nicht das am häufigsten verwendete Steuerelement ist, das jedoch die Kombination mehrerer anderer Steuerelemente erfordert und eine benutzerdefinierte Eigenschaft enthält, die Sie möglicherweise an eine Datenquelle binden möchten.

Abbildung 1. Mit dem Umfrageschaltflächen-Steuerelement kann eine Gruppe von Optionsfeldern als einzelnes Element positioniert und codiert werden.

Anstatt fünf einzelne Optionsfelder für jede dieser Umfragefragen zu verwenden, könnten diese Schaltflächen in einem einzelnen Steuerelement kombiniert werden, das als Einheit verwendet und datengebunden werden kann. Dazu muss ein Steuerelement erstellt werden, das dem "klassischen" Microsoft® ActiveX-Steuerelement® für Microsoft Visual Basic® 5.0 und Visual Basic 6.0 sehr ähnlich ist und mehrere Standardsteuerelemente in einem neuen benutzerdefinierten Steuerelement kombiniert und in Microsoft .NET über die Erstellung eines Benutzersteuerelements behandelt wird. Benutzersteuerelemente sind benutzerdefinierte Steuerelemente, die von der System.Windows.Forms.UserControl-Klasse erben (weitere Informationen finden Sie in der Microsoft Visual Studio .NET-Dokumentation ®zu Visual Basic- und Microsoft Visual C#-® Konzepten) und verfügen über eine Entwurfsoberfläche, die einer Microsoft Windows® Form sehr ähnlich ist. Es werden Vorlagen zum Erstellen dieses Steuerelements in Visual Basic .NET oder Microsoft Visual C# ® .NET bereitgestellt. Das Hinzufügen eines neuen Benutzersteuerelements zu einem Projekt ist daher so einfach, als klicken Sie mit der rechten Maustaste auf Ihr Projekt, klicken Sie auf Hinzufügen und dann auf Benutzersteuerelement hinzufügen , wie in Abbildung 2 dargestellt.

Hinweis Wenn Sie über Visual Basic .NET Standard Edition oder Visual C# .NET Standard Edition im Gegensatz zu einer der Versionen von Visual Studio .NET (Professional, Enterprise Developer oder Enterprise Architect) verfügen, verfügen Sie nicht über die Vorlage UserControl. Laden Sie einfach den Code für diesen Artikel herunter, und beginnen Sie mit der fertigen Version, oder wenn Sie ein neues leeres Benutzersteuerelement erstellen möchten, erstellen Sie einfach ein Windows Form und ändern Sie die Zeile "Erbt System.Windows.Forms.Form" in "Inherits System.Windows.Forms.UserControl". Sie erhalten eine Fehlermeldung in einer Zeile des neuen Steuerelements, "Me.AutoScaleBaseSize = ...", aber Sie können einfach die verletzende Zeile entfernen, und alles andere sollte funktionieren.

Abbildung 2. Sie können ein neues Benutzersteuerelement hinzufügen, indem Sie in Projektmappen-Explorer mit der rechten Maustaste auf ihr Projekt klicken oder das Menü Projekt auf der menüleiste Standard verwenden.

Wie bereits erwähnt, stellt ein Benutzersteuerelement eine Entwurfsoberfläche bereit, die einer Windows Form ähnelt, sodass Sie einen Container erhalten, auf dem Sie andere Steuerelemente platzieren können. Das Erstellen eines zusammengesetzten Steuerelements (ein benutzerdefiniertes Steuerelement, das aus einer Kombination aus anderen Steuerelementen besteht) umfasst drei Standard Schritte:

  1. Fügen Sie die konstituierenden Steuerelemente hinzu, positionieren Sie sie, und konfigurieren Sie ihre Eigenschaften wie gewünscht.
  2. Schreiben sie den Code, damit diese Steuerelemente wie gewünscht für Ihre neue Komponente zusammenarbeiten, zusammen mit dem anderen Code, der erforderlich ist, damit Das Steuerelement funktioniert.
  3. Erstellen der Eigenschaften, Methoden und Ereignisse, die die öffentliche Schnittstelle Ihrer neuen Komponente für Programmierer beschreiben, die sie verwenden.

Hinzufügen der konstituierenden Steuerelemente

Nach diesen Schritten habe ich zunächst fünf Optionsfelder zu einem neuen Benutzersteuerelement hinzugefügt und wie gewünscht positioniert (siehe Abbildung 3). Ich habe die Untertitel Text aus allen Schaltflächen entfernt, sie in Höhe und Breite so klein wie möglich gemacht und sie dann in einer Zeile positioniert.

Abbildung 3. Positionieren der Optionsfelder

Hinweis Wenn Sie die Ausrichtungsoptionen des Steuerelements auf der Layoutsymbolleiste verwenden, können Sie diese Steuerelemente ganz einfach anordnen: Wählen Sie einfach alle Steuerelemente aus, klicken Sie auf Tops ausrichten , um sie vertikal auszurichten, und entfernen/vergrößern Sie dann den horizontalen Abstand, bis Sie das gewünschte Aussehen erreicht haben.

Sobald die Steuerelemente alle größen und platziert sind, ändere ich die Namen der fünf Optionsfelder in rb0-rb4 – Namen, die aus keinem anderen Grund ausgewählt wurden, als die Übersichtlichkeit im Code des Steuerelements zu verringern. Dies ist ein guter Zeitpunkt, um sicherzustellen, dass die Steuerelementnamen mit ihrer Positionierung (rb0-rb1-rb2-rb3-rb4) und ihrer Aktivierreihenfolge übereinstimmen (um die aktuellen Werte anzuzeigen, klicken Sie im Menü Ansicht auf Tabstoppreihenfolge, wie in Abbildung 4 dargestellt).

Abbildung 4. Die Aktivierreihenfolge kann in Visual Studio .NET angezeigt und visuell festgelegt werden.

Handhabung der Größenänderung

Bei einem Steuerelement wie diesem, bei dem mehrere konstituierende Steuerelemente an festen Positionen vorhanden sind, möchten Sie entweder das Steuerelement einmal richtig dimensionieren und dann verhindern, dass der Benutzer die Größe ändert, oder Ihr Layout automatisch anpassen, wenn die Größe des Steuerelements geändert wird. In diesem Fall soll dieses Steuerelement eine feste Größe haben. Das Ändern der Größe von Optionsfeldern ist etwas, das ich nicht berücksichtigen möchte, daher muss ich meinem Steuerelement code hinzufügen, um zu verhindern, dass der Benutzer die Größe des Formulars des Benutzers ändert. Durch Überschreiben der SetBoundsCore-Methode kann ich alle Versuche abfangen, die Größe des Steuerelements zu ändern oder zu verschieben, und jede andere Änderung als das Verschieben des Steuerelements verhindern. In meinem überschriebenen SetBoundCore übergegehe ich die Positionsinformationen (x,y) unverändert, sodass das Steuerelement wie gewünscht verschoben werden kann, aber unabhängig davon, welche Höhen- und Breiteninformationen an die -Methode übergeben werden, übergehe ich immer eine feste Größe:

Const fixedWidth As Integer = 120
Const fixedHeight As Integer = 24

Protected Overrides Sub SetBoundsCore(ByVal x As Integer, 
               ByVal y As Integer, _
            ByVal [width] As Integer, ByVal height As Integer, _
            ByVal specified As BoundsSpecified)
    MyBase.SetBoundsCore(x, y, fixedWidth, fixedHeight, specified)
End Sub

Wenn Sie die Größenänderung auf andere Weise behandeln möchten, z. B. die Größe und Position Ihrer konstituierenden Steuerelemente anpassen, sollten Sie dem Resize-Ereignis Ihrer Basisklasse (UserControl) Ihren eigenen Code hinzufügen:

Private Sub SurveyRadioButtons_Resize(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles MyBase.Resize

Damit es funktioniert

Nur zwei Dinge müssen von diesem Steuerelement verfügbar gemacht werden, damit es verwendet werden kann: eine Eigenschaft, die das aktuell ausgewählte Optionsfeld darstellt, und ein Ereignis, das bei jeder Änderung der Auswahl ausgelöst wird. Um die Arbeit mit den mehreren Optionsfeldern zu erleichtern, platziert das erste Codebit, das ich diesem Steuerelement hinzufügen werde, sie in einem Array:

Const numButtons As Integer = 5
Dim radioButtons(numButtons - 1) As RadioButton

Public Sub New()
    MyBase.New()
    'This call is required by the Windows Form Designer.
    InitializeComponent()
    'Add any initialization after the InitializeComponent() call
    radioButtons(0) = Me.rb0
    radioButtons(1) = Me.rb1
    radioButtons(2) = Me.rb2
    radioButtons(3) = Me.rb3
    radioButtons(4) = Me.rb4
    Me.rb0.Checked = True
End Sub

Als Nächstes füge ich eine Eigenschaft für das derzeit ausgewählte Optionsfeld hinzu:

Dim currentValue As Integer = 1

<System.ComponentModel.Category("Appearance")> _
Public Property CurrentSelection() As Integer
    Get
        Return currentValue
    End Get
    Set(ByVal Value As Integer)
        If Value > 0 And Value <= numButtons Then
            radioButtons(Value - 1).Checked = True
        End If
    End Set
End Property

Damit diese Eigenschaft den richtigen Wert zurückgibt, muss die Variable currentValue mit dem aktuell ausgewählten Optionsfeld auf dem neuesten Stand gehalten werden. Die einfachste Möglichkeit, dies zu erreichen, besteht darin, einen Ereignishandler für das CheckedChanged-Ereignis aller Optionsfelder bereitzustellen und den Wert in diesem Handler zu aktualisieren:

Public Event CurrentSelectionChanged As EventHandler

Const numButtons As Integer = 5
Dim radioButtons(numButtons - 1) As RadioButton
Dim currentValue As Integer = 1

Private Sub rb_CheckedChanged(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
        Handles rb0.CheckedChanged, rb1.CheckedChanged, _
                    rb2.CheckedChanged, rb3.CheckedChanged, _
                    rb4.CheckedChanged
    Dim rb As RadioButton = CType(sender, RadioButton)
    If rb.Checked Then
        Dim i As Integer = 0
        Dim buttonFound As Boolean = False
        Do While (i < numButtons) And Not buttonFound
            If radioButtons(i).Checked Then
                currentValue = i + 1
                buttonFound = True
                RaiseEvent CurrentSelectionChanged(CObj(Me), e)
            End If
            i += 1
        Loop
    End If
End Sub

Dieser Ereignishandler wird zweimal aufgerufen, wenn sich der Wert ändert – einmal, wenn das zuvor ausgewählte Optionsfeld deaktiviert ist, und einmal, wenn das neue Optionsfeld aktiviert ist . Daher verwendet der Code "If rb. Aktiviert, um sicherzustellen, dass das Ereignis nur einmal verarbeitet wird. Nachdem der neue Wert ermittelt wurde, löst der Code ein Ereignis aus, um anzukündigen, dass sich der Wert geändert hat.

Letzte Berührungen

Jetzt, da mein neues SurveyRadioButtons-Steuerelement funktioniert, kann ich mich mit der Darstellung des Steuerelements beschäftigen und sicherstellen, dass es die Datenbindung ordnungsgemäß unterstützt.

Fokusrechteck

Wenn der Benutzer ein Standard-Optionsfeld ausgewählt hat, wird dies durch die Verwendung eines Fokusrechtecks (ein gepunktetes Rechteck um das Steuerelement) angegeben. Dieses Rechteck wird nur um den Text des Optionsfelds gezeichnet, nicht das gesamte Steuerelement. Wenn das Optionsfeld also ohne Text verwendet wird (wie in diesem Umfragesteuerelement), gibt es keinen sichtbaren Indikator, dass das Steuerelement den Fokus hat. Dies ist nicht wirklich akzeptabel, da der Benutzer nicht wissen würde, welches Steuerelement ausgewählt wurde, bis der Wert der Umfrageschaltfläche tatsächlich geändert wurde. Daher möchte ich mein eigenes Fokusrechteck hinzufügen, das um mein gesamtes benutzerdefiniertes Steuerelement herum angezeigt wird. Glücklicherweise ist dies für mich eine relativ einfache Aufgabe, da die .NET Framework eine spezielle Klasse mit dem Namen System.Windows.Forms.ControlPaint enthält, um allgemeine Steuerelementzeichnungsaufgaben zu verarbeiten. Diese Klasse macht eine Vielzahl von freigegebenen (statischen in C#)-Methoden verfügbar, einschließlich der DrawFocusRectangle-Methode, die ein Standardmäßiges Windows-Fokusrechteck an der von Ihnen angegebenen Stelle zeichnet. Mit dieser Methode und einem privaten booleschen Flag, das ich in den GotFocus - und LostFocus-Ereignissen festgelegt habe, überrode ich die OnPaint-Methode meiner übergeordneten Klasse (UserControl) und habe meinen Umfrageschaltflächen ein Fokusrechteck hinzugefügt.

Hinweis Nachdem ich die verfügbaren Eigenschaften eines Steuerelements durchgelesen habe, hätte ich die ContainsFocus-Eigenschaft (die True zurückgibt, wenn das Steuerelement oder eines seiner konstituierenden Steuerelemente den Fokus hat) anstelle meiner hasFocus-Variablen verwendet, aber ContainsFocus ruft die zugrunde liegenden Microsoft Win32-APIs® auf, was ein bisschen zu einem Leistungseinbußen sein kann. Da meine OnPaint-Routine häufig aufgerufen wird, möchte ich keine Aufrufe der Win32-API auslösen, wenn ein wenig zusätzlichen Code es mir ermöglicht, diesen Aufruf durch eine Variable zu ersetzen:

Dim hasFocus As Boolean = False
Dim showFocusRect As Boolean = MyBase.ShowFocusCues
Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs)
    hasFocus = True
End Sub

Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
    hasFocus = False
End Sub

Private Sub SurveyRadioButtons_ChangeUICues(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.UICuesEventArgs) _
        Handles MyBase.ChangeUICues
    showFocusRect = e.ShowFocus
End Sub

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    Dim focusRect As Rectangle = Me.ClientRectangle
    focusRect.Inflate(-2, -2)
    If hasFocus And showFocusRect Then
        'Draw focus rectangle around control
        ControlPaint.DrawFocusRectangle(e.Graphics, _
            focusRect, Me.ForeColor, Me.BackColor)
    Else
        'erase focus rectangle
        e.Graphics.DrawRectangle(New Pen(Me.BackColor, 1), focusRect)
    End If
    MyBase.OnPaint(e)
End Sub

Protected Overrides Sub OnEnter(ByVal e As System.EventArgs)
    Me.Invalidate() 'Invalidate to force redraw of focus rectangle
End Sub

Protected Overrides Sub OnLeave(ByVal e As System.EventArgs)
    Me.Invalidate() 'Invalidate to force redraw of focus rectangle
End Sub

Da das Fokusrechteck nur gezeichnet oder gelöscht wird, wenn das Steuerelement gezeichnet wird, sind die Aufrufe von Invalidate in OnEnter und OnLeave erforderlich, um eine Neuschreibung zu erzwingen, wenn das Steuerelement den Fokus gewinnt oder verliert.

Datenbindung

Visual Basic-Entwickler konnten ui-Elemente, z. B. Steuerelemente, seit mehreren Versionen direkt mit Datenquellen (ein Prozess, der als Datenbindung bezeichnet wird) verknüpfen, und Visual Basic .NET ist keine Ausnahme. Es gibt zwei Arten von Datenbindung, die manchmal als einfach und komplex beschrieben werden, und jeder Typ wird unterschiedlich implementiert. Die einfache Bindung erfolgt auf Eigenschaftsebene, wobei ein Feld aus Ihrer Datenquelle mit einer einzelnen Eigenschaft eines Ihrer Steuerelemente verknüpft ist (die Text-Eigenschaft eines TextBox-Steuerelements für instance). Komplexe Bindung wird nicht auf Eigenschaftsebene gebunden. Sie dient zum Binden einer gesamten Datenquelle an Ihr Steuerelement. Um den Unterschied zwischen diesen beiden Bindungstypen besser zu verstehen, betrachten Sie einige gängige Beispiele für die einzelnen: Binden des Felds "vorname" aus einer Tabelle an ein TextBox-Element, sodass textBox den Wert dieses Felds anzeigt, ist eine einfache Bindung, während das Binden einer gesamten Liste von Autoren (mit mehreren Spalten) an ein DataGrid-Steuerelementeine komplexe Bindung ist.

Da die Datenbindung eine gängige Technik für Entwickler ist, sollten Sie sicherstellen, dass jedes Steuerelement, das Sie ordnungsgemäß erstellen, diese unterstützt. Aber in .NET, zumindest für einfache Datenbindung, ist dies so einfach, dass es schwierig ist, falsch zu sein. Sie müssen eigentlich nichts tun, um die einfache Datenbindung zu unterstützen. Standardmäßig kann jede der Eigenschaften Ihres Steuerelements an eine Datenquelle gebunden werden, und sie werden automatisch ordnungsgemäß verknüpft. Es gibt keine wirkliche Einschränkung, welche Eigenschaften gebunden werden können, sodass Sie die Tab-Indexeigenschaft eines Steuerelements bei Bedarf mit Daten binden können, aber oft gibt es nur wenige Eigenschaften, die Sie jemals binden möchten. Um Entwicklern das Leben zu erleichtern, können die Datenbindungseinstellungen für einige ausgewählte Eigenschaften direkt im Eigenschaftenfenster (in der Kategorie Daten unter DataBindings) verfügbar gemacht werden, und es muss eine Option Erweitert ausgewählt werden, um eine der anderen Eigenschaften zu binden. In einem TextBox-Steuerelement werden für instance die Eigenschaften Text und Tag als allgemeine datengebundene Eigenschaften verfügbar gemacht. Bei einem neuen Benutzersteuerelement, das Sie erstellt haben, kann nur Tag direkt im Eigenschaftenfenster gebunden werden, es sei denn, Sie fügen Attribute hinzu, um anzugeben, welche Ihrer Eigenschaften wahrscheinlich datengebunden sind. Im Fall meines SurveyRadioButtons-Steuerelements denke ich, dass CurrentSelection die wahrscheinlichste Datenbindungseigenschaft ist. Daher füge ich dieser Eigenschaft ein Attribut hinzu, damit sie im Abschnitt Daten des Eigenschaftenfenster angezeigt wird:

<System.ComponentModel.Category("Appearance"), _
  System.ComponentModel.Bindable(True)> _
Public Property CurrentSelection() As Integer
    Get
        Return currentValue
    End Get
    Set(ByVal Value As Integer)
        If Value > 0 And Value < numButtons Then
            radioButtons(Value - 1).Checked = True
        End If
    End Set
End Property

Die zugrunde liegende Microsoft Windows Forms-Engine kann einfache Datenbindungen effizienter verarbeiten, wenn ein entsprechendes Changed-Ereignis für die datengebundene Eigenschaft namens Property>Changed vorhanden< ist. Durch dieses Ereignis können Windows Forms das -Ereignis verwenden, um zu bestimmen, wann sich die Eigenschaft ändert, und die zugrunde liegende Datenquelle zu diesem Zeitpunkt aktualisieren. Andernfalls wird die Datenquelle unabhängig davon aktualisiert, ob die Eigenschaft geändert wurde. Im Fall dieses Steuerelements gibt es ein CurrentSelectionChanged-Ereignis , sodass es die einfache Datenbindung der CurrentSelection-Eigenschaft auf die effizienteste Weise behandelt.

Hinweis In der Regel ist es nicht möglich, Daten an den ausgewählten Wert einer Gruppe von Optionsfeldern zu binden, aber durch das Erstellen der Gruppe in ein benutzerdefiniertes Steuerelement kann ich die CurrentSelection-Eigenschaft einfach verfügbar machen und an diese binden. Es gibt viele Fälle, in denen eine datengebundene Gruppe von Optionsfeldern nützlich sein könnte. Beachten Sie daher dieses Beispiel beim Erstellen Ihrer Benutzeroberflächen.

Die komplexe Datenbindung eignet sich nicht für dieses bestimmte Steuerelement, aber der nächste Beispielartikel , Erweitern des TreeView-Steuerelements, behandelt ein Steuerelement, das die vollständige Datenquellenmethode der Bindung verwendet.

Zusammenfassung

Indem Sie mehrere Steuerelemente in einem benutzerdefinierten Steuerelement kombinieren, können Sie eine komplexe Mischung aus Code und Layout erstellen, die einfach verpackt und verteilt werden kann. Diese Art des Steuerelements ist nützlich für etwas Einfaches, z. B. die Kombination von Label und TextBox, oder für etwas Komplexes, z. B. ein vollständiges Adresseingabesteuerelement. Unabhängig davon, wie Sie diesen Steuerelementstil verwenden, ist es in der Form dem Entwicklungsmodell des ActiveX-Steuerelements von Visual Basic 5.0 und 6.0 am nächsten.

Hinweis Wie das SurveyRadioButtons-Steuerelement , aber denken Sie, dass es nicht flexibel genug für Ihre Anforderungen ist? Sie könnten ein flexibleres Steuerelement implementieren, wenn Sie ein Steuerelement erstellen, bei dem die Anzahl der Optionen konfigurierbar ist, obwohl der Code komplexer wäre und Sie zwischen dem Zeichnen eigener Optionsfelder (erneut mit der ControlPaint-Klasse ) oder dem dynamischen Erstellen einer Reihe regulärer Optionsfelder und dem Hinzufügen dieser Optionen zur Oberfläche Des Steuerelements im Code wählen müssten. Wenn Sie diese Route gehen und eine variable Anzahl von Optionen unterstützen, kann eine komplexe Datenbindung nützlich sein, um den Satz möglicher Optionen anzugeben. (Sie können eine Bindung an den Satz möglicher Optionen herstellen, um die Anzahl der Optionsfelder zu bestimmen und gleichzeitig die Value-Eigenschaft an eine andere Datenquelle zu binden, um die ausgewählte Antwort zu speichern.)