Zeichnen eigener Steuerelemente mit GDI+

 

Duncan Mackenzie
Microsoft Developer Network

Mai 2002

Zusammenfassung: Details zur Entwicklung eines datengebundenen, vom Besitzer gezeichneten Steuerelements mithilfe von GDI+ als eines der Beispiele zur Entwicklung von Microsoft Windows-Steuerelementen, die in Verbindung mit einem zugehörigen Übersichtsartikel gelesen werden sollen. (15 gedruckte Seiten)

Laden Sie WinFormControls.exeherunter.

Dieser Artikel ist der fünfte in einer Fünf-Artikel-Reihe über die Entwicklung von Steuerelementen in Microsoft® .NET:

Inhalte

Einführung
Unterstützung komplexer Datenbindungen
Zeichnen der Bilder
Überschreiben von OnPaint
Behandeln von Tastendrücken und Mausklicks
Beispiel
Zusammenfassung

Einführung

Bevor Sie beginnen, lassen Sie mich Ihnen eine kleine Warnung geben: Erstellen Sie Ihre eigene Kontrolle von Grund auf neu und ihre eigene Zeichnung sollten nur dann durchgeführt werden, wenn Sie alle ihre anderen Optionen ausgeschöpft haben. Aus technischer Sicht ist es nicht extrem schwierig (obwohl es nicht einfach ist), aber es gibt eine so enorme Anzahl von Details, die behandelt werden müssen, dass Sie wahrscheinlich ein Steuerelement erstellen, dem mindestens einige der Design- und Benutzerfreundlichkeitsfeatures der Steuerelemente fehlen, die mit Microsoft® Visual Studio® .NET ausgeliefert werden. Wenn Sie mit einem dieser bereitgestellten Steuerelemente beginnen, davon erben und Ihre zusätzlichen Funktionen auf der Basisklasse erstellen, können Sie fast die gesamte Steuerelementfunktionalität kostenlos erhalten. Jetzt, da ich Sie gewarnt habe, möchte ich auch sagen, dass es mir viel Spaß macht, meine eigenen Steuerelemente zu erstellen, besonders wenn ich meine eigene Schnittstelle mit GDI+ zeichnen kann, und ich hoffe, Sie werden es versuchen.

Es gibt wirklich keinen bestimmten Typ von Steuerelementen, der am besten von Grund auf neu erstellt wird, außer, dass es sich in der Regel um ein Steuerelement handelt, das ungewöhnlich genug ist, dass es keine Möglichkeit gibt, auf einem vorhandenen Element zu bauen. Das Beispiel, das ich erstellen werde, ist eine datengebundene Miniaturansicht und unterscheidet sich definitiv genug von jedem anderen Microsoft Windows® Form-Steuerelement, das ich benötige, um von Grund auf neu zu beginnen.

Hinweis Wenn Sie eine Anforderung für ein Steuerelement wie diese finden, etwas, das nicht von einem der Windows Forms-Steuerelementen verarbeitet wird, die mit Microsoft .NET ausgeliefert werden und nicht leicht zu sein scheinen, dann gibt es einen weiteren Schritt, den Sie befolgen sollten, bevor Sie sich entscheiden, es selbst zu erstellen: Sehen Sie sich die Drittanbieter von Steuerungsanbietern an! Im Übersichtsartikel zur Steuerungsentwicklung habe ich erwähnt, dass der Erfolg von Microsoft Visual Basic® teilweise auf die große Anzahl verfügbarer Steuerelemente von Drittanbietern zurückzuführen ist, und dies gilt auch für Visual Basic .NET.

Ursprünglich habe ich dieses Steuerelement erstellt, um die folgenden Anforderungen zu erfüllen:

  1. Zeigt eine mehrspaltige Ansicht von Bildern an, die mehrere Seiten unterstützt, und ermöglicht es einem einzelnen Element, den Fokus zu erhalten.
  2. Der Elementtext, das Bild (Bild-URL) und ein entsprechender Wert müssen datengebundene Eigenschaften sein.
  3. Der Benutzer des Steuerelements sollte in der Lage sein, eine Größe für die Bilder anzugeben, und das Steuerelement sollte die Größe und Positionierung der Bilder automatisch ändern.

