Lesson 22 · RadioButton Control

The RadioButton Control

Master VB 2026's RadioButton — mutually exclusive selections, grouping with GroupBoxes and Panels, reading selected values, CheckedChanged events, iterating groups at runtime, combining RadioButtons with CheckBoxes, and Appearance = Button tab-strip patterns.

Key Takeaway: RadioButtons in the same container (Form, GroupBox, or Panel) are mutually exclusive — selecting one automatically deselects all others in that container. Use GroupBox or Panel to create separate groups on the same form. Read the selected button with a chain of If / ElseIf tests on .Checked, or loop with Controls.OfType(Of RadioButton)() and find the one where .Checked = True. The CheckedChanged event fires twice per click — once for the button being unchecked (the old one) and once for the button being checked (the new one) — so always guard with If rb.Checked Then inside the handler.
Checked
Boolean
True for the currently selected button in the group. Setting one True automatically sets others False.
Text
String
Label shown to the right of the bullet.
CheckAlign
ContentAlignment
Position of the radio bullet (default MiddleLeft).
AutoCheck
Boolean
True (default) — clicking selects automatically. Set False for manual control.
Appearance
Appearance enum
Normal (bullet) or Button (looks like a push button — great for tab strips).
FlatStyle
FlatStyle enum
Flat / Popup / Standard / System visual style.
TabIndex
Integer
Arrow keys navigate within a group when focus is on a RadioButton.
Tag
Object
Store a value (price, enum, etc.) so you can read it instead of comparing .Text strings.

22.1 Checked Property and CheckedChanged Event

RadioButtons in the same container are automatically mutually exclusive. You only need to check which one is Checked — VB deselects the others for you. In the CheckedChanged handler, always guard with If rb.Checked Then because the event fires once for the departing button (Checked becomes False) and once for the arriving button (Checked becomes True).

Basics.vb — Visual Basic 2026
' --- Reading which button is selected (If/ElseIf chain) ---
Private Sub btnConfirm_Click(...) Handles btnConfirm.Click
    Dim size As String
    If     rbSmall.Checked  Then size = "Small"
    ElseIf rbMedium.Checked Then size = "Medium"
    ElseIf rbLarge.Checked  Then size = "Large"
    Else                         size = "(none selected)"
    End If
    lblOrder.Text = $"Size: {size}"
End Sub

' --- Setting a default selection on Form_Load ---
Private Sub Form1_Load(...) Handles MyBase.Load
    rbMedium.Checked = True   ' sets Medium; Small and Large become False automatically
End Sub

' --- CheckedChanged fires TWICE per click ---
' Once when the OLD button loses selection (Checked → False)
' Once when the NEW button gains selection (Checked → True)
' Guard with If rb.Checked Then to run code only once:
Private Sub AnySize_CheckedChanged(...) Handles
        rbSmall.CheckedChanged, rbMedium.CheckedChanged, rbLarge.CheckedChanged

    Dim rb = DirectCast(sender, RadioButton)
    If Not rb.Checked Then Return   ' ignore the "unchecked" fire

    Select Case rb.Name
        Case "rbSmall"  : lblPrice.Text = "RM 8.00"
        Case "rbMedium" : lblPrice.Text = "RM 10.00"
        Case "rbLarge"  : lblPrice.Text = "RM 12.50"
    End Select
End Sub

' --- Using Tag to store numeric values instead of comparing strings ---
' In Designer, set rbSmall.Tag="8.00", rbMedium.Tag="10.00", rbLarge.Tag="12.50"
Private Sub AnySize_CheckedChanged2(...) Handles
        rbSmall.CheckedChanged, rbMedium.CheckedChanged, rbLarge.CheckedChanged
    Dim rb = DirectCast(sender, RadioButton)
    If Not rb.Checked Then Return
    Dim price = CDec(rb.Tag)
    lblPrice.Text = price.ToString("C2")
End Sub
The Double-Fire Trap

When the user clicks a RadioButton, CheckedChanged fires twice: first for the previously-selected button (its Checked goes from True → False), then for the newly-selected button (False → True). If you forget the If Not rb.Checked Then Return guard, your handler runs twice on every click — often causing visible flicker or duplicate work. This is the single most common RadioButton mistake.

