NYC

vb-winforms

SKILL.md

Visual Basic Windows Forms Patterns

Modern Windows Forms development with VB.NET focusing on proper UI threading, data binding, and event handling.

Quick Start

' Form definition
Public Class CustomerForm
    Inherits Form

    Private customerService As ICustomerService
    Private bindingSource As New BindingSource()

    Public Sub New()
        InitializeComponent()
        customerService = New CustomerService()
    End Sub

    ' Async load
    Private Async Sub CustomerForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Await LoadCustomersAsync()
    End Sub

    Private Async Function LoadCustomersAsync() As Task
        Try
            Cursor = Cursors.WaitCursor
            Dim customers = Await customerService.GetAllAsync()
            bindingSource.DataSource = customers
            dataGridView.DataSource = bindingSource
        Catch ex As Exception
            MessageBox.Show($"Error loading customers: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Finally
            Cursor = Cursors.Default
        End Try
    End Function
End Class

UI Threading Patterns

Invoke vs BeginInvoke

' Update UI from background thread
Private Sub BackgroundWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
    ' Already on UI thread with BackgroundWorker
    progressBar.Value = e.ProgressPercentage
    lblStatus.Text = $"Processing: {e.ProgressPercentage}%"
End Sub

' Manual invoke when needed
Private Sub UpdateUIFromThread(text As String)
    If lblStatus.InvokeRequired Then
        lblStatus.Invoke(Sub() lblStatus.Text = text)
    Else
        lblStatus.Text = text
    End If
End Sub

' Async pattern
Private Async Sub btnProcess_Click(sender As Object, e As EventArgs) Handles btnProcess.Click
    btnProcess.Enabled = False
    Try
        Dim result = Await Task.Run(Function() LongRunningOperation())
        lblResult.Text = result  ' Safe - back on UI thread
    Finally
        btnProcess.Enabled = True
    End Try
End Sub

BackgroundWorker Pattern

Private WithEvents bgWorker As New BackgroundWorker With {
    .WorkerReportsProgress = True,
    .WorkerSupportsCancellation = True
}

Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
    If Not bgWorker.IsBusy Then
        bgWorker.RunWorkerAsync()
    End If
End Sub

Private Sub bgWorker_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgWorker.DoWork
    For i = 0 To 100
        If bgWorker.CancellationPending Then
            e.Cancel = True
            Exit For
        End If

        ' Simulate work
        Threading.Thread.Sleep(50)
        bgWorker.ReportProgress(i)
    Next
End Sub

Private Sub bgWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bgWorker.ProgressChanged
    progressBar.Value = e.ProgressPercentage
End Sub

Private Sub bgWorker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
    If e.Cancelled Then
        MessageBox.Show("Operation cancelled")
    ElseIf e.Error IsNot Nothing Then
        MessageBox.Show($"Error: {e.Error.Message}")
    Else
        MessageBox.Show("Operation completed")
    End If
End Sub

Data Binding

BindingSource Pattern

Public Class CustomerForm
    Private bindingSource As New BindingSource()
    Private customers As List(Of Customer)

    Private Async Sub Form_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        customers = Await customerService.GetAllAsync()

        ' Setup binding source
        bindingSource.DataSource = customers

        ' Bind controls
        dataGridView.DataSource = bindingSource
        txtName.DataBindings.Add("Text", bindingSource, "Name")
        txtEmail.DataBindings.Add("Text", bindingSource, "Email")

        ' Navigation
        bindingNavigator.BindingSource = bindingSource
    End Sub

    ' Filter
    Private Sub txtSearch_TextChanged(sender As Object, e As EventArgs) Handles txtSearch.TextChanged
        If String.IsNullOrEmpty(txtSearch.Text) Then
            bindingSource.RemoveFilter()
        Else
            bindingSource.Filter = $"Name LIKE '%{txtSearch.Text}%'"
        End If
    End Sub
End Class

Object Data Binding

' Customer class with INotifyPropertyChanged
Public Class Customer
    Implements INotifyPropertyChanged

    Private _name As String
    Public Property Name As String
        Get
            Return _name
        End Get
        Set(value As String)
            If _name <> value Then
                _name = value
                OnPropertyChanged(NameOf(Name))
            End If
        End Set
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged

    Protected Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class

Event Handling

Standard Event Pattern

' Button click
Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click
    If ValidateForm() Then
        SaveCustomer()
    End If
End Sub

' Multiple controls, same handler
Private Sub TextBox_TextChanged(sender As Object, e As EventArgs) _
    Handles txtName.TextChanged, txtEmail.TextChanged

    ValidateForm()
End Sub

' Custom event arguments
Public Class CustomerEventArgs
    Inherits EventArgs

    Public Property Customer As Customer

    Public Sub New(customer As Customer)
        Me.Customer = customer
    End Sub
End Class

Public Event CustomerSaved As EventHandler(Of CustomerEventArgs)

Protected Sub OnCustomerSaved(customer As Customer)
    RaiseEvent CustomerSaved(Me, New CustomerEventArgs(customer))
End Sub

Form Validation

Private Function ValidateForm() As Boolean
    errorProvider.Clear()
    Dim isValid = True

    ' Validate name
    If String.IsNullOrWhiteSpace(txtName.Text) Then
        errorProvider.SetError(txtName, "Name is required")
        isValid = False
    End If

    ' Validate email
    If String.IsNullOrWhiteSpace(txtEmail.Text) OrElse Not txtEmail.Text.Contains("@") Then
        errorProvider.SetError(txtEmail, "Valid email is required")
        isValid = False
    End If

    ' Enable/disable save button
    btnSave.Enabled = isValid

    Return isValid
End Function

' Real-time validation
Private Sub txtEmail_Validating(sender As Object, e As System.ComponentModel.CancelEventArgs) _
    Handles txtEmail.Validating

    If Not txtEmail.Text.Contains("@") Then
        errorProvider.SetError(txtEmail, "Invalid email format")
        e.Cancel = True
    Else
        errorProvider.SetError(txtEmail, "")
    End If
End Sub

Dialog Patterns

Custom Dialog Result

Public Class CustomerDialog
    Inherits Form

    Public Property Customer As Customer

    Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
        If ValidateForm() Then
            Customer = New Customer With {
                .Name = txtName.Text,
                .Email = txtEmail.Text
            }
            DialogResult = DialogResult.OK
            Close()
        End If
    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        DialogResult = DialogResult.Cancel
        Close()
    End Sub
End Class

' Usage
Private Sub ShowCustomerDialog()
    Using dialog = New CustomerDialog()
        If dialog.ShowDialog() = DialogResult.OK Then
            ' Use dialog.Customer
            customers.Add(dialog.Customer)
            RefreshGrid()
        End If
    End Using
End Sub

Best Practices

✅ DO

' Use async for I/O operations
Private Async Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
    Dim data = Await LoadDataAsync()
End Sub

' Dispose resources properly
Private Sub Form_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
    connection?.Dispose()
    timer?.Dispose()
End Sub

' Use BindingSource for data binding
bindingSource.DataSource = customers
dataGridView.DataSource = bindingSource

' Validate on events
Private Sub txtName_Validating(sender As Object, e As CancelEventArgs) Handles txtName.Validating

' Use ErrorProvider for validation feedback
errorProvider.SetError(txtName, "Name is required")

❌ DON'T

' Don't block UI thread
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
    Dim data = LoadDataAsync().Result  ' Blocks UI!
End Sub

' Don't update UI from background thread directly
Task.Run(Sub()
    lblStatus.Text = "Done"  ' WRONG - cross-thread operation
End Sub)

' Don't forget to dispose forms
Dim form = New CustomerForm()
form.Show()  ' Memory leak - use Using or handle FormClosed

' Don't use Application.DoEvents
While processing
    Application.DoEvents()  ' Bad practice - use async instead
End While

Related Skills

  • vb-core: Core VB.NET patterns and type safety
  • vb-database: Database integration with Windows Forms
  • test-driven-development: Testing Windows Forms applications
Weekly Installs
9
First Seen
6 days ago
Installed on
codex7
opencode6
gemini-cli6
claude-code6
replit5
github-copilot5