Das Ergebnis ermöglichte es mir, mit minimalem Aufwand einen visuellen Browser für meine Heim-CD-Bibliothek (siehe Abbildung 1) zu erstellen.

Abbildung 1. Mögliche Verwendung für eine datengebundene Miniaturansicht

Ich musste meine eigene Schnittstelle für dieses Steuerelement mithilfe von GDI+ erstellen, da das nächstgelegene Windows Form-Steuerelement, die ListView, entstanden ist. Dieses Steuerelement und einige andere wie TextBox, ComboBox und ListBox ist tatsächlich ein allgemeines Windows-Steuerelement, das gerade mit .NET-Code umschlossen wurde, um die Verwendung in Windows Forms Anwendungen zu ermöglichen. Diese Steuerelemente erlauben es mir nicht, ihre Zeichnungsroutinen vollständig außer Kraft zu setzen, sodass ich nicht genügend Anpassungsmöglichkeiten für meine Anforderungen hatte.

Unterstützung komplexer Datenbindungen

Obwohl ich nicht dachte, dass ich mein neues Steuerelement auf einer vorhandenen Klasse aufbauen könnte, wollte ich mein Steuerelement dennoch so erstellen, dass es so wiederverwendbar wie möglich ist. Daher habe ich mich entschieden, eine zugrunde liegende Basisklasse zu erstellen, die die grundlegenden Arbeiten für ein komplexes datengebundenes Steuerelement kapselte. Ich habe diese Klasse erstellt und für meine erste Version dieses Steuerelements verwendet. Das Erstellen meiner Steuerung auf diese Weise funktionierte sehr gut, und ich konnte meine Basisklasse wiederverwenden, als ich ein GDI+-basiertes Listenfeld und mehrere andere Steuerelemente erstellt habe, was dazu führte, dass viel weniger Code erforderlich war, um jedes einzelne zu erstellen. Es stellt sich heraus, dass ich noch mehr Zeit hätte sparen können – obwohl es mir eingefallen ist (nachdem ich natürlich fertig war), dass, wenn dies eine so gute Idee war, die Windows Forms Programmierer selbst daran gedacht hätten, und sicher genug, dass die ListBox- und ComboBox-Steuerelemente auf einer gemeinsamen ListControl-Klasse basierten, die fast genau das war, was ich erstellt hatte. Meine Version funktioniert gut, aber seitdem habe ich meine Steuerelemente für die Verwendung dieser Klasse umgeschrieben, und ich werde sie auch für diesen Artikel verwenden.

Durch die Verwendung der ListControl-Klasse wird fast die gesamte Datenbindungsarbeit erledigt, was den Code, den ich schreiben muss, erheblich reduziert. In meinem Steuerelement füge ich eine neue Eigenschaft hinzu, um das Feld anzugeben, das den Pfad zum Bild enthält. Die Eigenschaften DataSource, DataMember, SelectedIndex, DisplayMember und ValueMember werden jedoch alle für mich bereitgestellt.

Ich muss weiterhin mit den Daten in meinem Steuerelement arbeiten (um die Elemente in meinen Zeichnungsroutinen für instance zu durchlaufen), aber die ListControl-Klasse macht dies einfach, indem sie mir einen instance der CurrencyManager-Klasse als Me.DataManager bereitstellt. Über dieses Objekt habe ich Zugriff auf die Liste der Elemente (Me.DataManager.List), die aktuelle Position in der Liste (Me.DataManager.Position) und eine PropertyDescriptorCollection-Klasse , mit der ich auf jedes Feld eines Listenelements (Me.DataManager.GetItemProperties) zugreifen kann.

Zeichnen der Bilder

Durch Überschreiben der OnPaint-Methode der Basisklasse kann ich die Zeichnung für dieses Steuerelement übernehmen, und in diesem Code muss ich meine Seite voll mit Miniaturansichten zeichnen. Bevor ich die Zeichnung tatsächlich machen kann, muss ich jedoch die Position jedes einzelnen Bilds (und des zugehörigen Texts), die zu verwendenden Farb- und Schriftartinformationen und die Anzahl von Zeilen und Spalten bestimmen, die ich gleichzeitig sichtbar haben sollte.

Bestimmen eines anpassbaren Layouts