Try It — Simulation 22.1: Pizza Size Selector

Select a pizza size. Only one can be active at a time. The price updates immediately via CheckedChanged, and the trace shows when the "double-fire" guard triggers.

Pizza Order — Form1.vb
Pizza Size (GroupBox — mutually exclusive)
Small (9") — RM 18.00
Medium (12") — RM 24.00
Large (15") — RM 30.00
Extra Large (18") — RM 38.00

22.2 Grouping with GroupBox and Panel

RadioButtons in the same direct parent container form one group. Place two GroupBoxes (or Panels) on the same form and they create two independent groups — VB treats them as separate radio families. This is essential for multi-question forms, order forms with multiple dimensions, and settings pages.

Grouping.vb — Visual Basic 2026
' Two independent groups — each GroupBox is its own selection scope.
' Selecting rbCheese in grpCrust has NO effect on grpSauce buttons.

' Reading both groups to build an order
Private Sub btnOrder_Click(...) Handles btnOrder.Click
    Dim crust  = GetSelected(grpCrust)    ' returns Text of checked button
    Dim sauce  = GetSelected(grpSauce)
    Dim size   = GetSelected(grpSize)
    lblSummary.Text = $"{size} pizza, {crust} crust, {sauce} sauce"
End Sub

' Generic helper: find checked button in any container
Private Function GetSelected(container As Control) As String
    Dim rb = container.Controls.OfType(Of RadioButton)() _
                       .FirstOrDefault(Function(r) r.Checked)
    Return If(rb IsNot Nothing, rb.Text, "(none)")
End Function

' Generic helper using Tag value instead of Text
Private Function GetSelectedTag(Of T)(container As Control) As T
    Dim rb = container.Controls.OfType(Of RadioButton)() _
                       .FirstOrDefault(Function(r) r.Checked)
    If rb IsNot Nothing AndAlso rb.Tag IsNot Nothing Then
        Return CType(rb.Tag, T)
    End If
    Return Nothing
End Function

' Programmatically select a button by value
Private Sub SelectByTag(container As Control, tagValue As Object)
    For Each rb In container.Controls.OfType(Of RadioButton)()
        If rb.Tag?.ToString() = tagValue.ToString() Then
            rb.Checked = True
            Return
        End If
    Next
End Sub
Try It — Simulation 22.2: Multi-Group Pizza Builder

Three independent GroupBoxes on one form. Selecting within one group never affects the others. The order summary reads all three groups using the GetSelected helper.

Pizza Builder — Three Independent GroupBoxes
grpSize
Small
Medium
Large
grpCrust
Thin
Thick
Stuffed
grpSauce
Tomato
Pesto
BBQ

22.3 Iterating RadioButton Groups at Runtime

When groups are built dynamically (loaded from a database, config file, or survey engine), you cannot hard-code control names. Use Controls.OfType(Of RadioButton)() to scan a container at runtime to find what is selected or to build the group programmatically.

RuntimeGroups.vb — Visual Basic 2026
' Build a group of RadioButtons at runtime from a list of options
Private Sub BuildRadioGroup(panel As Panel, options() As String,
                              defaultIndex As Integer = 0)
    panel.Controls.Clear()
    Dim y = 8
    For i = 0 To options.Length - 1
        Dim rb As New RadioButton With {
            .Text     = options(i),
            .Tag      = i,
            .Checked  = (i = defaultIndex),
            .Location = New Point(8, y),
            .AutoSize = True
        }
        AddHandler rb.CheckedChanged, AddressOf DynamicRb_CheckedChanged
        panel.Controls.Add(rb)
        y += 26
    Next
End Sub

Private Sub DynamicRb_CheckedChanged(sender As Object, e As EventArgs)
    Dim rb = DirectCast(sender, RadioButton)
    If Not rb.Checked Then Return   ' double-fire guard
    lblResult.Text = $"Selected: {rb.Text} (index {rb.Tag})"
End Sub

' Get selected index from any RadioButton group panel
Private Function GetSelectedIndex(panel As Panel) As Integer
    Dim rb = panel.Controls.OfType(Of RadioButton)() _
                   .FirstOrDefault(Function(r) r.Checked)
    Return If(rb IsNot Nothing, CInt(rb.Tag), -1)
End Function

' Load survey questions from a list
Dim satisfactionOptions = {"Very Satisfied", "Satisfied", "Neutral", "Dissatisfied"}
BuildRadioGroup(pnlSatisfaction, satisfactionOptions, defaultIndex:=1)

' Save a survey response
Private Sub btnSubmit_Click(...) Handles btnSubmit.Click
    Dim q1 = GetSelectedIndex(pnlQ1)
    Dim q2 = GetSelectedIndex(pnlQ2)
    Dim q3 = GetSelectedIndex(pnlQ3)
    If q1 = -1 OrElse q2 = -1 OrElse q3 = -1 Then
        MessageBox.Show("Please answer all questions.")
        Return
    End If
    SaveResponse(q1, q2, q3)
End Sub
Try It — Simulation 22.3: Customer Satisfaction Survey

A 3-question survey with RadioButton groups built from data arrays — just like BuildRadioGroup above. Submit validates all three are answered and shows the VB handler trace.

Customer Satisfaction Survey

22.4 Appearance = Button — Tab-Strip Pattern

Setting Appearance = Appearance.Button on RadioButtons inside a Panel creates a professional tab-strip or segmented control. The selected button stays "pressed" while others appear raised. This pattern is common in settings pages, filter bars, and view-switchers.

TabStrip.vb — Visual Basic 2026
' In the Designer:
'   1. Add a Panel (pnlTabs) with FlowLayout, horizontal
'   2. Add RadioButtons: rbDay, rbWeek, rbMonth, rbYear
'   3. Set ALL to Appearance = Appearance.Button, FlatStyle = Flat
'   4. Style with BackColor, ForeColor as needed
'   5. They share pnlTabs as container → mutually exclusive automatically

Private Sub Form1_Load(...) Handles MyBase.Load
    rbDay.Checked = True   ' default tab
End Sub

Private Sub AnyTab_CheckedChanged(...) Handles
        rbDay.CheckedChanged, rbWeek.CheckedChanged,
        rbMonth.CheckedChanged, rbYear.CheckedChanged

    Dim rb = DirectCast(sender, RadioButton)
    If Not rb.Checked Then Return

    ' Hide all content panels, show the relevant one
    pnlDay.Visible   = rbDay.Checked
    pnlWeek.Visible  = rbWeek.Checked
    pnlMonth.Visible = rbMonth.Checked
    pnlYear.Visible  = rbYear.Checked

    ' Or use Select Case on rb.Name:
    Select Case rb.Name
        Case "rbDay"   : LoadDayData()
        Case "rbWeek"  : LoadWeekData()
        Case "rbMonth" : LoadMonthData()
        Case "rbYear"  : LoadYearData()
    End Select
End Sub
Try It — Simulation 22.4: Sales Dashboard Tab Strip

Four RadioButtons with Appearance = Button style act as tabs. Selecting a tab switches the visible content panel and loads data — just as AnyTab_CheckedChanged would work in a real WinForms app.

Sales Dashboard — Appearance=Button RadioButtons

22.5 Combining RadioButtons and CheckBoxes

Real forms mix both controls. RadioButtons choose one mandatory option (size, plan tier, payment method); CheckBoxes add optional extras. A common pattern is using a RadioButton to enable a whole section, which in turn enables or disables a group of CheckBoxes.

Combined.vb — Visual Basic 2026
' Subscription plan form:
' RadioButtons choose the plan tier (Basic / Pro / Enterprise)
' CheckBoxes choose optional add-ons (some disabled for Basic)

Private ReadOnly _plans As New Dictionary(Of String, Decimal) From {
    {"Basic", 29.0}, {"Pro", 79.0}, {"Enterprise", 199.0}
}
Private ReadOnly _addons As New Dictionary(Of String, Decimal) From {
    {"Extra Storage", 10.0}, {"Priority Support", 20.0}, {"Analytics", 15.0}
}

Private Sub AnyPlan_CheckedChanged(...) Handles
        rbBasic.CheckedChanged, rbPro.CheckedChanged, rbEnterprise.CheckedChanged
    Dim rb = DirectCast(sender, RadioButton)
    If Not rb.Checked Then Return

    ' Basic plan cannot have add-ons
    Dim isBasic = rbBasic.Checked
    chkStorage.Enabled  = Not isBasic
    chkSupport.Enabled  = Not isBasic
    chkAnalytics.Enabled = Not isBasic
    If isBasic Then
        chkStorage.Checked   = False
        chkSupport.Checked   = False
        chkAnalytics.Checked = False
    End If
    UpdateTotal()
End Sub

Private Sub UpdateTotal()
    Dim planName = GetSelected(grpPlan)
    Dim total    = _plans(planName)
    If chkStorage.Checked   Then total += _addons("Extra Storage")
    If chkSupport.Checked   Then total += _addons("Priority Support")
    If chkAnalytics.Checked Then total += _addons("Analytics")
    lblTotal.Text = $"Monthly total: {total:C2}"
End Sub
Try It — Simulation 22.5: Subscription Plan Builder

RadioButtons choose the plan tier; CheckBoxes add optional extras. The Basic plan disables all add-ons. Price updates instantly on every change. The VB code trace shows which .Enabled assignments fire on each plan switch.

Subscription Plan Builder
Plan Tier (grpPlan)
Basic — RM 29/mo
Pro — RM 79/mo
Enterprise — RM 199/mo
Add-ons (CheckBoxes)
Extra Storage +RM 10
Priority Support +RM 20
Analytics +RM 15

22.6 GitHub Copilot — RadioButton Patterns

GitHub Copilot — Dynamic Quiz Engine
You typed: ' Build a multiple-choice quiz engine. Load questions from a List(Of QuizQuestion). Each question has Text and an array of Answers. Build RadioButtons at runtime in a Panel, handle CheckedChanged to track selections, score on Submit.'
' Multiple-choice quiz engine with runtime RadioButton generation.
Structure QuizQuestion
    Dim Text        As String
    Dim Answers()   As String
    Dim CorrectIndex As Integer
End Structure

Private _questions As List(Of QuizQuestion)
Private _panels    As New List(Of Panel)
Private _current   As Integer = 0

Private Sub LoadQuestion(index As Integer)
    pnlQuestion.Controls.Clear()
    Dim q = _questions(index)
    Dim y = 0
    pnlQuestion.Controls.Add(New Label With {
        .Text = $"Q{index+1}: {q.Text}", .AutoSize = True,
        .Font = New Font("Segoe UI", 10, FontStyle.Bold),
        .Location = New Point(0, y)})
    y += 30
    For i = 0 To q.Answers.Length - 1
        Dim rb As New RadioButton With {
            .Text     = q.Answers(i),
            .Tag      = i,
            .Location = New Point(0, y),
            .AutoSize = True
        }
        AddHandler rb.CheckedChanged, AddressOf Answer_CheckedChanged
        pnlQuestion.Controls.Add(rb)
        y += 26
    Next
    lblProgress.Text = $"Question {index+1} of {_questions.Count}"
End Sub

Private Sub Answer_CheckedChanged(sender As Object, e As EventArgs)
    Dim rb = DirectCast(sender, RadioButton)
    If Not rb.Checked Then Return
    btnNext.Enabled = True   ' enable Next only once answered
End Sub

Private Sub btnNext_Click(...) Handles btnNext.Click
    Dim selected = pnlQuestion.Controls.OfType(Of RadioButton)() _
                               .FirstOrDefault(Function(r) r.Checked)
    If selected IsNot Nothing AndAlso CInt(selected.Tag) = _questions(_current).CorrectIndex Then
        _score += 1
    End If
    _current += 1
    If _current < _questions.Count Then
        LoadQuestion(_current)
    Else
        MessageBox.Show($"Score: {_score}/{_questions.Count}")
    End If
End Sub
Copilot Chat Prompts for This Lesson

Try these in the Copilot Chat panel while working with RadioButtons:

  • "Write a generic Function GetCheckedRadio(container As Control) As RadioButton that returns the checked RadioButton or Nothing if none is selected, using LINQ OfType and FirstOrDefault"
  • "Build a shipping calculator: RadioButtons for Standard/Express/Overnight rates, CheckBoxes for insurance and signature-required add-ons, Label showing total formatted C2 updating on every CheckedChanged"
  • "Create a settings form with four tab-strip RadioButtons (Appearance=Button) that each show/hide a different Panel using a shared CheckedChanged handler and Select Case rb.Name"
  • "Implement a payment method selector: Radio buttons for Credit Card / Bank Transfer / eWallet, each unhiding a different sub-panel with the relevant input fields"

Lesson Summary

  • RadioButtons in the same container are automatically mutually exclusive — selecting one deselects all others in that container. No code is needed to implement this behaviour.
  • Use separate GroupBox or Panel containers to create multiple independent groups on one form. Each container is its own selection scope.
  • CheckedChanged fires twice per click — once for the deselected button (Checked → False) and once for the newly selected button (Checked → True). Always guard with If Not rb.Checked Then Return at the top of the handler.
  • Use a shared handler with DirectCast(sender, RadioButton) to handle all buttons in a group with one Sub. List them all after Handles, separated by commas.
  • Store values in the Tag property (price, enum index, ID) instead of comparing .Text strings — this makes the code locale-independent and easier to maintain.
  • Loop with container.Controls.OfType(Of RadioButton)().FirstOrDefault(Function(r) r.Checked) to find the selected button without hard-coding names — essential for dynamically built groups.
  • Appearance = Appearance.Button creates visually pressed toggle buttons — use them inside a Panel for a professional tab-strip or segmented control. Mutual exclusivity still applies automatically.

Exercises

Exercise 22.1 — Hotel Room Booking

  • Create three GroupBoxes: Room Type (Standard/Deluxe/Suite), Duration (1 night/Weekend/Week), and Meal Plan (None/Breakfast/Half-Board/Full-Board)
  • Use a shared CheckedChanged handler with If Not rb.Checked Then Return guard
  • Calculate total cost using a Dictionary keyed by Tag values
  • Show a formatted booking summary with String.Format alignment (Lesson 20)
  • Copilot challenge: Ask Copilot to "add early-bird discount: if Duration is Weekend or Week, apply 10% discount and show saving formatted C2"

Exercise 22.2 — Font Selector

  • Build a real-time font preview: RadioButtons for font family, RadioButtons for size, CheckBoxes for Bold/Italic/Underline
  • Apply all selections to a sample TextBox Label using New Font(...)
  • Use Appearance = Button for the size options as a tab-strip (8pt / 10pt / 12pt / 16pt / 24pt)
  • Disable Italic for monospace fonts (Courier New, Consolas) using the CheckedChanged handler
  • Copilot challenge: Ask Copilot to "serialize the selected font settings to JSON using System.Text.Json and restore them on Form_Load"

Exercise 22.3 — Survey Engine

  • Store 5 questions in a List(Of QuizQuestion) Structure, each with 4 answer options and a correct index
  • Build RadioButtons at runtime using AddHandler and a dynamic Panel
  • Navigate Next/Previous, tracking answers in an Integer array
  • Show results page: score, percentage (P0), and per-question correct/wrong indicator
  • Copilot challenge: Ask Copilot to "add a progress bar that fills as questions are answered, and colour answered questions green in a sidebar list"

Next: Lesson 23 — Web Browser Control

Embed a full web browser in your VB 2026 application using the WebView2 control — navigate to URLs, execute JavaScript, handle navigation events, and build a mini-browser with a toolbar.

Continue »

Related Resources


Featured Books

Visual Basic 2022 Made Easy

Visual Basic 2022 Made Easy

by Dr. Liew Voon Kiong

RadioButton, CheckBox, and all WinForms controls covered with complete working programs.

View on Amazon →
VB Programming With Code Examples

VB Programming With Code Examples

by Dr. Liew Voon Kiong

Order forms, settings dialogs, and survey engines using RadioButtons throughout.

View on Amazon →