Adwaenyth
Adwaenyth

Reputation: 2110

Using form to return variable fails to produce a return value

I'm writing a script for a user that concatenates several PDF files and appends tabular data as a text file. Now the problem is that the user can manually name the text files so that the script has to verify which PDF file belongs to the data. Usually it would do so by name, but since the user can (and will obviously) change the name of the PDF, the script shall ask the user to pick the correct PDF to merge.

I've written a function in my script, that uses Winforms to display a Listbox with the available PDF files and the user should pick one.

function Select-Rechnung
{
    param
    (
        [string] $Rechnung,
        [string[]] $PdfFiles
    )

    $form = New-Object System.Windows.Forms.Form
    $form.Text = "Rechnung wählen"
    $form.Size = New-Object System.Drawing.Size(640,320)
    $form.StartPosition = "CenterScreen"

    $form.KeyPreview = $true
    $form.Add_KeyDown({if($_.KeyCode -eq "Enter") { $x = $PdfFiles[$ListBox.SelectedIndex]; $form.Close() }})
    $form.Add_KeyDown({if($_.KeyCode -eq "Escape") { $form.Close() }})

    $OkButton = New-Object System.Windows.Forms.Button
    $OkButton.Location = New-Object System.Drawing.Size(240,240)
    $OkButton.Size = New-Object System.Drawing.Size(75,23)
    $OkButton.Text = "OK"
    $OkButton.Add_Click({ $x = $PdfFiles[$ListBox.SelectedIndex]; $form.Close() })
    $form.Controls.Add($OkButton)

    $CancelButton = New-Object System.Windows.Forms.Button
    $CancelButton.Location = New-Object System.Drawing.Size(325,240)
    $CancelButton.Size = New-Object System.Drawing.Size(75,23)
    $CancelButton.Text = "Abbrechen"
    $CancelButton.Add_Click({$form.Close()})
    $form.Controls.Add($CancelButton)

    $Label = New-Object System.Windows.Forms.Label
    $Label.Location = New-Object System.Drawing.Size(10,20) 
    $Label.Size = New-Object System.Drawing.Size(600,20) 
    $Label.Text = [string]::Format("Für die Rechnung {0} wurden mehrere mögliche Dateien gefunden. Bitte auswählen:", $Rechnung)
    $form.Controls.Add($Label)

    $ListBox = New-Object System.Windows.Forms.ListBox
    $ListBox.Location = New-Object System.Drawing.Size(10,40)
    $ListBox.Size = New-Object System.Drawing.Size(600, 20)
    $ListBox.Height = 200
    foreach($pdfFile in $PdfFiles)
    {
        [void] $ListBox.Items.Add($pdfFile)
    }
    $form.Controls.Add($ListBox)

    $form.TopMost = $true
    $form.Add_Shown({$form.Activate()})
    [void] $form.ShowDialog()

    $x
}

Now within the KeyDown handler or the Click handler, the function should assign the selected PDF file to the variable $x. I've checked that the $PdfFiles are correctly handed to the function and that during the handlers execution, the $PdfFiles[$ListBox.SelectedIndex] actually has the correct string value. However, when I access $x after the forms ShowDialog has been processed, it is empty and thus the function's return value is empty.

Why won't it assign the value (that it correctly evaluates during the handler) to my variable and return it?

Upvotes: 1

Views: 2679

Answers (3)

Adwaenyth
Adwaenyth

Reputation: 2110

I did it now by working around the problem. In the KeyDown and respectivlely the Click handler I'm no longer setting the variable directly but as:

$form.Add_KeyDown({
    if($_.KeyCode -eq "Enter") 
    { 
         if($ListBox.SelectedIndex -ge 0) 
         { 
             $form.DialogResult = System.Windows.Forms.DialogResult]::OK;
         } 
         $form.Close() 
    }
})

And in the end I do check for the DialogResult:

[string]$x = $null
if($form.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK)
{
    $x = $PdfFiles[$ListBox.SelectedIndex]
}   
return $x

That did result in the correct value being returned.

Upvotes: 2

DAXaholic
DAXaholic

Reputation: 35398

Try it by introducing a reference type like a hashtable and use it to modify and return the value like so

function Select-Rechnung
{
    param
    (
        [string] $Rechnung,
        [string[]] $PdfFiles
    )

    $x = @{ Value = '' }

    ...
    $OkButton.Add_Click({ $x.Value = $PdfFiles[$ListBox.SelectedIndex];
    ...

    $x.Value
}

You could also achieve your goal by accessing the $x variable of the outer scope with
Set-Variable -Name x -Scope 1 $PdfFiles[$ListBox.SelectedIndex]
or one of the other scope modifiers (global or script - see about scopes) if you want to avoid the extra hash table.

Upvotes: 4

Kirill Pashkov
Kirill Pashkov

Reputation: 3236

I suggest you to change scope level for $x variable to at least script scope, so it would be available everywhere within your script. Right now it's only "lives" inside script block of handle event. Change every $x variable to $script:x

function Select-Rechnung
{
    param
    (
        [string] $Rechnung,
        [string[]] $PdfFiles
    )

    $form = New-Object System.Windows.Forms.Form
    $form.Text = "Rechnung wählen"
    $form.Size = New-Object System.Drawing.Size(640,320)
    $form.StartPosition = "CenterScreen"

    $form.KeyPreview = $true
    $form.Add_KeyDown({if($_.KeyCode -eq "Enter") { $script:x = $PdfFiles[$ListBox.SelectedIndex]; $form.Close() }})
    $form.Add_KeyDown({if($_.KeyCode -eq "Escape") { $form.Close() }})

    $OkButton = New-Object System.Windows.Forms.Button
    $OkButton.Location = New-Object System.Drawing.Size(240,240)
    $OkButton.Size = New-Object System.Drawing.Size(75,23)
    $OkButton.Text = "OK"
    $OkButton.Add_Click({ $script:x = $PdfFiles[$ListBox.SelectedIndex]; $form.Close() })
    $form.Controls.Add($OkButton)

    $CancelButton = New-Object System.Windows.Forms.Button
    $CancelButton.Location = New-Object System.Drawing.Size(325,240)
    $CancelButton.Size = New-Object System.Drawing.Size(75,23)
    $CancelButton.Text = "Abbrechen"
    $CancelButton.Add_Click({$form.Close()})
    $form.Controls.Add($CancelButton)

    $Label = New-Object System.Windows.Forms.Label
    $Label.Location = New-Object System.Drawing.Size(10,20) 
    $Label.Size = New-Object System.Drawing.Size(600,20) 
    $Label.Text = [string]::Format("Für die Rechnung {0} wurden mehrere mögliche Dateien gefunden. Bitte auswählen:", $Rechnung)
    $form.Controls.Add($Label)

    $ListBox = New-Object System.Windows.Forms.ListBox
    $ListBox.Location = New-Object System.Drawing.Size(10,40)
    $ListBox.Size = New-Object System.Drawing.Size(600, 20)
    $ListBox.Height = 200
    foreach($pdfFile in $PdfFiles)
    {
        [void] $ListBox.Items.Add($pdfFile)
    }
    $form.Controls.Add($ListBox)

    $form.TopMost = $true
    $form.Add_Shown({$form.Activate()})
    [void] $form.ShowDialog()

    $script:x
}

It's now working for me with that way:

Select-Rechnung -Rechnung 'string' -PdfFiles 'file1.pdf','file2.pdf','file3.pdf'

Output:

file3.pdf

Upvotes: 2

Related Questions