Damit dieses Steuerelement nützlich ist, muss die Positionierung und Größenanpassung der Miniaturansichten konfigurierbar sein. Daher war es bei der Bestimmung meiner Zeichnungsroutine sehr hilfreich, in Variablen zu denken (siehe Abbildung 2).

Abbildung 2. Beim Zeichnen eines eigenen Steuerelements hilft es, das Layout zu bestimmen, bevor Sie mit dem Code beginnen.

Jede dieser Variablen kann über eine öffentliche Eigenschaft für das Steuerelement konfiguriert werden:

  • HorizontalSpacing (x)
  • VerticalSpacing (y)
  • ImageHeight (h)
  • ImageWidth (w)

Darüber hinaus kann die Darstellung des Steuerelements über die Standardeigenschaften ForeColor, BackColor und Font angepasst werden, auf die jeweils vom Grafikcode entsprechend verwiesen wird. Dies ist nicht automatisch, daher müssen Sie sicherstellen, dass Sie bei Ihrer Grafikarbeit auf diese Standardeigenschaften verweisen, wenn Sie möchten, dass sich Ihr Steuerelement wie erwartet verhält.

Berechnen der Anzahl von Zeilen und Spalten pro Seite

Da mehr Bilder vorhanden sein könnten, als auf eine einzelne Seite passen, muss ich nachverfolgen, welches Element sich derzeit oben links im Steuerelement befindet, und Elemente relativ zu diesem bestimmten Listenelement zeichnen. Die Bestimmung, wie viele Zeilen und Spalten auf meine Canvas passen, wird im Ereignis zum Ändern der Größe des Steuerelements durchgeführt, da diese Werte neu berechnet werden müssen, wenn das Steuerelement wächst oder verkleinert wird:

    Private Sub imageList_Resize(ByVal sender As Object, _
            ByVal e As System.EventArgs) Handles MyBase.Resize
        Dim new_rowsPerPage As Integer = (Me.Height - y) \ ((2 * y) + h)
        Dim new_colsPerPage As Integer = Me.Width \ ((2 * x) + w)
        If (new_rowsPerPage <> rowsPerPage) _
            OrElse (new_colsPerPage <> colsPerPage) Then
            rowsPerPage = new_rowsPerPage
            colsPerPage = new_colsPerPage
        End If
    End Sub

Um unnötige Arbeit und Flimmern zu vermeiden, benötigen Sie die minimale Menge an Steuerelement-Neuzeichnung, aber in diesem Fall erfordert jede Größenänderung eine Neuzeichnung. Manchmal können Sie aufgrund der Art Ihres UI-Designs nicht um eine Neuzeichnung vermeiden, wie dies bei diesem Steuerelement der Fall ist. Ich ziehe zwei Pfeile (einen oben und einen am unteren Rand des Steuerelements), um anzugeben, wenn weitere Elemente außerhalb des Bildschirms verfügbar sind. Um bei jeder Größenänderung eine Neubildung zu erzwingen, könnte ich einen Aufruf von Me.Invalidate innerhalb dieser Resize-Routine hinzufügen, aber Windows Forms stellt eine andere Methode durch die Verwendung von Steuerelementformatvorlagen bereit. Durch Hinzufügen von Aufrufen der SetStyle-Methode im Konstruktor (Neue Methode) unseres Steuerelements können wir steuern, wie es vom Windows Forms-Engine gezeichnet und aktualisiert wird. In diesem Fall erzwingt das Festlegen des ResizeRedraw-Stils eine Aktualisierung, wenn die Größe des Steuerelements geändert wird, aber ich werde auch den DoubleBuffer-Stil festlegen, da dies eine hervorragende Möglichkeit ist, Flimmern aus einem benutzerdefinierten gezeichneten Steuerelement zu entfernen:

Public Sub New()
    Me.SetStyle(ControlStyles.DoubleBuffer, True)
    Me.SetStyle(ControlStyles.ResizeRedraw, True)
    Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
    Me.SetStyle(ControlStyles.UserPaint, True)
End Sub

