sessie 5 - Live demo - Excel Experience Day

Commentaren

Transcriptie

sessie 5 - Live demo - Excel Experience Day
Een invoegtoepassing maken in Excel
1. Inleiding
Excel is een krachtige applicatie met duizenden opties en functies om rekenmodellen, rapporten en
analyses te bouwen. Het komt toch nog regelmatig voor dat je in je dagelijkse werk wat functionaliteit
zou kunnen gebruiken die Excel niet biedt.
Met behulp van VBA is het mogelijk om functionaliteit aan Excel toe te voegen. In dit artikel laat ik zien
hoe je een kleine invoegtoepassing maakt in Excel VBA. Het artikel bespreekt alle benodigde stappen die
ik nam om de invoegtoepassing te bouwen. De principes die ik tijdens het bouwen heb gebruikt helpen
jou hopelijk bij het maken van je eigen tool!
2. specificatie
Een heel belangrijke stap bij het maken van software is het bepalen wat de software moet kunnen; de
specificatie. Laten we eens proberen een specificatie op te stellen.
2.1.
Doel
De primaire doelstellingen van de invoegtoepassing die ik voor ogen heb zijn:
 Een eenvoudige methode maken zodat de gebruiker aan zijn Excel bestand een inhoudsopgave
toe kan voegen (of bijwerken);
 Een hulpmiddel op het lint om het navigeren tussen werkbladen makkelijker te maken.
Zoals je je kunt voorstellen is meer detail nodig voordat we ons in het programmeerwerk onder kunnen
dompelen. We hebben nog meer specificaties nodig.
2.2.
Meer gedetailleerde specificatie
2.2.1. Werking van de inhoudsopgave
Ik preciseer hier nader wat de invoegtoepassing zal doen
 De Inhoudsopgave zal geplaatst worden op een tabblad genaamd Inhoud, beginnende in cel C2.
 De tabel zal worden “Opgemaakt als tabel”
 De tabel zal in kolom C een lijst met alle werkbladnamen bevatten, met in kolom D een directe
snelkoppeling naar cel A1 van dat werkblad. Kolom E geeft de gebruiker om naast het werkblad
een opmerking in te voegen.
 Deze opmerkingen zullen behouden blijven bij het vernieuwen van de Inhoudsopgave, zodanig
dat –op basis van de tabnaam– de opmerking bij het juiste tabblad blijft staan.
2.2.2. Het lint
De invoegtoepassing zal een aangepaste tab op het lint maken met daarin één groep met daarop:
 Een vervolgkeuzelijst die alle werkbladen zal bevatten van de actieve werkmap;
 Een knop om de inhoudsopgave te maken of te vernieuwen
2.2.3. Andere functies


Zodra de gebruiker een andere werkmap selecteert wordt de lijst met werkbladen in de
vervolgkeuzelijst automatisch bijgewerkt;
Als een ander werkblad wordt geselecteerd wordt deze op de vervolgkeuzelijst weergegeven.
2.3.
Is dat alles?
In “echte” softwareprojecten zijn er natuurlijk nog allerlei andere vragen die ook nog beantwoord
moeten worden. In willekeurige volgorde (niet pretenderend dat deze lijst volledig is):
 Zorg voor een sponsor, je moet tenslotte je rekeningen betalen;
 Doe marktonderzoek (als je tenminste van plan bent om die nieuwe invoegtoepassing te
verkopen. Het zou toch leuk zijn als er ook een markt voor bestaat;)
 Google. Twitter. Vraag het aan de buurman. Zorg er in ieder geval voor dat je niet iets gaat
bouwen dat al bestaat. De kans is aanzienlijk dat iemand jou voorging met dit idee en misschien
kan je zijn invoegtoepassing gewoon gebruiken;
 Planning: Wanneer moet het klaar zijn?
2.4.
Andere dingen om aan te denken
En dan hier nog een lijstje met dingen die je nodig zou kunnen hebben die eigenlijk weinig te maken
hebben met de basisfunctionaliteit van je invoegtoepassing:
 Ontwerpen van de gebruikersinterface;
 Internationalisatie (vertalen in andere talen)
 Bugs waar je omheen zal moeten werken;
 Installatie;
 Activatie (Mocht je van plan zijn te licenties te verkopen, hoe zorg je er dan voor dat er ook
wordt betaald);
 Demo versie;
 Hoe zorg ik ervoor dat updates bij mijn klanten komen;
