Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Learn Working with Form Controls | User Interaction
Excel VBA for Business Automation

Working with Form Controls

Swipe to show menu

TextBoxes and ComboBoxes cover most data entry, but a few other controls express certain kinds of choices more naturally — and reading their values correctly has a couple of genuine gotchas worth knowing before you rely on them.

Figure 5.5

CheckBoxes

A CheckBox's Value property is a plain Boolean — True when checked, False when not:

If chkWelcomeEmail.Value = True Then
    MsgBox "A welcome email would be sent here."
End If

Each CheckBox on a form is completely independent of every other one — checking chkWelcomeEmail has no effect on chkNewsletter, which is exactly the behavior you want when the choices aren't mutually exclusive.

OptionButtons

OptionButtons behave the opposite way: within the same group, selecting one automatically deselects all the others — exactly what screenshot's Status choices need, since a customer can't simultaneously be both Active and Inactive. Reading which one is selected means checking each one's Value individually:

Dim status As String
If optActive.Value = True Then
    status = "Active"
ElseIf optInactive.Value = True Then
    status = "Inactive"
ElseIf optProspect.Value = True Then
    status = "Prospect"
End If

The mutual exclusivity is automatic and handled entirely by VBA — you never have to write code that unchecks the other two when one is selected. Grouping matters, though: OptionButtons only exclude each other within the same container (the form itself, or a Frame control placed on it), so two separate groups of OptionButtons on the same form — say, Status and Priority — need to sit inside two different Frames, or VBA would treat all of them as one single mutually-exclusive group.

ListBoxes

A ListBox shows several choices at once without needing to be clicked open, like the City list in Figure 5.5. Populate it the same way as a ComboBox, with .AddItem, and read the selection back through either .Value or .ListIndex:

Private Sub UserForm_Initialize()
    lstCities.AddItem "Chicago"
    lstCities.AddItem "Boston"
    lstCities.AddItem "Austin"
End Sub
 
Private Sub cmdAssign_Click()
    If lstCities.ListIndex = -1 Then
        MsgBox "Please select a city first."
        Exit Sub
    End If
    MsgBox "Assigning to: " & lstCities.Value
End Sub

ListIndex is the position of the selected item, counting from 0 for the first item — and critically, it's -1 whenever nothing at all has been selected yet, which is the check cmdAssign_Click uses to catch a user who clicks the button without picking a city first. .Value gives you the selected text directly, which is usually more useful than the numeric position .ListIndex provides.

Multi-Page Forms

A MultiPage control (the Details / Preferences tabs across the top of Figure 5.5) groups related controls onto separate pages of the same physical form — useful once a form has too many fields to fit comfortably on one screen. Each page is addressed by position, and only the controls on the currently visible page are meaningful to the user, though all of them still exist and can be read from code regardless of which page is showing:

Private Sub MultiPage1_Change()
    If MultiPage1.Value = 1 Then
        MsgBox "Now viewing Preferences"
    End If
End Sub

MultiPage1.Value follows the same 0-based counting as ListIndex — the first page is 0, the second (Preferences, in Figure 5.5) is 1 — and the Change event fires automatically whenever the user clicks a different tab.

Task

  1. Add a Frame containing three OptionButtons (optActive, optInactive, optProspect) to frmAddCustomer, replacing the earlier cboStatus ComboBox, and write the If...ElseIf block shown above to read the selection.
  2. Add two independent CheckBoxes (chkWelcomeEmail, chkNewsletter) and confirm in testing that checking one never affects the other.
  3. Add a ListBox (lstCities) populated in UserForm_Initialize, and write a check that shows an error if the user tries to proceed with nothing selected (ListIndex = -1).
  4. Challenge: wrap the Company Name, Contact, and Email fields on one MultiPage page named "Details", and the CheckBoxes and OptionButtons on a second page named "Preferences".
Hint
expand arrow

1. Replacing cboStatus with a Frame of OptionButtons

  • Delete cboStatus and its Label from the form first — you're replacing it, not adding alongside it.
  • Drag a Frame control onto the form, then drag all three OptionButtons inside that Frame's boundary — this containment is what makes them mutually exclusive as one group.
  • Rename each OptionButton individually in the Properties window; the Frame itself can keep a simple Caption like "Status" but doesn't need a code-friendly name unless you plan to reference it directly.
  • Reading the selection means checking each OptionButton's .Value individually with If...ElseIf — there's no single property that tells you "which one in the group is checked."
  • Since cboStatus.Value no longer exists, cmdSave_Click needs a status variable built from the OptionButtons instead, written into newRow in its place.

