1

I'm trying to capture the data of a CheckBox form by storing the Booleans as an array using a simple "For Each" loop. No matter what variation on the basic structure I've tried, the result is always the same:

The array gets populated correctly during the Click event, as confirmed by some basic checks placed in the loop to display what's happening in the console. This data remains in the array until the form is closed, at which point the array becomes empty.

A second (possibly related) issue I've noted is that if the array is declared outside of the Click event it doesn't get populated correctly and instead show the following pattern:

[Correct sequence]

  1. Apple
  2. Pear
  3. Orange

[Actual result]

  1. [Empty]
  2. [Empty]
  3. ApplePearOrange

This doesn't happen if the array is declared inside the Click event. Regardless of where it's declared though, the array data is wiped completely when the form closes.

Here's the current script I'm using to test things, which includes some checks to compare with the console output, provided further down. I've included the results for both scenarios (declaring the array at the start as a global variable, and inside the click event)

[SCRIPT]

Clear-Host

# Define a global array
$global:arrayGlobal = @()

# Path to the data file
$dataFile = "D:\Path\To\Some\Data.txt"

# Parse text file to get data
$listNames = Get-Content $dataFile | ForEach-Object { ($_ -split '=')[0].Trim() }

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Create a form
$form = New-Object System.Windows.Forms.Form
$form.Text = "Example Form"
$form.Size = New-Object System.Drawing.Size(300,500)
$form.StartPosition = "CenterScreen"
$form.Topmost = $true

# Create checkboxes for each item
$verticalPosition = 30  # Initial vertical position for checkboxes
foreach ($item in $listNames) {
    $checkBox = New-Object System.Windows.Forms.CheckBox
    $checkBox.Text = $item
    $checkBox.AutoSize = $true
    $checkBox.Location = New-Object System.Drawing.Point(20, $verticalPosition)
    $form.Controls.Add($checkBox)
    $verticalPosition += 25  # Increment to space checkboxes vertically
}

# Add a button to submit selection
$submitButton = New-Object System.Windows.Forms.Button
$form.AcceptButton = $submitButton
$submitButton.Text = "Submit"
$submitButton.Location = New-Object System.Drawing.Point(100,350)
$submitButton.Add_Click({  

    $items = $form.Controls | Where-Object { $_.GetType() -eq [System.Windows.Forms.CheckBox] }
    Write-Host "Check 1 (total on Click): $($items.Length)"

    # Define another array inside the Click event to compare
    $arrayCompare = @()

    for ($i = 0; $i -lt $items.Length; $i++) {
    $item = $items[$i]
        $arrayGlobal += $item.Checked
        $arrayCompare += $item.Checked
        Write-Host = "Check 2 (For Each):" $item.Checked
    }

    Write-Host "Check 3:" $arrayGlobal.Count "items in Global Array"
    Write-Host "Check 3:" $arrayCompare.Count "items in Comparison Array"
    
    # CloseForm
    $form.DialogResult = [System.Windows.Forms.DialogResult]::OK
    $form.Close()
})

$form.Controls.Add($submitButton)

# Display the form
$form.ShowDialog()

Write-Host "Check 4:" $arrayGlobal.Count "items in Global Array"
Write-Host "Check 4:" $arrayCompare.Count "items in Comparison Array"
Write-Host "Global Array:" $arrayGlobal

[CONSOLE]

Check 1 (total on Click): 9
= Check 2 (For Each): False
= Check 2 (For Each): True
= Check 2 (For Each): True
= Check 2 (For Each): True
= Check 2 (For Each): False
= Check 2 (For Each): False
= Check 2 (For Each): False
= Check 2 (For Each): False
= Check 2 (For Each): False
Check 3: 1 items in Global Array
Check 3: 9 items in Comparison Array
OK
Check 4: 0 items in Global Array
Check 4: 0 items in Comparison Array
Global Array:

[END]

Any insight or suggestions would be much appreciated!

4
  • 1
    In short: Event handlers run in a child scope of the caller, so in order to create or update variables in the caller's scope, the latter must be targeted explicitly, typically with $script:variable = ...; without that, you'll implicitly create a block-local variable. Commented Dec 10, 2023 at 16:07
  • 1
    @mklement0 - thanks for that explanation, that's actually a concept that I'd come across but hadn't really grasped. And now this suddenly puts into context what a fundamental concept it really is! Such is the problem of learning PowerShell by tackling ambitious projects without ever learning fundamentals, unfortunately 🙂 Commented Dec 10, 2023 at 16:40
  • @mklement0 - I just saw that you've answered this question before. Apologies for the repeat. I honestly did spend quite a while Googling solutions but I think I must have not quite been expressing it right. I found very little that seemed helpful. Commented Dec 10, 2023 at 16:43
  • 1
    Glad to hear the explanation helped. No worries about the duplicate; if you don't know (the name of) the concept that is crucial to the solution, it's hard to find the right posts. Yes, scopes are important in PowerShell, but the way they work can be tricky, due to PowerShell's dynamic rather than lexical scoping. The bottom section of this answer tries to provide an overview. Commented Dec 10, 2023 at 16:48

1 Answer 1

2

This has to do with the way you handle scoping of the array. Inside the form code, you need to address the array you want for output using either $script:arrayGlobal or $global:arrayGlobal.

I have taken the liberty of renaming that output array as $selected and modified your code to demonstrate its use:

Clear-Host

# Path to the data file
$dataFile = "D:\Path\To\Some\Data.txt"

# Parse text file to get data
$listNames = Get-Content $dataFile | ForEach-Object { ($_ -split '=')[0].Trim() }

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Create a form
$form               = New-Object System.Windows.Forms.Form
$form.Text          = "Example Form"
$form.Size          = New-Object System.Drawing.Size(300,500)
$form.StartPosition = "CenterScreen"
$form.Topmost       = $true

# Create checkboxes for each item
$verticalPosition = 30  # Initial vertical position for checkboxes
foreach ($item in $listNames) {
    $checkBox          = New-Object System.Windows.Forms.CheckBox
    $checkBox.Text     = $item
    $checkBox.AutoSize = $true
    $checkBox.Location = New-Object System.Drawing.Point(20, $verticalPosition)
    $form.Controls.Add($checkBox)
    $verticalPosition += 25  # Increment to space checkboxes vertically
}

# Add a button to submit selection
$submitButton          = New-Object System.Windows.Forms.Button
$form.AcceptButton     = $submitButton
$submitButton.Text     = "Submit"
$submitButton.Location = New-Object System.Drawing.Point(100,350)
$submitButton.Add_Click({  
    $items = $form.Controls | Where-Object { $_.GetType() -eq [System.Windows.Forms.CheckBox] }
    # loop over the checkbox items and capture the text of each checked box in the array
    # use either script: or global: scoping, so the array holds its value afterwards
    $script:selected = for ($i = 0; $i -lt $items.Length; $i++) {
        if ($items[$i].Checked) { $items[$i].Text }
    }

    # Close Form
    $form.DialogResult = [System.Windows.Forms.DialogResult]::OK
    $form.Close()
})

$form.Controls.Add($submitButton)

# Display the form
$form.ShowDialog()
# Remove the form from memory
$form.Dispose()

Write-Host ("{0} items in Output Array: " -f $selected.Count)
$selected -join ([environment]::NewLine)
Sign up to request clarification or add additional context in comments.

2 Comments

This did the trick! Thank you so much! I've been stuck on this for days and had started to conclude it might be impossible.
Nice, though I wouldn't recommend $global: variables, given that they are session-global, i.e. linger after the script ends.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.