VB.NET AddHandler – Wire Events at Runtime (2026)
The VB.NET AddHandler statement connects events to methods at runtime instead of wiring them statically with the Handles keyword. This is essential when you create controls dynamically or need to assign event handlers based on conditions. This guide covers the syntax, the difference from Handles, dynamic controls, RemoveHandler, custom events, and common mistakes.
Inhaltsverzeichnis
Handles vs. AddHandler
In WinForms there are two ways to connect an event to a method:
' Way 1: Handles clause (static, at design time)
Private Sub BtnSave_Click(sender As Object, e As EventArgs) Handles BtnSave.Click
' Automatically wired by the designer
End Sub
' Way 2: AddHandler (dynamic, at runtime)
AddHandler BtnSave.Click, AddressOf BtnSave_Click
Private Sub BtnSave_Click(sender As Object, e As EventArgs)
' No "Handles" needed
End Sub
The Handles clause only works with controls that exist at design time. AddHandler works anywhere, including controls you create through code. As soon as you generate controls dynamically, AddHandler is the only option.
AddHandler syntax
AddHandler Object.Event, AddressOf MethodName
Object.Eventis the event you want to subscribe to (e.g.Button1.Click)AddressOfreturns a reference to the method that should be called- The method signature must match the event (e.g.
sender As Object, e As EventArgs)
Basic example
A button click using AddHandler instead of Handles:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler BtnGreet.Click, AddressOf BtnGreet_Click
End Sub
Private Sub BtnGreet_Click(sender As Object, e As EventArgs)
MessageBox.Show("Hello!")
End Sub
End Class
Notice that BtnGreet_Click has no Handles clause at the end. The connection is made entirely through AddHandler in the Load event.
Creating dynamic controls
The most common use case for AddHandler: controls are created through code and need event handlers. Here is an example with dynamically created buttons:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim categories = New List(Of String) From {
"Personal", "Business", "Archive"
}
For Each category In categories
Dim btn As New Button()
btn.Text = category
btn.Size = New Size(120, 40)
btn.Margin = New Padding(5)
AddHandler btn.Click, AddressOf CategoryButton_Click
FlowLayoutPanel1.Controls.Add(btn)
Next
End Sub
Private Sub CategoryButton_Click(sender As Object, e As EventArgs)
Dim btn = DirectCast(sender, Button)
MessageBox.Show($"Category: {btn.Text}")
End Sub
Every button gets the same click handler. Use DirectCast(sender, Button) to determine which button was clicked. This scales to any number of controls without needing a separate handler for each one.
Complete example: numpad keyboard
A realistic example that creates a virtual numpad keyboard at runtime. Each key button is connected to a shared handler via AddHandler:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim keys = New List(Of String) From {
"7", "8", "9",
"4", "5", "6",
"1", "2", "3",
"0", "00", ","
}
For Each key In keys
Dim btn As New Button()
btn.Text = key
btn.Size = New Size(50, 50)
btn.FlatStyle = FlatStyle.Flat
btn.FlatAppearance.BorderSize = 1
btn.BackColor = Color.White
btn.Font = New Font("Segoe UI", 12, FontStyle.Bold)
AddHandler btn.Click, AddressOf NumpadButton_Click
FlowLayoutPanel1.Controls.Add(btn)
Next
End Sub
Private Sub NumpadButton_Click(sender As Object, e As EventArgs)
Dim btn = DirectCast(sender, Button)
TextBox1.Text &= btn.Text
End Sub
End Class
You only need a FlowLayoutPanel (about 180 x 260 pixels) and a TextBox on the form. The buttons are created entirely through code, no designer work needed.
RemoveHandler: disconnecting events
Use RemoveHandler to disconnect the event binding. This is important to avoid memory leaks when removing controls at runtime:
' Assign handler AddHandler btn.Click, AddressOf Button_Click ' Remove handler RemoveHandler btn.Click, AddressOf Button_Click
Use RemoveHandler whenever you:
- Remove dynamic controls (e.g. when closing a tab)
- Want to swap a handler (remove first, then add)
- Want to prevent an event from firing multiple times
Multiple events, one handler
A single handler can process multiple events. This is especially useful for forms with many similar controls:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler TxtName.TextChanged, AddressOf ValidateField
AddHandler TxtEmail.TextChanged, AddressOf ValidateField
AddHandler TxtPhone.TextChanged, AddressOf ValidateField
End Sub
Private Sub ValidateField(sender As Object, e As EventArgs)
Dim txt = DirectCast(sender, TextBox)
If String.IsNullOrWhiteSpace(txt.Text) Then
txt.BackColor = Color.MistyRose
Else
txt.BackColor = Color.White
End If
End Sub
All three text boxes share the ValidateField handler. The sender parameter identifies which text box changed.
Custom events with AddHandler
You can also define your own events and subscribe to them with AddHandler. This is useful for loosely coupled communication between classes:
Public Class FileWatcher
Public Event FileChanged(filePath As String)
Public Sub CheckForChanges()
' Check logic...
RaiseEvent FileChanged("C:\data\config.xml")
End Sub
End Class
' In the form:
Private WithEvents watcher As New FileWatcher()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler watcher.FileChanged, AddressOf OnFileChanged
End Sub
Private Sub OnFileChanged(filePath As String)
MessageBox.Show($"File changed: {filePath}")
End Sub
The class uses RaiseEvent to trigger the event. The subscriber reacts through AddHandler. This keeps the FileWatcher class independent from the form.
Common mistakes
Handler registered multiple times
' BAD - each click adds another handler
Private Sub BtnRefresh_Click(sender As Object, e As EventArgs) Handles BtnRefresh.Click
AddHandler Timer1.Tick, AddressOf Timer1_Tick
End Sub
' GOOD - register handler once in Load
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler Timer1.Tick, AddressOf Timer1_Tick
End Sub
Calling AddHandler multiple times for the same event and method causes the method to execute multiple times. Register handlers in Load or call RemoveHandler first. For more on timers, see the VB.NET Timer guide.
Wrong method signature
' BAD - parameters don't match the event Private Sub Button_Click(text As String) End Sub ' GOOD - signature matches the Click event Private Sub Button_Click(sender As Object, e As EventArgs) End Sub
The method must have exactly the parameter types the event expects. For Click, that is (sender As Object, e As EventArgs). The compiler will report an error if the signature does not match.
Forgetting RemoveHandler for dynamic controls
If you remove controls with Controls.Remove() but don’t disconnect the handler, the reference keeps the object in memory. Always call RemoveHandler before removing a dynamic control.
FAQ
Handles connects an event to a method statically at design time. AddHandler connects an event dynamically at runtime. For controls created through code, AddHandler is the only option.
Yes, but the handler will execute multiple times. If that is not what you want, call RemoveHandler first or register the handler only once in the Load event.
Whenever you remove dynamic controls, want to swap handlers, or need to prevent an event from firing multiple times. Without RemoveHandler you risk memory leaks.
Yes. Use AddHandler multiple times with different controls but the same method. The sender parameter tells you which control triggered the event.
AddressOf creates a delegate, which is a typed reference to a method. It is used with AddHandler to tell the event which method to call when the event is raised.
Wrapping up
The AddHandler statement makes event handling in VB.NET flexible. Use it for dynamically created controls, when you need to assign or swap handlers at runtime, and for custom events in your classes. Remember to call RemoveHandler when removing controls, and register handlers only once to avoid multiple executions. For related topics, check out the VB.NET Timer guide and the VB.NET List guide.