2. Adding two independent CheckBoxes

  • These sit directly on the form (not inside the Frame) — CheckBoxes outside a shared container never affect each other regardless of grouping.
  • To "confirm in testing," just check one, then the other, and watch that neither un-checks the first.

3. Adding and validating a ListBox

  • Populate it the same way as cboCity was populated — repeated .AddItem calls in UserForm_Initialize.
  • ListIndex = -1 specifically means nothing has been clicked yet — that's the exact condition to check before letting the save proceed.
  • This check belongs in the same validation block as the other required-field checks, appending to the same errors string.

4. MultiPage challenge

  • Drag a MultiPage control onto the form — it comes with two pages by default, which is exactly how many you need.
  • Rename each page via its own right-click → Page Order, or by selecting the page tab itself and using the Properties window's Caption field (Caption controls the tab's visible text; there's a separate (Name) field too if you want to reference a page by name in code).
  • Moving existing controls onto a page means dragging them physically onto that page's visible area in Design view — code references to them don't change at all, since a control's name doesn't depend on which page it sits on.
Solution
expand arrow
Private Sub UserForm_Initialize()
    cboCity.AddItem "Chicago"
    cboCity.AddItem "Boston"
    cboCity.AddItem "Austin"
    cboCity.AddItem "Seattle"
    cboCity.AddItem "Denver"

    lstCities.AddItem "Chicago"
    lstCities.AddItem "Boston"
    lstCities.AddItem "Austin"
    lstCities.AddItem "Seattle"
    lstCities.AddItem "Denver"
End Sub

Private Sub cmdCancel_Click()
    Unload Me
End Sub

Private Sub cmdSave_Click()
    Dim tbl As ListObject
    Dim newRow As ListRow
    Dim errors As String
    Dim status As String

    ' --- Reset field colors ---
    txtCompanyName.BackColor = vbWhite
    txtContact.BackColor = vbWhite
    txtEmail.BackColor = vbWhite

    ' --- Required field checks ---
    If Trim(txtCompanyName.Value) = "" Then
        errors = errors & "- Company Name is required" & vbNewLine
        txtCompanyName.BackColor = RGB(255, 199, 206)
    End If

    If Trim(txtContact.Value) = "" Then
        errors = errors & "- Contact is required" & vbNewLine
        txtContact.BackColor = RGB(255, 199, 206)
    End If

    If Trim(txtEmail.Value) = "" Then
        errors = errors & "- Email is required" & vbNewLine
        txtEmail.BackColor = RGB(255, 199, 206)
    End If

    ' --- Status via OptionButtons ---
    If optActive.Value = True Then
        status = "Active"
    ElseIf optInactive.Value = True Then
        status = "Inactive"
    ElseIf optProspect.Value = True Then
        status = "Prospect"
    Else
        errors = errors & "- Please select a Status" & vbNewLine
    End If

    ' --- ListBox check ---
    If lstCities.ListIndex = -1 Then
        errors = errors & "- Please select a city from the list" & vbNewLine
    End If

    If errors <> "" Then
        MsgBox "Please fix the following:" & vbNewLine & errors, vbExclamation
        Exit Sub
    End If

    ' --- All checks passed — safe to save now ---
    Set tbl = ThisWorkbook.Worksheets("Customers").ListObjects("tblCustomers")
    Set newRow = tbl.ListRows.Add

    newRow.Range(1, 1).Value = "C0" & (tbl.ListRows.Count + 100)
    newRow.Range(1, 2).Value = txtCompanyName.Value
    newRow.Range(1, 3).Value = txtContact.Value
    newRow.Range(1, 4).Value = txtEmail.Value
    newRow.Range(1, 5).Value = cboCity.Value
    newRow.Range(1, 6).Value = status

    If chkWelcomeEmail.Value = True Then
        MsgBox "A welcome email would be sent to " & txtEmail.Value & "."
    End If

    MsgBox txtCompanyName.Value & " was added successfully."
    Unload Me
End Sub

Testing notes:

  • Point 2 (CheckBoxes) has no dedicated validation — just click chkWelcomeEmail and chkNewsletter independently and confirm each toggles without affecting the other.
  • Point 4 (MultiPage) is a layout change only — no code changes are needed beyond what's already here, since every control keeps its own name regardless of which page it's dragged onto.
  • Notice cboCity.Value is still used for the actual save (newRow.Range(1, 5)), while lstCities exists purely as a separate, independently validated control per the task — the two aren't wired together unless you choose to extend it that way yourself.
Everything was clear?

How can we improve it?

Thanks for your feedback!

Section 5. Chapter 4

Ask AI