Hinweis Das Doppelte Puffern ist eine grafische Technik, bei der ein vollständiges Bild der Benutzeroberfläche in einen Puffer (z. B. ein Image-Objekt im Arbeitsspeicher) gezeichnet und dann als einzelnes Bild in das Fenster gezogen wird. Dies reduziert das Flimmern erheblich im Vergleich zur ausführung aller einzelnen Grafikbefehle direkt im Fenster nacheinander. Gemäß der .NET Framework Dokumentation für die ControlStyles-Optionen muss ich UserPaint und AllPaintingInWmPaint festlegen, um die vollen Vorteile der doppelten Pufferung zu nutzen.

Überschreiben von OnPaint

Die eigentliche Grafikarbeit wird durch Das Überschreiben von OnPaint erreicht, das ein Argumentobjekt übergeben wird, das ein Graphics-Objekt enthält, mit dem Sie auf den Canvas des Steuerelements zeichnen können. In meiner OnPaint-Routine durchläuft ich die Elemente in meiner Datenquelle, wobei ich die Zeilen- und Spaltenpositionen nachverfolge und jedes Element ziehe:

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    Dim myList As IList
    Dim gr As Graphics = e.Graphics
    gr.FillRectangle(New SolidBrush(Me.BackColor), e.ClipRectangle)
    gr.InterpolationMode = scalingMode
    gr.SmoothingMode = SmoothingMode.Default

    ControlPaint.DrawBorder3D(gr, Me.DisplayRectangle, m_borderStyle)

    If Me.DataManager Is Nothing Then
        myList = Nothing
    Else
        myList = Me.DataManager.List
    End If

    If Not myList Is Nothing Then 'if there is any data
        Dim itemCount As Integer
        itemCount = myList.Count

        Dim itemsDisplayed As Integer 'current position in the list
        itemsDisplayed = currentTopLeftItem

        Dim i, j As Integer 'loop indexes
        Dim height, width As Integer

        For i = 0 To rowsPerPage - 1
            For j = 0 To colsPerPage - 1
                If itemsDisplayed < itemCount Then
                    DrawOneItem(itemsDisplayed, i, j, gr)
                    itemsDisplayed += 1
                End If
            Next
        Next

        'draw page down / page up indicators
        Dim webdingsFont As New Font("Webdings", 20, _
            FontStyle.Regular, GraphicsUnit.Pixel)
        Dim textBrush As New SolidBrush(Me.ForeColor)

        If itemsDisplayed < itemCount - 1 Then
            'draw down arrow
            gr.DrawString("6", _
            webdingsFont, textBrush, 0, Me.Height - 24)
        End If

        If currentTopLeftItem > 0 Then
            'draw up arrow
            gr.DrawString("5", webdingsFont, textBrush, 0, 0)
        End If

    End If
End Sub

Der meiste Code in dieser Malroutine umfasst die Bestimmung der Position (Zeile und Spalte) jedes bestimmten Listenelements, und nur ein kleiner Teil des Codes verarbeitet die Zeichnung tatsächlich. Diese Routine wird viel sauberer gemacht, indem der Code zum Zeichnen eines einzelnen Elements in eine eigene Prozedur unterteilt wird:

Private Sub DrawOneItem(ByVal index As Integer, _
   ByVal row As Integer, _
   ByVal col As Integer, _
   ByVal gr As Graphics)
    Dim textFont As Font = Me.Font
    Dim textBrush As New SolidBrush(Me.ForeColor)

    Dim myStringFormat As StringFormat = New StringFormat()
    myStringFormat.Alignment = StringAlignment.Center
    myStringFormat.FormatFlags = StringFormatFlags.LineLimit

    Dim imageURL As String = GetListItemImage(index)
    If imageURL = "" Then imageURL = m_GenericImage

    If imageURL <> "" Then
        If IO.File.Exists(imageURL) Then
            Dim myNewImage As New Bitmap(imageURL)
            'scale image to fit into defined size
            With myNewImage
                If .Height > h Then
                    Height = h
                    Width = CInt((h / .Height) * .Width)
                Else
                    Height = .Height
                    Width = .Width
                End If
                If Width > w Then
                    Height = CInt((w / Width) * Height)
                    Width = w
                End If
            End With

            Dim imageRect _
                As New Rectangle((2 * x) + _
                  (col * ((2 * x) + w)) + ((w - Width) \ 2), _
                   (1 * y) + (row * ((2 * y) + h)) _ 
                   + ((h - Height) \ 2), _
                   Width, Height)
            gr.DrawImage(myNewImage, imageRect)
            Dim myNewPen As Pen
            If index = Me.DataManager.Position Then  'selected
                myNewPen = New Pen(Color.Yellow)
                myNewPen.Width = 4
            Else
                myNewPen = New Pen(Color.Black)
                myNewPen.Width = 1
            End If
            gr.DrawRectangle(myNewPen, imageRect)
        End If
    End If

    Dim textHeight As Integer = y * 2
    gr.DrawString(Me.GetItemText(Me.DataManager.List.Item(index)), _
        textFont, textBrush, _
        New RectangleF((x) + (col * ((2 * x) + w)), _
            2 + (1 * y) + h + (row * ((2 * y) + h)), _
            w + (2 * x), textHeight), myStringFormat)
