Registreren   Inloggen   Zoeken:  Nu zoeken
donderdag 24 juli 2008
     
Visual Basic Magazine 2004-08 | augustus 2004

Inherit Controls met Visual Basic .NET

Een van de krachtigste elementen binnen Visual Basic 2003 vind ik wel de mogelijkheid om objecten te overerven; ook wel inheritance genoemd. U kunt niet alleen uw eigen classes en business objecten overerven, maar ook standaard Visual Basic .NET controls. Hierdoor bent u in staat om op eenvoudige wijze bestaande control op een zodanige wijze uit te breiden of aan te passen, dat het voor uw specifieke situatie geschikt wordt.

In dit artikel gaan we de standaard ListBox uitbreiden met de mogelijkheid om ook afbeeldingen in de lijst te tonen. Ik ben mij er van bewust dat op het Internet wel meer van deze controls te downloaden zijn, maar het gaat in dit artikel hoofdzakelijk om de werkwijze. Het doel is dat u deze techniek weet toe te passen, om zelf wel die unieke controls te kunnen bouwen, die u op het Internet niet kunt vinden.

Met Visual Basic .NET kunt u op twee manieren uw eigen controls bouwen.

  • op basis van een 'Class Library' of
  • op basis van een 'Windows Control Library'.

De keuze hangt af van welk type control u wilt gaan maken. Als u een bestaand control wil gaan aanpassen, dan kunt u het beste kiezen voor 'Class Library'. Wilt u echter meerdere controls combineren in één control, dan kiest u voor 'Windows Control Library'. U maakt dan een zogenaamde usercontrol.

Aangezien ik in dit artikel een ListBox wil gaan uitbreiden met extra functionaliteiten, kies ik voor het eerste. Tevens voeg ik altijd direct nog een project toe aan de solution, namelijk een 'Windows Application', zodat ik mijn control makkelijk kan testen tijdens het ontwikkelen. Wanneer het control eenmaal gecompileerd is tot een DLL, kunt u het op een eenvoudige wijze toevoegen in alle andere .NET projecten waaraan u werkt. De taal op zich is dan niet meer belangrijk, u kunt het namelijk gebruiken in Visual Basic .NET of C#.

Afbeelding 1

Afbeelding 1. Nieuw project starten

Alle controls binnen Visual Basic .NET, zoals bijvoorbeeld de TextBox, ListBox en zelfs een Form zijn overge‰rfd van System.Control. Er zijn dus eigenlijk niet twee manieren om een eigen control te bouwen, maar drie. U kunt namelijk overerven van:

  • System.Windows.Forms.UserControl
    (Windows Control Library);
  • System.Windows.Forms.Control;
  • System.Windows.Forms.ListBox

In het geval van de laatste kan natuurlijk elk ander Windows Control worden gekozen. Omdat wij echter een ListBox willen uitbreiden kiezen we voor het laatste.

De VBGListBox control zal worden 'derived' of 'afgeleid' van de standaard ListBox. Een afgeleide class kan de pro- perties, methods en events van baseclass uitbreiden door deze te 'overriden' of 'shadowen'. Normaal gesproken zijn alle classes over te erven, tenzij ze zijn gemarkeerd als NotInheritable.


 Public Class VBGListBox

    Inherits System.Windows.Forms.ListBox
 End Class

Codevoorbeeld 1. VBGListBox

Wanneer u bovenstaande code compileert, dan heeft u reeds een eigen ListBox gebouwd.

Namespaces
Namespaces maken een integraal onderdeel uit van het Microsoft .NET Framework. Om uw control niet te laten verwarren met de originele ListBox, kunt u het natuurlijk een andere naam geven, maar ook binnen een andere namespace bewaren. Ik geef er de voorkeur aan, om binnen mijn codevensters altijd de volledige namespace te gebruiken. Ik dien daarom wel in de het propertiesscherm van mijn project de 'Root namespace' te wissen. Ook kunnen we in dit scherm de naam van de assembly (DLL) wijzigen. Omdat onze DLL slechts één control, bevat, namelijk de ListBox, noem ik de DLL:

   VBG.Windows.Forms.ListBox