expand

Ask AI

ChatGPT

Ask anything or try one of the suggested questions to begin our chat

Working with Form Controls

TextBoxes and ComboBoxes cover most data entry, but a few other controls express certain kinds of choices more naturally — and reading their values correctly has a couple of genuine gotchas worth knowing before you rely on them.

Figure 5.5

CheckBoxes

A CheckBox's Value property is a plain Boolean — True when checked, False when not:

If chkWelcomeEmail.Value = True Then
    MsgBox "A welcome email would be sent here."
End If

Each CheckBox on a form is completely independent of every other one — checking chkWelcomeEmail has no effect on chkNewsletter, which is exactly the behavior you want when the choices aren't mutually exclusive.

OptionButtons

OptionButtons behave the opposite way: within the same group, selecting one automatically deselects all the others — exactly what screenshot's Status choices need, since a customer can't simultaneously be both Active and Inactive. Reading which one is selected means checking each one's Value individually:

Dim status As String
If optActive.Value = True Then
    status = "Active"
ElseIf optInactive.Value = True Then
    status = "Inactive"
ElseIf optProspect.Value = True Then
    status = "Prospect"
End If

The mutual exclusivity is automatic and handled entirely by VBA — you never have to write code that unchecks the other two when one is selected. Grouping matters, though: OptionButtons only exclude each other within the same container (the form itself, or a Frame control placed on it), so two separate groups of OptionButtons on the same form — say, Status and Priority — need to sit inside two different Frames, or VBA would treat all of them as one single mutually-exclusive group.

ListBoxes

A ListBox shows several choices at once without needing to be clicked open, like the City list in Figure 5.5. Populate it the same way as a ComboBox, with .AddItem, and read the selection back through either .Value or .ListIndex:

Private Sub UserForm_Initialize()
    lstCities.AddItem "Chicago"
    lstCities.AddItem "Boston"
    lstCities.AddItem "Austin"
End Sub
 
Private Sub cmdAssign_Click()
    If lstCities.ListIndex = -1 Then
        MsgBox "Please select a city first."
        Exit Sub
    End If
    MsgBox "Assigning to: " & lstCities.Value
End Sub

ListIndex is the position of the selected item, counting from 0 for the first item — and critically, it's -1 whenever nothing at all has been selected yet, which is the check cmdAssign_Click uses to catch a user who clicks the button without picking a city first. .Value gives you the selected text directly, which is usually more useful than the numeric position .ListIndex provides.

Multi-Page Forms

A MultiPage control (the Details / Preferences tabs across the top of Figure 5.5) groups related controls onto separate pages of the same physical form — useful once a form has too many fields to fit comfortably on one screen. Each page is addressed by position, and only the controls on the currently visible page are meaningful to the user, though all of them still exist and can be read from code regardless of which page is showing:

Private Sub MultiPage1_Change()
    If MultiPage1.Value = 1 Then
        MsgBox "Now viewing Preferences"
    End If
End Sub

MultiPage1.Value follows the same 0-based counting as ListIndex — the first page is 0, the second (Preferences, in Figure 5.5) is 1 — and the Change event fires automatically whenever the user clicks a different tab.

Task

  1. Add a Frame containing three OptionButtons (optActive, optInactive, optProspect) to frmAddCustomer, replacing the earlier cboStatus ComboBox, and write the If...ElseIf block shown above to read the selection.
  2. Add two independent CheckBoxes (chkWelcomeEmail, chkNewsletter) and confirm in testing that checking one never affects the other.
  3. Add a ListBox (lstCities) populated in UserForm_Initialize, and write a check that shows an error if the user tries to proceed with nothing selected (ListIndex = -1).
  4. Challenge: wrap the Company Name, Contact, and Email fields on one MultiPage page named "Details", and the CheckBoxes and OptionButtons on a second page named "Preferences".
Hint
expand arrow

1. Replacing cboStatus with a Frame of OptionButtons

  • Delete cboStatus and its Label from the form first — you're replacing it, not adding alongside it.
  • Drag a Frame control onto the form, then drag all three OptionButtons inside that Frame's boundary — this containment is what makes them mutually exclusive as one group.
  • Rename each OptionButton individually in the Properties window; the Frame itself can keep a simple Caption like "Status" but doesn't need a code-friendly name unless you plan to reference it directly.
  • Reading the selection means checking each OptionButton's .Value individually with If...ElseIf — there's no single property that tells you "which one in the group is checked."
  • Since cboStatus.Value no longer exists, cmdSave_Click needs a status variable built from the OptionButtons instead, written into newRow in its place.

