home / skills / bobmatnyc / claude-mpm-skills / winforms

This skill helps you implement robust VB.NET Windows Forms patterns with UI threading, data binding, and event handling.

npx playbooks add skill bobmatnyc/claude-mpm-skills --skill winforms

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
9.3 KB
---
name: vb-winforms
description: Windows Forms development patterns, UI threading, data binding
version: 1.0.0
category: toolchain
author: Claude MPM Team
license: MIT
tags: [visualbasic, winforms, ui, desktop, data-binding]
---

# Visual Basic Windows Forms Patterns

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

## Quick Start

```vb
' 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

```vb
' 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

```vb
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

```vb
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

```vb
' 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

```vb
' 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

```vb
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

```vb
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

```vb
' 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

```vb
' 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

Overview

This skill provides practical patterns for Windows Forms development in VB.NET, focusing on UI threading, data binding, event handling, validation, and dialog patterns. It consolidates modern, safe approaches—async I/O, BackgroundWorker, BindingSource, INotifyPropertyChanged—and highlights common pitfalls to avoid. Use it to build responsive, maintainable desktop UIs with predictable state and resource management.

How this skill works

The skill inspects and documents concrete code patterns and idioms for WinForms applications: async loading and Task.Run for long-running work, Invoke/BeginInvoke checks for cross-thread UI updates, BackgroundWorker usage with progress and cancellation, and BindingSource/Object binding with INotifyPropertyChanged. It also covers form validation, custom dialog patterns, resource disposal, and standard event patterns for reuse and testability.

When to use it

  • Building responsive WinForms UIs that perform asynchronous I/O without blocking the UI thread
  • Implementing data-bound lists and editable detail views using BindingSource and object data binding
  • Handling long-running operations with progress reporting and cancellation
  • Implementing form validation, error feedback, and enabling/disabling actions reliably
  • Creating modal dialogs that return typed results and integrate cleanly with parent forms

Best practices

  • Use async/await for I/O and Task.Run for CPU-bound work to keep the UI responsive
  • Check Control.InvokeRequired before updating UI from background threads or marshal via the synchronization context
  • Prefer BindingSource and INotifyPropertyChanged for two-way data binding and UI synchronization
  • Report progress and support cancellation (BackgroundWorker or CancellationToken) for long operations
  • Dispose connections, timers, and disposable forms on FormClosing to avoid leaks; avoid Application.DoEvents

Example use cases

  • Async customer list load with loading cursor and error handling
  • Editable grid bound to a list of Customer objects with navigation and search/filter via BindingSource.Filter
  • Background processing with progress bar updates and cancel support using BackgroundWorker or Task with progress
  • Custom modal customer dialog that returns a populated Customer on OK, added to the main list
  • Form-level validation with ErrorProvider, enabling Save only when fields are valid

FAQ

How do I safely update controls from a background thread?

Check control.InvokeRequired and call Invoke or BeginInvoke to marshal UI updates back to the UI thread. Alternatively use async/await so continuations run on the UI context.

When should I use BindingSource vs direct list binding?

Use BindingSource to manage currency, filtering, and navigation. It provides a consistent layer for data-binding controls and works well with INotifyPropertyChanged objects.

Is BackgroundWorker still useful with async/await?

BackgroundWorker is still valid for simple progress/cancel scenarios, but Task-based async with Progress<T> and CancellationToken provides more flexible modern patterns.