De naam van de namespace moet ik dan natuurlijk nog wel toevoegen aan mijn code.

Afbeelding 2

Afbeelding 2. Properties van onze DLL


Let's code
Onze VBGListBox is op dit moment nog niets meer of minder dan de bestaande ListBox. We gaan het nu uitbreiden met nieuwe functionaliteit. Allereerst voegen we een constructor toe. In deze procedure wordt de DrawMode ingesteld op OwnerDrawFixed; wat inhoudt dat we aangeven dat wijzelf gaan zorgdragen voor het tekenen van de elementen, maar ook dat deze elementen dezelfde grootte hebben. Ook wordt een importstatement voor System.Windows.Forms toegevoegd. Dat scheelt ons in het verloop nogal wat typewerk.


 Public Sub New()
    MyBase.New()
    MyBase.DrawMode = DrawMode.OwnerDrawFixed
 End Sub
Public Shadows ReadOnly Property DrawMode() As DrawMode Get Return MyBase.DrawMode End Get End Property


Codevoorbeeld 2. Constructor en shadowed property

Interessant is de verwijzing naar MyBase. Wellicht ken u het keyword Me. Me verwijst naar zichzelf, dus in ons geval naar de instantie van de class VBGListBox. Deze class kent geen property DrawMode; de class waar het van derived is echter wel. Aangezien onze baseclass een ListBox is, verwijzen we naar deze class met behulp van het keyword MyBase.

Wanneer we nu onze DLL compileren, het control zouden toevoegen aan ons testproject en zouden plaatsen op een Form, dan zouden we nog steeds de DrawMode kunnen aanpassen. Dit is iets wat we niet willen, daarom moeten we deze property overriden of shadowen als een Read Only Property. Met het keyword Shadow geven we aan dat een property (method) met dezelfde naam onzichtbaar moet worden gemaakt en in plaats daarvan deze procedure moet worden gebruikt. Deze property implementeren we dus als ReadOnly, waardoor we als gebruiker van het control deze waarde niet meer kunnen instellen. Zie afbeelding 3.

Afbeelding 3

Afbeelding 3. ReadOnly Property DrawMode

VBGListBox toevoegen aan Test Project
U kunt het control testen door het toe te voegen aan een testproject. Dit kan pas nadat u het gecompileerd hebt tot een DLL. Voeg een control toe door in de Toolbox met uw rechtermuisknop te klikken en te kiezen voor 'Add/Remove items'. Gebruik de knop 'Browse' om de DLL te selecteren die zich in de \Bin map van het VBGListBoxProject bevindt.

 

Afbeelding 4

Afbeelding 4. Control toevoegen aan Toolbox

VBGListItem en VBGListItems
De kracht van onze ListBox zit hem in het feit dat wij een 'ander soort' item willen toevoegen aan de lijst. Een item die het in ieder geval mogelijk maakt om ook afbeeldingen op te nemen. Zowel het object voor dit item, als de collectie van die items zullen we zelf moeten definiëren. Daarna zullen we de gewone .Items collectie moeten vervangen door onze collectie: VBGListItems.

VBGListItem
Het maken van een VBGListItem is principe eenvoudig. We voegen aan de huidige class VBGListBox een nieuwe class toe met als naam VBGListItem. In dit voorbeeld worden omwille van de duidelijkheid de properties geimplementeerd als Public variabelen, in plaats van Get/Set procedures.


 Public Class VBGListItem
     Public Text As String
     Public Bitmap As Bitmap
 End Class 

Codevoorbeeld 3. VBGListItem

Deze class kunt u altijd uitbreiden met extra properties wanneer u deze nodig mocht hebben. Wanneer in de VBGListBox bijvoorbeeld records uit een database moet worden getoond, dan zou u bijvoorbeeld nog een property RecordID kunnen toevoegen, om elk item direct te kunnen identificeren. Dit object representeert dus een regel (item) in onze VBGListBox.

VBGListItems
Een collectie van VBGListItem objecten wordt opgeslagen in het VBGListItems object. Dit object wordt afgeleid van het CollectionBase object. Hierdoor wordt het maken van dit object erg eenvoudig.


 Public Class VBGListItems
