I have a windows form with a drop box I need to save the selected index, but out of the lambda scope the variable is still set to zero
$List = New-Object system.Windows.Forms.ComboBox
$List.text = “”
$List.Size = New-Object System.Drawing.Size(280,20)
# Add the items in the dropdown list
@("a","b") | ForEach-Object {[void] $List.Items.Add($_)}
# Select the default value
$List.SelectedIndex = 0
$List.location = New-Object System.Drawing.Point(10,$Y); $Y+=30
$List.Font = ‘Microsoft Sans Serif,10’
$selected=0
$List.add_SelectedIndexChanged({
([ref]$selected) = $List.SelectedIndex
})
$form.Controls.Add($List)
then when I show the dialog and check the value
if ($form.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { return $selected }
variable $selected is =0 even if I select the second element in the list
am I missing something ?
thanks for your help
([ref]$selected)
returns a [ref]
instance that wraps either a given value or - when casting a variable to it - is a dynamic reference to that variable. Either way, the wrapped value must be accessed via the .Value
property.
Therefore, replace ([ref]$selected) = $List.SelectedIndex
with:
# Note the need for .Value
([ref] $selected).Value = $List.SelectedIndex
Note:
The primary purpose of [ref]
is to pass ref
or out
parameter values to .NET APIs.
Here, you're repurposing it to refer to $selected
variable defined in an ancestral (parent) scope in a manner that allows updating it.
$selected = $List.SelectedIndex
, a local $selected
variable would implicitly be created,[1] confined to the event-handler script block, given that such script blocks run in a child scope of the caller.Conceptually clearer alternatives:
If you know the $selected
variable to have been created in the script scope (as is true in your case), you can use the $script:
scope specifier to refer to it, which also allows updating it:
# Update the $selected variable in the *script* scope.
$script:selected = $List.SelectedIndex
If you want to update the variable in the parent scope - which may or may not the script scope - use the Set-Variable
cmdlet with -Scope 1
:
# Update the $selected variable in the *parent* scope (-Scope 1)
Set-Variable -Scope 1 -Name selected -Value $List.SelectedIndex
If you want to update the variable in the closest ancestral scope in which it was defined (whatever scope that may be), use Get-Variable
and assign to the returned variable object's .Value
property:
(Get-Variable -Name selected).Value $List.SelectedIndex
([ref] $selected).Value = ...
technique; note that both techniques require that such an ancestral variable already exist - by contrast, the $script:selected = ...
and Set-Variable -Scope 1 selected ...
techniques create the variable on demand.As for what you tried:
Trying the non-effective form ([ref]$selected) = $List.SelectedIndex
is understandable, and the fact that such an assignment is seemingly quietly ignored makes it harder to detect the problem:
In short: Your attempt created a local $selected
variable containing a [ref]
instance that (statically) wraps the value of $List.SelectedIndex
:
([ref] $selected) = $List.SelectedIndex
is the same as [ref] $selected = $List.SelectedIndex
, which is a regular type-constrained variable assignment.
(...)
is effectively ignored may be surprising, but that's how it has always worked.That is, variable $selected
is assigned to, which implicitly creates a local variable, and - by virtue of the "cast" placed to the left of the target variable - the values it can hold are constrained to instance of [ref]
, meaning that you can only assign values that either already are [ref]
instances or are convertible to [ref]
.
Because any value can be converted to [ref]
(e.g. [ref] 1
), the newly created local $selected
variable ended up containing a [ref]
instance that statically wraps the assigned value, i.e. the then-current value of $List.SelectedIndex
.
If we take the type-constraining aspect out of the picture, your attempt was equivalent to the following, which makes it clearer why it didn't work:
# Creates *local* variable $selected, holding a [ref] instance.
$selected = [ref] $List.SelectedIndex
Because a local variable was accidentally created, the script-level definition of $selected
remained unchanged.
[1] This perhaps surprising behavior is explained in this answer.