Ü vbg.vbnet.immediate | 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 ùw 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’.
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 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#.
De VBGListBox control zal worden ‘derived’ of ‘afgeleid’ van de standaard ListBox. Een afgeleide class kan de properties, 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.
Afbeelding 1 | Nieuw project starten
| VB Magazine Online | 2004 – 01/08|
Listing 1 | VBGListBox Public Class VBGListBox Inherits System.Windows.Forms.ListBox End Class
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.
1/6
Ü vbg.vbnet.immediate | Inherit Controls met Visual Basic .NET |
Afbeelding 3 | ReadOnly Property DrawMode
VBGListBox toevoegen aan Test Project
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.
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.
Listing 2 | Constructor en shadowed property. Public Sub New() MyBase.New() MyBase.DrawMode = DrawMode.OwnerDrawFixed
Afbeelding 4 | Control toevoegen aan Toolbox
End Sub Public Shadows ReadOnly Property DrawMode() As DrawMode Get Return MyBase.DrawMode End Get End 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.
| VB Magazine Online | 2004 – 01/08|
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 geïmplementeerd als Public variabelen, in plaats van Get/Set procedures. Listing 3 | VBGListItem Public Class VBGListItem Public Text As String Public Bitmap As Bitmap End Class
2/6
Ü vbg.vbnet.immediate | Inherit Controls met Visual Basic .NET |
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. Listing 4 | VBGListItems Public Class VBGListItems
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. Listing 5 | Onze .Items property 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
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
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 geïmplementeerd: • 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. Listing 6a | ItemInserted() Event - VBGListItems 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
Listing 6b | ItemInsert() Event - VBGListBox 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.
| VB Magazine Online | 2004 – 01/08|
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
3/6
Ü vbg.vbnet.immediate | Inherit Controls met Visual Basic .NET |
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. Listing 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. Listing 7 | VBGListBox in actie Dim objVBGListItem As New VBG.Windows.Forms.VBGListItem objVBGListItem.Text = "Visual Basic Groep" objVBGListItem.Bitmap = picVBGListBox.Image VBGListBox.Items.Add(objVBGListItem)
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
| VB Magazine Online | 2004 – 01/08|
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 gebruikt worden als transparante kleur;
4/6
Ü vbg.vbnet.immediate | Inherit Controls met Visual Basic .NET |
•
•
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.
_
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.
Listing 8 | SelectedItem Public Shadows Property SelectedItem() As VBGListItem Get Return MyBase.SelectedItem End Get Set(ByVal Value As VBGListItem) MyBase.SelectedItem = Value End Set End Property
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 | Overloaded Add() method
Listing 9a | VBGListItems – 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
Listing 9b | VBGListBoxTest – Add() method ' Gebruik overloaded Add() method VBGListBox.Items.Add("http://www.vbgroup.nl", Me.Icon.ToBitmap)
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.
| VB Magazine Online | 2004 – 01/08|
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.
5/6
Ü vbg.vbnet.immediate | Inherit Controls met Visual Basic .NET |
Wie is André Obelink? André Obelink (1969) is werkzaam als technical manager en consultant bij AcouSoft Informatisering B.V. AcouSoft is marktleider in Nederland op het gebied van software voor de audicienbranche. Daarnaast bouwt AcouSoft maatwerkapplicaties en ondersteunt zij andere bedrijven bij allerhande softwaretrajecten in VB6/VB.NET/MS Access 97/XP en SQL-Server 7/2000. André is een MCSD, programmeert in Visual Basic sinds 1991 (ja… versie 1.0 dus..) en behoort tot een van de oprichters van de Visual Basic Groep. Als hij niet programmeert, doceert, leest, schrijft of droomt over programmeren, brengt hij graag zijn tijd door met zijn vrouw en twee kinderen. U kunt hem bereiken via [email protected] © 2004 - Copyright Visual Basic Groep. Dit artikel is auteursrechtelijk beschermd. Afdrukken voor eigen gebruik is toegestaan. http://www.vbgroup.nl
| VB Magazine Online | 2004 – 01/08|
6/6