Inherits CollectionBase
Public Function Add(ByVal VBGListItem As VBGListItem) As _ Int32 Return List.Add(VBGListItem) End Function
Public Sub Insert(ByVal Index As Int32, ByVal VBGListItem _ As VBGListItem) List.Insert(Index, VBGListItem) End Sub
Public Shadows Function IndexOf(ByVal VBGListItem As _ VBGListItem) As Int32 Return list.IndexOf(VBGListItem) End Function
Public Shadows Function Contains(ByVal VBGListItem As _ VBGListItem) As Boolean Return List.Contains(VBGListItem) End Function
Public Function Remove(ByVal VBGListItem As VBGListItem) List.Remove(VBGListItem) End Function
Default Public Property Item(ByVal Index As Int32) As _ VBGListItem Get Return List.Item(Index) End Get Set(ByVal Value As VBGListItem) List.Item(Index) = Value End Set End Property


Codevoorbeeld 4. VBGListItems

U ziet dat bestaande methods op het standaard List-object (CollectionBase) weer geshadowed worden, om om te werken met ons eigen VBGListItem object.

We hebben nu het VBGListItem en de collectie ervan, het VBGListItems object gedefinieerd. De volgende stap is om de functionaliteit van de standaard .Items property van de ListBox te vervangen door onze functionaliteit. We moeten immers onze VBGListItem-objecten kunnen toevoegen in plaatst van alleen tekstregels.

We gaan daarom in onze VBGListBox class een nieuwe property toevoegen, die de oorspronkelijke .Items property gaat overschrijven. U ziet dat wederom het keyword Shadow wordt gebruikt, omdat we een bestaande property willen overschrijven.


 Private WithEvents mobjItems As New VBGListItems()
    
 Public Shadows Property Items() As VBGListItems
   Get
       Return mobjItems
   End Get
   Set(ByVal Value As VBGListItems)
       mobjItems = Value
   End Set
 End Property 

Codevoorbeeld 5. Onze .Items property

Wanneer u op dit moment het control zou gaan testen, zult u merken dat u aan de VBGListBox inderdaad items kan toevoegen. Zelfs de propertywindow binnen Visual Studio .NET 'snapt' dat we met een ander object te maken hebben dan een simpele tekstregel. Of dit wenselijk is? Hier kom ik later op terug.

De volgende stap is het toevoegen en afhandelen van events. Op de VBGListBox worden vier events geimplementeerd:

  • Cleared()
  • ItemInserted()
  • ItemChanged()
  • ItemRemoved()

Deze vier event moeten zowel in de VBGListItems-class, als in de VBGListBox worden gedeclareerd en afgehandeld. In Listing 6 ziet u hoe de code er voor het ItemInserted() event eruit ziet. De andere events werken analoog hieraan.


 Public Event ItemInserted(ByVal Index As Integer, _
                           ByVal VBGListItem As VBGListItem)
Protected Overrides Sub OnInsertComplete(ByVal Index As Int32, _ ByVal Value As Object) RaiseEvent ItemInserted(Index, Value) End Sub

Codevoorbeeld 6a. ItemInserted() Event - VBGListItems 


 Public Event ItemInserted(ByVal Index As Integer, _
                           ByVal VBGListItem As VBGListItem)
 Private Sub mobjItems_ItemInserted(ByVal Index As Integer, _
                             ByVal VBGListItem As VBGListItem) _
        Handles mobjItems.ItemInserted
  MyBase.Items.Insert(Index, VBGListItem)
  RaiseEvent ItemInserted(Index, VBGListItem)
End Sub

Codevoorbeeld 6b. ItemInsert() Event - VBGListBox

Binnen de collectionclass wordt het OnInsertComplete event afgevangen. Dit is een standaard event van de CollectionBase class. Feitelijk doen we vervolgens niets meer of minder dan dit event als het ware doorsturen, door het door ons gedeclareerde event opnieuw te 'raisen'. Doordat we het object mobjItems met WithEvents hebben gedeclareerd (Listing 5), kunnen we het in onze VBGListBox weer afvangen. In dit event (Listing 6b) wordt het object daadwerkelijk aan de ListBox toegevoegd en vervolgens wordt het event nogmaals geraised, zodat het eventueel binnen ons Testproject ook afgehandeld kan worden.


