Skip to content

04 · Advanced VBA Application Development

This module goes past Level 3's UserForms and error handling (Module 05) into building a small multi-procedure VBA application: a class module, a collection of objects, and a UserForm that drives them — the shape of a real internal tool.

1. Worked scenario — an expense-tracking mini app

Sheet Data, A1:C1 headers Item | Category | Amount, initially empty below the header — the VBA app will populate it.

2. A class module to represent one expense

Insert a class module named Expense:

Private pItem As String
Private pCategory As String
Private pAmount As Double

Public Property Let Item(v As String): pItem = v: End Property
Public Property Get Item() As String: Item = pItem: End Property
Public Property Let Category(v As String): pCategory = v: End Property
Public Property Get Category() As String: Category = pCategory: End Property
Public Property Let Amount(v As Double): pAmount = v: End Property
Public Property Get Amount() As Double: Amount = pAmount: End Property

Wrapping the three fields in a class (instead of three parallel arrays) means every expense travels as one object — pass it to a function, add it to a collection, or write it to a sheet in one call, without three separate variables staying manually in sync.

3. A standard module managing a collection of them

Public allExpenses As Collection

Sub InitExpenses()
    Set allExpenses = New Collection
End Sub

Sub AddExpense(itemName As String, cat As String, amt As Double)
    Dim e As New Expense
    e.Item = itemName
    e.Category = cat
    e.Amount = amt
    allExpenses.Add e
End Sub

Function TotalByCategory(cat As String) As Double
    Dim e As Expense
    Dim total As Double
    total = 0
    For Each e In allExpenses
        If e.Category = cat Then total = total + e.Amount
    Next e
    TotalByCategory = total
End Function

Manual trace: after AddExpense "Paper","Office",25, AddExpense "Taxi","Travel",40, AddExpense "Pens","Office",8, calling TotalByCategory("Office") loops all three, matches items 1 and 3 (Category="Office"), and returns 25+8=33.

4. Writing the collection back to the sheet

Sub DumpToSheet()
    Dim e As Expense
    Dim r As Long
    r = 2
    For Each e In allExpenses
        Sheets("Data").Cells(r, 1).Value = e.Item
        Sheets("Data").Cells(r, 2).Value = e.Category
        Sheets("Data").Cells(r, 3).Value = e.Amount
        r = r + 1
    Next e
End Sub

After the three AddExpense calls above, DumpToSheet writes three rows starting at A2, and a plain =SUM(C2:C4) on the sheet independently confirms the grand total: 25+40+8=73.

5. Wiring a UserForm to the collection

A UserForm frmAddExpense with text boxes txtItem, txtCategory, txtAmount and a button btnAdd:

Private Sub btnAdd_Click()
    If Not IsNumeric(txtAmount.Value) Then
        MsgBox "Amount must be a number.", vbExclamation
        Exit Sub
    End If
    AddExpense txtItem.Value, txtCategory.Value, CDbl(txtAmount.Value)
    MsgBox "Added. Office total so far: " & TotalByCategory("Office")
    Unload Me
End Sub

This is the pattern that makes VBA feel like a real application rather than a macro: the UserForm is only responsible for collecting and validating input; AddExpense and TotalByCategory (business logic) live in reusable, independently testable procedures.

6. Error handling around the whole flow

Sub RunApp()
    On Error GoTo ErrHandler
    InitExpenses
    frmAddExpense.Show
    Exit Sub
ErrHandler:
    MsgBox "Unexpected error: " & Err.Description, vbCritical
End Sub

On Error GoTo ErrHandler catches anything unanticipated (a corrupted form, a missing sheet) so the app fails with a readable message instead of an unhandled runtime error dialog.

Cheat sheet

Concept Purpose
Class module (Property Let/Get) Bundle related fields into one reusable object type
Collection Hold a growable list of custom objects
For Each ... In collection Iterate every object in the collection
UserForm calling standard-module procedures Separate input/validation from business logic

How It Actually Works

A full VBA application built with class modules is really building custom COM-like objects inside the workbook's VBA project, giving you your own object model layered on top of Excel's — a class module compiles to a private COM-style interface with properties (backed by Property Get/Let/ Set procedures) and methods, and instantiating it with New creates a genuinely separate object instance with its own private state, exactly like Excel's own Range or Worksheet objects, just implemented in your VBA rather than Excel's native code. Events work through a callback registration mechanism: declaring WithEvents on an object variable registers your procedure with Excel's event-dispatch table for that object, and Excel's application loop calls back into your code whenever the matching event fires (a Worksheet_Change, a custom class's own raised event) — this is fundamentally different from procedural VBA calls, because your code doesn't call these procedures directly; Excel's own runtime does, asynchronously relative to your other code, which is why event-handling code must be defensively written against re-entrancy (an event handler that itself edits a cell can retrigger the same event before the first call finishes, unless explicitly guarded).

Exercise

Add a RemoveExpense(index As Integer) procedure using allExpenses.Remove(index), then confirm that after removing item 1 (Paper), TotalByCategory("Office") correctly drops from 33 to 8.