2. Adding two independent CheckBoxes

  • These sit directly on the form (not inside the Frame) — CheckBoxes outside a shared container never affect each other regardless of grouping.
  • To "confirm in testing," just check one, then the other, and watch that neither un-checks the first.

3. Adding and validating a ListBox

  • Populate it the same way as cboCity was populated — repeated .AddItem calls in UserForm_Initialize.
  • ListIndex = -1 specifically means nothing has been clicked yet — that's the exact condition to check before letting the save proceed.
  • This check belongs in the same validation block as the other required-field checks, appending to the same errors string.

4. MultiPage challenge

  • Drag a MultiPage control onto the form — it comes with two pages by default, which is exactly how many you need.
  • Rename each page via its own right-click → Page Order, or by selecting the page tab itself and using the Properties window's Caption field (Caption controls the tab's visible text; there's a separate (Name) field too if you want to reference a page by name in code).
  • Moving existing controls onto a page means dragging them physically onto that page's visible area in Design view — code references to them don't change at all, since a control's name doesn't depend on which page it sits on.
Solution
expand arrow
Private Sub UserForm_Initialize()
    cboCity.AddItem "Chicago"
    cboCity.AddItem "Boston"
    cboCity.AddItem "Austin"
    cboCity.AddItem "Seattle"
    cboCity.AddItem "Denver"

    lstCities.AddItem "Chicago"
    lstCities.AddItem "Boston"
    lstCities.AddItem "Austin"
    lstCities.AddItem "Seattle"
    lstCities.AddItem "Denver"
End Sub

Private Sub cmdCancel_Click()
    Unload Me
End Sub

Private Sub cmdSave_Click()
    Dim tbl As ListObject
    Dim newRow As ListRow
    Dim errors As String
    Dim status As String

    ' --- Reset field colors ---
    txtCompanyName.BackColor = vbWhite
    txtContact.BackColor = vbWhite
    txtEmail.BackColor = vbWhite

    ' --- Required field checks ---
    If Trim(txtCompanyName.Value) = "" Then
        errors = errors & "- Company Name is required" & vbNewLine
        txtCompanyName.BackColor = RGB(255, 199, 206)
    End If

    If Trim(txtContact.Value) = "" Then
        errors = errors & "- Contact is required" & vbNewLine
        txtContact.BackColor = RGB(255, 199, 206)
    End If

    If Trim(txtEmail.Value) = "" Then
        errors = errors & "- Email is required" & vbNewLine
        txtEmail.BackColor = RGB(255, 199, 206)
    End If

    ' --- Status via OptionButtons ---
    If optActive.Value = True Then
        status = "Active"
    ElseIf optInactive.Value = True Then
        status = "Inactive"
    ElseIf optProspect.Value = True Then
        status = "Prospect"
    Else
        errors = errors & "- Please select a Status" & vbNewLine
    End If

    ' --- ListBox check ---
    If lstCities.ListIndex = -1 Then
        errors = errors & "- Please select a city from the list" & vbNewLine
    End If

    If errors <> "" Then
        MsgBox "Please fix the following:" & vbNewLine & errors, vbExclamation
        Exit Sub
    End If

    ' --- All checks passed — safe to save now ---
    Set tbl = ThisWorkbook.Worksheets("Customers").ListObjects("tblCustomers")
    Set newRow = tbl.ListRows.Add

    newRow.Range(1, 1).Value = "C0" & (tbl.ListRows.Count + 100)
    newRow.Range(1, 2).Value = txtCompanyName.Value
    newRow.Range(1, 3).Value = txtContact.Value
    newRow.Range(1, 4).Value = txtEmail.Value
    newRow.Range(1, 5).Value = cboCity.Value
    newRow.Range(1, 6).Value = status

    If chkWelcomeEmail.Value = True Then
        MsgBox "A welcome email would be sent to " & txtEmail.Value & "."
    End If

    MsgBox txtCompanyName.Value & " was added successfully."
    Unload Me
End Sub

Testing notes:

  • Point 2 (CheckBoxes) has no dedicated validation — just click chkWelcomeEmail and chkNewsletter independently and confirm each toggles without affecting the other.
  • Point 4 (MultiPage) is a layout change only — no code changes are needed beyond what's already here, since every control keeps its own name regardless of which page it's dragged onto.
  • Notice cboCity.Value is still used for the actual save (newRow.Range(1, 5)), while lstCities exists purely as a separate, independently validated control per the task — the two aren't wired together unless you choose to extend it that way yourself.
Everything was clear?

How can we improve it?

Thanks for your feedback!

Section 5. Chapter 4
some-alt