End Sub

Da diese Routine aus dem Standard OnPaint-Code entfernt wurde, ist es einfacher, die tatsächliche GDI+-Arbeit zu besprechen. Wenn der Pfad für das Bild auf eine reale Datei verweist, wird zunächst ein neues Bitmap-Objekt mit diesem Pfad als Konstruktor erstellt, und dann wird das Bild selbst mit der DrawImage-Methode des Graphics-Objekts auf das Steuerelement gezeichnet. Bevor das eigentliche Bild gezeichnet wird, wird ein wenig funky Mathe gemacht, um das Bild proportional in seinen Zielraum zu skalieren.

Hinweis Ein Graphics-Objekt stellt eine Zeichnungsoberfläche dar, sodass dieselben Methoden in verschiedenen Situationen verwendet werden können, einschließlich der Erstellung eigener Bilddateien, z. B. Bitmaps oder JPEGs.

Der nächste Schritt beim Zeichnen eines Elements besteht darin, ein Rechteck um das Bild herum zu zeichnen, wobei Farbe und Linienstärke verwendet werden, um den Fokus anzugeben. Der Rahmen wird nach dem Zeichnen des Bilds so gezeichnet, dass es sich oben befindet, und kein Teil davon wird vom Bild verdeckt. Als Nächstes wird die Textzeichenfolge unter dem Bild mit der DrawString-Methode gezeichnet. Mithilfe der Überladung von DrawString , die ein Layoutrechteck akzeptiert, kann der Text automatisch nach Bedarf innerhalb des angegebenen Bereichs umschließen. DrawString wird auch ein StringFormat-Objekt übergeben, mit dem Sie die Details zum Zeichnen des Texts konfigurieren können, z. B. die Verwendung von Wortumbruch. In diesem Beispiel wird das StringFormat-Objekt mit dem LineLimit-Flag konfiguriert, wodurch verhindert wird, dass Text gezeichnet wird, wenn er teilweise abgeschnitten würde, sodass nur Text angezeigt wird, der vollständig in das Layoutrechteck passt.

Behandeln von Tastendrücken und Mausklicks

Ich muss die gesamte Navigation innerhalb dieses Steuerelements selbst verarbeiten, da es nicht auf einem vorhandenen Steuerelement wie einer ListView basiert, daher habe ich beschlossen, die folgenden Navigationsverhalten zu unterstützen:

  • Pfeiltasten, um sich zwischen den Bildern zu bewegen. Der Versuch, über den unteren oder oberen Rand des Steuerelements zu gelangen, hat die gleiche Auswirkung wie ein PageUp oder PageDown.
  • PageUp oder PageDown , um jedes Mal einen gesamten Bildschirm mit Informationen zu verschieben.
  • Wählen Sie ein einzelnes Element mit einem Mausklick aus. Keine Mehrfachauswahl.
  • Wenn Sie auf ein Bild doppelklicken oder ein Bild auswählen, währendSie die Eingabetaste drücken, wird ein spezielles ItemPicked-Ereignis ausgelöst.
  • Wenn Sie versuchen, über die Kanten des Steuerelements zu navigieren (jederzeit links oder rechts, nach oben oder unten, wenn keine weiteren Elemente in dieser Richtung verfügbar sind), wird ein anderes benutzerdefiniertes Ereignis ausgelöst, LeaveControl. Dadurch können Programmierer, die das Steuerelement in ihren Formularen verwenden, die Navigation von diesem Steuerelement zu anderen Steuerelementen im selben Formular steuern.

