1

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

1
  • 1
    There is no need for the Event and $Selected is there? Just run: $List.SelectedIndex After the GUI has closed in your if statement Commented Aug 2, 2023 at 10:29

1 Answer 1

1

([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.

    • If you did just $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
    
    • This is the equivalent of the ([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.

    • That enclosing the assignment target in (...) 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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.