Tekenen afhandelen
We kunnen nu items toevoegen, maar ze worden nog niet getoond in het control. Dit heeft te maken met het feit dat we reeds eerder hebben aangegeven (middels de DrawMode property) dat we dit zelf voor onze rekening zouden nemen. Om dit voor elkaar te krijgen, gaan we het MyBase.DrawItem event overschrijven. Dit doen we door een procedure aan te maken, met dezelfde parameters als het event en deze met het keyword Handles aan het MyBase.DrawItem event te koppelen.


 Private Sub VBGListBoxDrawItem(ByVal sender As Object, _
                                ByVal e As DrawItemEventArgs) _
                                Handles MyBase.DrawItem
' Bepaal of we met een geldige regel te maken hebben If (e.Index = -1) Or (e.Index > MyBase.Items.Count - 1) Then e.DrawBackground() Exit Sub End If
' Bepaal of we geen geselecteerd item moeten tekenen... If Not (e.State And DrawItemState.Selected) Then e.DrawBackground() End If
' Bepaal of we een gewoon item of geselecteerd item... Dim objTextBrush As Brush If e.State And DrawItemState.Selected Then objTextBrush = SystemBrushes.HighlightText Else objTextBrush = SystemBrushes.ControlText End If
' Bepaal of we een afbeelding moeten tekenen If Me.Items(e.Index).Bitmap Is Nothing Then ' Nee dus, alleen tekst. e.Graphics.DrawString(Me.Items(e.Index).Text, Me.Font, _ objTextBrush, e.Bounds.X, e.Bounds.Y) Else ' Ja dus... teken de afbeelding en tekst. e.Graphics.DrawImage(Me.Items(e.Index).Bitmap, e.Bounds.X, _ e.Bounds.Y, Me.Items(e.Index).Bitmap.Width, e.Bounds.Height) e.Graphics.DrawString(Me.Items(e.Index).Text, Me.Font, _ objTextBrush, e.Bounds.X + Me.Items(e.Index).Bitmap.Width, _ e.Bounds.Y) End If
End Sub

Codevoorbeeld 7. MyBase.DrawItem

Het voert te ver om de code in deze procedure uitvoerig te bespreken. Wat van belang is dat uiteindelijk de e.Graphics.DrawImage() en de e.Graphics.DrawText() methods worden aangeroepen voor het daadwerkelijke tekenen van de items. De parameters zijn in de online help terug te vinden en hebben met name betrekking op het juist positioneren van de afbeelding en tekst. Me.Items(e.Index) is de referentie naar het huidige VBGListItem-object. U ziet dus ook 'onze' properties Text en Bitmap terug komen.

Onze VBGListBox is nu klaar voor gebruik. We moeten het eerst compileren. Nu kunnen we het binnen ons TestProject toevoegen (Afbeelding 4) en op een Form plaatsen. Tevens plaatsen we op het form een PictureBox, hernoemen deze tot picVBGListBox en stellen de .Image property in op een pictogram van 16x16 pixels. Vervolgens kunnen we met de volgende code een item aan onze VBGListBox toevoegen.


 Dim objVBGListItem As New VBG.Windows.Forms.VBGListItem
objVBGListItem.Text = "Visual Basic Groep" objVBGListItem.Bitmap = picVBGListBox.Image
VBGListBox.Items.Add(objVBGListItem)

Codevoorbeeld 8. VBGListBox in actie

Afbeelding 5

Afbeelding 5. VBGListBox in actie


Perfectioneren
Onze VBGListBox doet nu zijn werk, maar er zijn nog wel een paar aandachtspunten waardoor we onze code kunnen perfectioneren.