Behandeln von Tastendrucken

Der Code zur Unterstützung der Tastaturnavigation ist ziemlich einfach. Es umfasst nur ein wenig Mathematik, um zu bestimmen, welche Zeile und Spalte derzeit ausgewählt ist, und alles wird in eine Routine namens KeyPressed gekapselt (die vom KeyDown-Ereignis des -Steuerelements aufgerufen wird):

Private Sub imageList_KeyDown(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.KeyEventArgs) _
        Handles MyBase.KeyDown
    KeyPressed(e.KeyCode)
End Sub

Private Sub KeyPressed(ByVal Key As System.Windows.Forms.Keys)
    Try
        Dim m_oldTopItem As Integer = currentTopLeftItem
        Dim m_oldSelectedItem As Integer = Me.DataManager.Position

        Dim newPosition As Integer = m_oldSelectedItem
        Dim selectedRow As Integer
        Dim selectedColumn As Integer

        selectedRow = System.Math.Floor( _
         (m_oldSelectedItem - currentTopLeftItem) / colsPerPage)
        selectedColumn = (m_oldSelectedItem - currentTopLeftItem) _
         Mod colsPerPage

        If Not Me.DataManager.List Is Nothing Then
            Select Case Key
                Case Keys.Up
                    If newPosition >= colsPerPage Then
                     newPosition -= colsPerPage
                    End If
                Case Keys.Down
                    If Me.DataManager.Count - colsPerPage > _
                        newPosition Then
                        newPosition += colsPerPage
                    End If
                Case Keys.Left
                    If selectedColumn = 0 Then
                        RaiseEvent LeaveControl(Direction.Left)
                    Else
                        newPosition -= 1
                    End If
                Case Keys.Right
                    If selectedColumn = (colsPerPage - 1) Then
                        RaiseEvent LeaveControl(Direction.Right)
                    Else
                        newPosition += 1
                    End If
                Case Keys.PageDown
                    If newPosition < Me.DataManager.Count Then
                        newPosition += (rowsPerPage * colsPerPage)
                        If newPosition >= Me.DataManager.Count Then
                            newPosition = Me.DataManager.Count - 1
                        End If
                    Else
                        RaiseEvent LeaveControl(Direction.Down)
                    End If
                Case Keys.PageUp
                    If newPosition > 0 Then
                        newPosition -= (rowsPerPage * colsPerPage)
                        If newPosition < 0 Then
                            newPosition = 0
                        End If
                    Else
                        RaiseEvent LeaveControl(Direction.Down)
                    End If

                Case Keys.Enter, Keys.Return
                    RaiseEvent ItemChosen(newPosition)
            End Select

            If newPosition < 0 Then newPosition = 0
            If newPosition >= Me.DataManager.Count Then
                newPosition = Me.DataManager.Count - 1
            End If

            If newPosition <> m_oldSelectedItem Then
                Me.DataManager.Position = newPosition
            End If
        End If
    Catch except As Exception
        Debug.WriteLine(except)
    End Try
End Sub

Unterstützung der Maus

Damit das Steuerelement für die Miniaturansicht gut funktioniert, muss die Maus nur zwei Ereignishandler schreiben: ein öffentliches Ereignis (ItemChosen, das auch von KeyPressed aufgerufen wird, wenn der Benutzer die EINGABETASTE oder die Rückgabe drückt), das ausgelöst werden kann, und eine Hilfsfunktion zum Verarbeiten von Treffertests:

Private Function HitTest(ByVal loc As Point) As Integer
    Dim i As Integer
    Dim found As Boolean = False
    i = 0
    Do While i < Me.DataManager.Count And Not found
        If GetItemRect(i).Contains(loc) Then
            found = True
        Else
            i += 1
        End If
    Loop
    If found Then
        Return i
    Else
        Return -1
    End If
End Function

Private Sub dbThumbnailView_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles MyBase.Click
    Dim mouseLoc As Point = Me.PointToClient(Me.MousePosition())
    Dim itemHit As Integer = HitTest(mouseLoc)
    If itemHit <> -1 Then
        Me.DataManager.Position = itemHit
    End If