3. De basis: een lijstje met werkbladen maken op een tabblad
Allereerst moeten we een nieuw werkblad invoegen met de naam “Inhoud”, tenzij dat werkblad al
bestaat:
If Not IsIn(Worksheets, "Inhoud") Then
With Worksheets.Add(Worksheets(1))
.Name = "Inhoud"
End With
Set oToc = Worksheets("Inhoud")
ActiveWindow.DisplayGridlines = False
ActiveWindow.DisplayHeadings = False
Else
Set oToc = Worksheets("Inhoud")
vRemarks = oToc.Range(oToc.Range("C2"), _
oToc.Range("C2").End(xlDown).End(xlToRight)).Value
End If
De functie IsIn wordt hier gebruikt om te bepalen of het werkblad al bestaat en zo ja, dan wordt dat
werkblad bijgewerkt met de inhoudsopgave:
Function IsIn(vCollection As Variant, ByVal sName As String) As Boolean
'------------------------------------------------------------------------' Procedure : IsIn Created by Jan Karel Pieterse
' Company
: JKP Application Development Services (c) 2005
' Author
: Jan Karel Pieterse
' Created
: 28-12-2005
' Purpose
: Determines if object is in collection
'------------------------------------------------------------------------Dim oObj As Object
On Error Resume Next
Set oObj = vCollection(sName)
If oObj Is Nothing Then
IsIn = False
Else
IsIn = True
End If
If IsIn = False Then
sName = Application.Substitute(sName, "'", "")
Set oObj = vCollection(sName)
If oObj Is Nothing Then
IsIn = False
Else
IsIn = True
End If
End If
End Function
Nu het werkblad is gevonden (of ingevoegd) wijzen we dat toe aan de object variabele oTOC zodat we er
in de rest van de code makkelijk mee kunnen werken:
Set oToc = Worksheets("Inhoud")
Vervolgens kijken we of er al een tabel op het werkblad staat. (ListObject). Zo niet, dan maken we deze
aan:
If oToc.ListObjects.Count = 0 Then
oToc.Range("C2").Value = "Werkblad"
oToc.Range("D2").Value = "Snelkoppeling"
oToc.Range("E2").Value = "Opmerkingen"
oToc.ListObjects.Add xlSrcRange, oToc.Range("C2:E2"), , xlYes
End If
Dan maken we de lijst leeg en plaatsen we alle werkbladnamen er weer in:
On Error Resume Next
oToc.ListObjects(1).DataBodyRange.Rows.Delete
For Each oSh In Worksheets
lRow = oSh.Index
oToc.Range("C2").Offset(lRow).Value = oSh.Name
oToc.Range("C2").Offset(lRow, 1).FormulaR1C1 = "=HYPERLINK(""#'""&RC[1]&""'!A1"",RC[-1])"
oToc.Range("C2").Offset(lRow, 2).Value = ""
For lCt = LBound(vRemarks, 1) To UBound(vRemarks, 1)
If vRemarks(lCt, 1) = oSh.Name Then
oToc.Range("C2").Offset(lRow, 2).Value = vRemarks(lCt, 3)
Exit For
End If
Next
Next
oToc.ListObjects(1).Range.EntireColumn.AutoFit
Tevens zorgen we ervoor, dat eventuele opmerkingen die er al in stonden weer naast de juiste
werkbladen worden gezet:
For lCt = LBound(vRemarks, 1) To UBound(vRemarks, 1)
If vRemarks(lCt, 1) = oSh.Name Then
oToc.Range("C2").Offset(lRow, 2).Value = vRemarks(lCt, 3)
Exit For
End If
Next
De hele routine vind u in het bestand dat u na de dag kunt downloaden.
4. Het lint aanpassen
Voor de aanpassingen aan het lint gebruiken we de tool genaamd “Custom UI editor for Office”. U kunt
dit bestand gratis downloaden van de Microsoft website.
We openen de tool en openen ons Excel bestand erin:
Vervolgens kiezen we “Insert”, “Office 2007 Custom UI Part”.
Een leeg venster verschijnt, waarin we onderstaande XML code plakken:
<customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui
onLoad="rxJKPSheetToolscustomUI_onLoad">
<ribbon>
<tabs>
<tab id="rxJKPSheetTools" label="SheetTools">
<group id="rxJKPSheetToolsGeneral" label="Sheet Tools">
<dropDown id="rxJKPSheetToolsbtnSheets"
getItemCount="rxJKPSheetToolsbtnSheets_Count"
getItemLabel="rxJKPSheetToolsbtnSheets_getItemLabel"
onAction="rxJKPSheetToolsbtnSheets_Click"
sizeString="MMMMMMMMMMMMMMMMMMMMMMMM"
label="Sheets"
getSelectedItemIndex="rxJKPSheetToolsbtnSheets_getSelectedItemIndex"
/>
<button id="rxJKPSheetToolsbtnInsertToc"
label="Table Of Contents"
size="large"
onAction="rxJKPSheetToolsbtnInsertToc"
imageMso="CustomTableOfContentsGallery" />
</group>
</tab>
</tabs>
</ribbon>
</customUI>
Deze code verzorgt het maken van de volgende tab op het lint:
Van groot belang zijn de zogeheten callbacks die in de xml staan. Bijvoorbeeld:
getItemCount="rxJKPSheetToolsbtnSheets_Count"
Hiermee maak je aan Excel duidelijk, dat bij het vernieuwen van het lint de VBA routine
“rxJKPSheetToolsbtnSheets_Count” moet worden aangeroepen.
De Custom UI editor heeft een knop waarmee automatisch (op basis van de xml code) alle routines
worden aangemaakt die een callback behoeven:
Door op deze knop te drukken wordt de volgende lijst met (blanco) routines geleverd:
'Callback for customUI.onLoad
Sub rxJKPSheetToolscustomUI_onLoad(ribbon As IRibbonUI)
End Sub
'Callback for rxJKPSheetToolsbtnSheets getItemCount
Sub rxJKPSheetToolsbtnSheets_Count(control As IRibbonControl, ByRef returnedVal)
End Sub
'Callback for rxJKPSheetToolsbtnSheets getItemLabel
Sub rxJKPSheetToolsbtnSheets_getItemLabel(control As IRibbonControl, index As Integer,
ByRef returnedVal)
End Sub
'Callback for rxJKPSheetToolsbtnSheets onAction
Sub rxJKPSheetToolsbtnSheets_Click(control As IRibbonControl, id As String, index As
Integer)
End Sub
'Callback for rxJKPSheetToolsbtnSheets getSelectedItemIndex
Sub rxJKPSheetToolsbtnSheets_getSelectedItemIndex(control As IRibbonControl, ByRef
returnedVal)
End Sub
4.1.
Werking Sheets dropdown en lint
Zodra het lint wordt vernieuwd wordt als eerste de callback aangeroepen die een verwijzing geeft naar
het lint object:
Sub rxJKPSheetToolscustomUI_onLoad(ribbon As IRibbonUI)
Set moRibbon = ribbon
ThisWorkbook.Worksheets("Sheet1").Range("RibbonPointer").Value = ObjPtr(moRibbon)
End Sub
moRibbon is een object variabele die bovenaan de module is gedeclareerd.
Dim moRibbon As IRibbonUI
Omdat een module variabele haar waarde verliest zou het vernieuwen van het lint ophouden zodra er
een fout optreedt in onze code. Daarom gebruiken we een speciale truc om de verwijzing naar het lint
als een getal in een cel op het werkblad van onze tool op te slaan. Bij het “invalideren” van het lint halen
we zo nodig dat getal op uit het werkblad om opnieuw deze objectvariabele moRibbon de juiste
verwijzing te geven.
De dropdown moet gevuld worden met een lijst met werkbladen. Als het lint moet worden vernieuwd
wordt als eerste de routine “rxJKPSheetToolsbtnSheets getItemCount” aangeroepen. In die routine moet
worden doorgegeven aan het lint hoeveel werkbladen er in de lijst moeten worden geladen:
'Callback for rxJKPSheetToolsbtnSheets getItemCount
Sub rxJKPSheetToolsbtnSheets_Count(control As IRibbonControl, ByRef returnedVal)
returnedVal = ActiveWorkbook.Sheets.Count
End Sub
Vervolgens zal de routine “rxJKPSheetToolsbtnSheets getItemLabel” dat aantal keer worden
aangeroepen om de naam van ieder werkblad op te vragen, dus als er drie werkbladen zijn, wordt die
routine drie keer aangeroepen om de naam van dat werkblad op te vragen:
Public Sub rxJKPSheetToolsbtnSheets_getItemLabel(control As IRibbonControl, _
Index As Integer, ByRef returnedVal)
returnedVal = Sheets(Index + 1).Name
End Sub
Index is een geheel getal dat bij nul begint, maar de sheets in Excel beginnen bij 1, daarom tellen we er 1
bij op.
Als een ander werkblad gekozen wordt willen we eigenlijk dat de keuzelijst dat ook aangeeft. Daarvoor
zorgt de callback routine “rxJKPSheetToolsbtnSheets_getSelectedItemIndex”. Deze wordt aangeroepen
zodra alle werkbladen in de keuzelijst gezet zijn:
'Callback for rxJKPSheetToolsbtnSheets getSelectedItemIndex
Sub rxJKPSheetToolsbtnSheets_getSelectedItemIndex(control As IRibbonControl, _
ByRef returnedVal)
returnedVal = ActiveSheet.Index - 1
End Sub
Tenslotte willen we natuurlijk dat een werkblad wordt geselecteerd als die wordt gekozen uit de
keuzelijst:
Sub rxJKPSheetToolsbtnSheets_Click(control As IRibbonControl, id As String, _
Index As Integer)
Sheets(Index + 1).Activate
End Sub
4.1.
Het lint vernieuwen
Zodra we een ander werkblad selecteren op een andere manier dan via de keuzelijst, willen we eigenlijk
dat het lint zich vernieuwd, zodat het juiste werkblad ook te zien is op die keuzelijst. Ook als we van
bestand veranderen moet er iets gebeuren, want we willen dan de lijst met werkbladen geactualiseerd
hebben. We gebruiken hiertoe een klassemodule genaamd “clsApp” met daarin de volgende code:
Option Explicit
Public WithEvents App As Application
Private Sub App_SheetActivate(ByVal Sh As Object)
InvalidateRibbon
End Sub
Private Sub App_WorkbookActivate(ByVal Wb As Workbook)
InvalidateRibbon
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
InvalidateRibbon
End Sub
Private Sub Class_Terminate()
Set App = Nothing
End Sub
Iedere keer dat hetzij een ander werkblad wordt geselecteerd, hetzij een ander bestand, wordt nu de
routine “InvalidateRibbon” aangeroepen, die ervoor zorgt dat het lint wordt vernieuwd.
De routine InvalidateRibbon ziet er als volgt uit:
Sub InvalidateRibbon()
On Error Resume Next
GetRibbonObjectReference.Invalidate
End Sub
De werking van GetRibbonObjectReference wordt hier verder niet uitgelegd.
Om deze klasse module haar werk te laten doen is wat code in een normale module noodzakelijk, de
module modInit:
Option Explicit
'Variable to hold instance of class clsApp
Dim mcApp As clsApp
Public Sub Init()
'Rest mcApp in case it is already loaded
Set mcApp = Nothing
'Create a new instance of clsApp
Set mcApp = New clsApp
'Pass the Excel object to it so it knows what application
'it needs to respond to
Set mcApp.App = Application
End Sub
De routine “Init” wordt aangeroepen vanuit de Workbook_Open routine die zich in de ThisWorkbook
module bevindt:
Option Explicit
Private Sub Workbook_Open()
Application.OnTime Now, "Init"
End Sub
Nu hebben we alle code die nodig is om onze tool haar werk te laten doen.
5. Invoegtoepassing maken
Nu alle code is geschreven is het tijd om de invoegtoepassing te maken. Dit is de simpelste stap van
allemaal: Bestand, Opslaan Als en dan het juiste bestandstype kiezen:
Zodra “Excel-invoegtoepassing” is gekozen springt Excel automatisch naar de map met
invoegtoepassingen (rechter rode kader). Je kan simpel terug naar de map waar het “originele” bestand
staat door op de terug-knop te klikken (linker rode kader):
6. Conclusie
Er komt heel wat kijken bij het maken van een invoegtoepassing. Slechts 20 procent van het werk is het
schrijven van de code die het werk doet (het maken van de lijst). 80 procent gaat “verloren” met het
maken van de gebruikersinterface.
7. Copyright
Niets uit dit artikel mag worden vermenigvuldigd of anderszins verspreid zonder voorafgaande
schriftelijke toestemming van JKP Application Development Services.

Vergelijkbare documenten

VBA voorbeelden Excel

VBA voorbeelden Excel If Not IsIn(Worksheets, "Inhoud") Then With Worksheets.Add(Worksheets(1)) .Name = "Inhoud" End With Set oToc = Worksheets("Inhoud") ActiveWindow.DisplayGridlines = False ActiveWindow.DisplayHeading...

Nadere informatie

Het gebruik van Visual Basic for Applications in Microsoft Excel

Het gebruik van Visual Basic for Applications in Microsoft Excel Omdat een module variabele haar waarde verliest zou het vernieuwen van het lint ophouden zodra er een fout optreedt in onze code. Daarom gebruiken we een speciale truc om de verwijzing naar het lin...

Nadere informatie