Toolboxbitmap
Het zal u wellicht zijn opgevallen dat wanneer u de VBGListBox hebt toegevoegd aan de Toolbox dat er een standaard pictogram wordt getoond (namelijk een tandwieletje). Hoewel het toevoegen van zo'n toolboxbitmap niet vreselijk ingewikkeld is, zijn er een paar aandachtspunten waarop u dient te letten.

  • De afbeelding moet een Bitmap (*.bmp) met maximaal 16 kleuren en een afmeting van 16x16 pixels;
  • De kleur die de pixel linksonder heeft zal ge- bruikt worden als transparante kleur;
  • De afbeelding dient aan het project te worden toegevoegd. Let op: de bestandsnaam moet de volledige namespace bevatten. In ons geval noemen we het bestand VBGListBox.bmp dus: VBG.Windows.Forms.VBGListBox.bmp. Ook moet vervolgens in de property windows van Visual Basic .NET dit bestand gemarkeerd worden als 'Build Action = Embedded Resource'.
  • De classdefefinitie laat men voorafgaan met het volgende attribuut.

    <ToolboxBitmap(GetType(VBGListBox), "VBGListBox.bmp")> _

Afbeelding 6

Afbeelding 6. ToolboxBitmap

Properties verbergen
We hebben het reeds eerder gehad over het feit dat de IDE van Visual Basic .NET al op de hoogte is van het feit dat we te maken hebben met een andere type Item-object. Nu zouden we code kunnen gaan schrijven dat we zelfs deze collectie al 'at design time' kunnen gaan vullen (en opslaan!), maar we zouden er ook voor kunnen kiezen om deze property simpelweg voor de designer te verbergen. Dit kunnen we doen door het toevoegen van het attribuut Browsable(False) direct voor de procedure. Wel moet er nog een import statement komen naar System.ComponentModel.

Afbeelding 7

Afbeelding 7. Verberg een property

SelectedItem & Overloading van Add() Method
Het control kent nu SelectedItem property meer. Dit is toch wel handig, omdat men dan niet op basis van de SelectedIndex het object hoeft op te zoeken. Aangezien deze property wel op een standaard ListBox zit, zullen we deze moeten shadowen.


 Public Shadows Property SelectedItem() As VBGListItem
    Get
        Return MyBase.SelectedItem
    End Get
    Set(ByVal Value As VBGListItem)
        MyBase.SelectedItem = Value
    End Set
 End Property 
Codevoorbeeld 9. SelectedItem 

Het toevoegen van een VBGListItem kan ook nog vereenvoudigd worden. Op dit moment moet men een VBGListItem object aanmaken, de properties zetten en vervolgens toevoegen met behulp van de Add() method. Het zou makkelijker zijn om direct de Add() method te kunnen aanroepen met twee parameters, Text en Bitmap. We kunnen deze functionaliteit bereiken door in de VBGListItems() class de Add() method te overloaden. Simpelweg komt het er op neer dat we een nieuwe Add() procedure toevoegen, met andere parameters. Doordat deze parameters afwijken van de eerste method, weet de compiler welke procedure hij moet gebruiken.

Afbeelding 8


Afbeelding 8. Overloaded Add() method


  Public Function Add(ByVal Text As String, _
                      ByVal Bitmap As Bitmap) As Int32
Dim objVBGListItem As New VBGListItem
objVBGListItem.Text = Text objVBGListItem.Bitmap = Bitmap
Return List.Add(objVBGListItem)
End Function

Codevoorbeeld 10a. VBGListItems - Add() method 


 ' Gebruik overloaded Add() method
 VBGListBox.Items.Add("http://www.vbgroup.nl", Me.Icon.ToBitmap)
Codevoorbeeld 10b. VBGListBoxTest - Add() method

Afsluiting
Het aanpassen van standaard controls binnen Visual Basic .NET is goed te doen en biedt legio mogelijkheden. Wel moet u van te voren goed nadenken wat de uiteindelijke functionaliteit moet zijn. Wanneer dit helder is, zijn in principe ook de properties en methods bekend. Op dat moment kan in feite al met de implementatie begonnen worden.

Succes!

André

Advertenties
Leer ook Visual Basic!

Visual Basic 2005 - de Basis | André Obelink

Bestanden bij artikel
 


Microsoft Certified Solution Developer

Microsoft Certified Professional

Microsoft Most Valuable Professional

VP Speakers Bureau - INETA Europe

Brainbench Certified Master Visual Basic 6.0 Programmer

Gebruiksovereenkomst   Privacybeleid   Copyright 2005 André Obelink