End Sub

Private Sub dbThumbnailView_DoubleClick(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles MyBase.DoubleClick
    Dim mouseLoc As Point = Me.PointToClient(Me.MousePosition())
    Dim itemHit As Integer = HitTest(mouseLoc)
    If itemHit <> -1 Then
        RaiseEvent ItemChosen(itemHit)
    End If
End Sub

Beachten Sie, dass ich nicht explizit ein Click-Ereignis innerhalb des dbThumbnailView_Click-Ereignishandlers auslösen muss. das Steuerelement löst automatisch ein Standardmäßiges Click-Ereignis aus, das vom Benutzer des Steuerelements verarbeitet werden kann. Für jeden Doppelklick wird ebenfalls ein Double-Click-Ereignis ausgelöst, aber das ItemChosen-Ereignis tritt nur auf, wenn auf ein tatsächliches Element doppelt geklickt wird.

Angeben eines Standardereignisses

Während ich mich mit den Ereignissen befasse, dachte ich, ich würde einen dieser "letzten Schliffe" Erwähnung, die Ihre Steuerung einfach zu bedienen machen. Wenn in der Entwurfsansicht (in der Visual Studio .NET-IDE) auf ihr Steuerelement doppelt geklickt wird, wird der Programmierer automatisch zum Ereignishandler für eines der Ereignisse Ihres Objekts weitergeleitet. Diese IDE-Funktion ist im Allgemeinen sehr nützlich, funktioniert aber nur gut, wenn der Programmierer zum gängigsten Ereignishandler führt. Durch Hinzufügen des DefaultEvent-Attributs zur Klasse Ihres Steuerelements können Sie angeben, welches Ereignis von der IDE ausgewählt wird, wenn ein Programmierer in der Entwurfsansicht auf ihr Steuerelement doppelt klickt:

<DefaultEvent("ItemChosen")> _
Public Class dbThumbnailView
    Inherits ListControl

Ohne dieses DefaultEvent-Attribut verwendet die IDE das DefaultEvent-Attribut , das von Ihrer Basisklasse oder von anderen Klassen weiter oben in der Vererbungskette definiert wurde. Im Fall meines Miniaturansichtssteuerelements ist das Click-Ereignis das Standardereignis, da es das Standardereignis der Control-Klasse ist und meine Basis (ListControl) von Control erbt.

Einige Probleme und Hinweise

Mein ursprüngliches Steuerelement war nicht für die Verwendung mit einer Tastatur oder Maus konzipiert, daher habe ich keine Bildlaufleiste eingeschlossen. Nur die visuellen Indikatoren (die Pfeile in der unteren und oberen linken Ecke) waren ausreichend, aber in einer Umgebung, in der eine Maus verfügbar ist, möchten Sie möglicherweise eine Scrollleiste hinzufügen. Ich habe auch in meiner ersten Version dieses Steuerelements keinen Rahmen verwendet oder die Verwendung der Maus unterstützt, aber ich habe beide Features der in diesem Artikel verfügbaren Version hinzugefügt.

Beispiel

Dieses Steuerelement wird als Teil desselben Beispiels wie die datengebundene TreeView veranschaulicht und wird verwendet, um alle Bücher für einen bestimmten Autor oder Herausgeber anzuzeigen (siehe Abbildung 3). Der Code für diese Beispielanwendung ist im Download für diesen Artikel enthalten.

Abbildung 3. Diese Beispielanwendung wird verwendet, um das Miniaturansichtssteuerelement und das datengebundene Struktursteuerelement aus Beispiel 3 zu veranschaulichen.

Zusammenfassung

Manchmal müssen Sie einfach genau das erstellen, was Sie benötigen. Bei der Steuerungsentwicklung müssen Sie Ihr eigenes Steuerelement vollständig von Grund auf neu erstellen, einschließlich des Programmierens Ihrer eigenen Grafikarbeit. Wenn Sie ein komplexes datengebundenes Steuerelement wie ein Grid oder eine Form von ListBox erstellen, können Sie sich viel Arbeit sparen, indem Sie Ihr Steuerelement auf der ListControl-Klasse basieren, wie in diesem Artikel beschrieben.