Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically create .NET buttons, with button pressed return its name, or other unique identifier

I am trying to dynamically add buttons to a list, and have $Button (or another variable) be set to whatever is clicked (No OK/Cancel button needed).

The below code creates the buttons, and resizes the window fine, but I cannot for the life of me figure out how to return the button name, or anything else unique to it. I have tried too many things to remember, but throughout all those tries, write-host $Button.name (when added, not below) works fine upon clicking, but with similar to below either $Button is empty, or contains "cancelled" (which I guess is because of the form.close()), or only the first assigned button .name. What am I missing?

#Add assemblies
add-type -assemblyname System.Windows.Forms
add-type -assemblyname System.Drawing

#Define the form
$Form = new-object System.Windows.Forms.Form
$Form.text = 'Which ticket to work on?'

$Tickets = "A","2","i"

#Define each button
for($i = 0; $i -lt $Tickets.count; $i++)
{
  write-host "`$i = $i"
  $Button = new-object System.Windows.Forms.Button
  $Button.name = $i
  $Position = 25 + (55 * $i)
  $Button.location = new-object System.Drawing.Point(25,$Position)
  $Button.size = new-object System.Drawing.Size(525,50)
  $Button.text = $Tickets[$i]

  $Button.add_click({
    $Button = $this.text
    $form.close()
  }.GetNewClosure())

  $Form.Controls.Add($Button)
}

#Set form size, for contents
$Height = 125 + (50 * ($Tickets.count))
$Form.Size = New-Object System.Drawing.Size(600,$Height)
$Form.StartPosition = 'CenterScreen'
$Form.Topmost = $true

#display the form
$Form.ShowDialog()

#display the result
"Button $Button was pressed"
like image 472
user66001 Avatar asked Dec 21 '25 13:12

user66001


2 Answers

You’re running into two issues:

  • Scope: the variable $Button inside the click handler is not the same “result” variable you read after ShowDialog(). Event scriptblocks execute in their own scope unless you explicitly write to a parent scope.

  • Identification: relying on the outer $Button variable is fragile because you overwrite it each loop; use the event sender instead.

Use the event sender to know which button was clicked, store the selected value somewhere accessible (e.g., the form’s Tag), set DialogResult to OK, then close the form.

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

# Define the form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = 'Which ticket to work on?'

$Tickets = 'A','2','i'

# Create buttons dynamically
for ($i = 0; $i -lt $Tickets.Count; $i++) {
    $button = New-Object System.Windows.Forms.Button
    $button.Name = "btn_$i"                 # unique name if you want it
    $button.Text = $Tickets[$i]             # what the user sees
    $button.Tag  = $Tickets[$i]             # store the value you want to return
    $button.Size = New-Object System.Drawing.Size(525, 50)
    $button.Location = New-Object System.Drawing.Point(25, 25 + (55 * $i))

    # Use the sender to know which button was clicked
    $null = $button.Add_Click({
        param($sender, $eventArgs)
        $form.Tag = $sender.Tag                                # or $sender.Text / $sender.Name
        $form.DialogResult = [System.Windows.Forms.DialogResult]::OK
        $form.Close()
    }.GetNewClosure())

    $Form.Controls.Add($button)
}

# Size and show
$Form.Size = New-Object System.Drawing.Size(600, 125 + (55 * $Tickets.Count))
$Form.StartPosition = 'CenterScreen'
$Form.TopMost = $true

$result = $Form.ShowDialog()

# Read back the selection
if ($result -eq [System.Windows.Forms.DialogResult]::OK -and $Form.Tag) {
    "Button $($Form.Tag) was pressed"
} else {
    "No selection."
}
like image 114
rossir.paulo Avatar answered Dec 24 '25 05:12

rossir.paulo


Event callbacks are invoked in a child scope so the assignment to $Button is lost when the callback ends. Simple solution can be to use the $script: scope modifier, see about_Scopes for details.

for ($i = 0; $i -lt $Tickets.count; $i++) {
    Write-Host "`$i = $i"
    $Button = [System.Windows.Forms.Button]@{
        Name     = $i
        Location = [System.Drawing.Point]::new(25, 25 + (55 * $i))
        Size     = [System.Drawing.Size]::new(525, 50)
        Text     = $Tickets[$i]
    }

    $Button.Add_Click({
        $script:callbackResult = "Button Number '$($this.Name)' with Text '$($this.Text)'"
        $form.Close()
    })

    $Form.Controls.Add($Button)
}

...
...

$Form.ShowDialog()
"Button $callBackResult was pressed"

Or a reference type defined before your for loop, for example a hash table. In both cases the closure is not needed, either using a reference type or a $script: scoped variable, you do not need a closure mainly because of your usage of $this to refer to the Button control.

$callbackResult = @{}
for ($i = 0; $i -lt $Tickets.count; $i++) {
    Write-Host "`$i = $i"
    $Button = [System.Windows.Forms.Button]@{
        Name     = $i
        Location = [System.Drawing.Point]::new(25, 25 + (55 * $i))
        Size     = [System.Drawing.Size]::new(525, 50)
        Text     = $Tickets[$i]
    }

    $Button.Add_Click({
        $callbackResult['Value'] = "Button Number '$($this.Name)' with Text '$($this.Text)'"
        $form.Close()
    })

    $Form.Controls.Add($Button)
}

...
...

$Form.ShowDialog()
"Button $($callBackResult['Value']) was pressed"
like image 37
Santiago Squarzon Avatar answered Dec 24 '25 03:12

Santiago Squarzon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!