Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
934 views
in Technique[技术] by (71.8m points)

winforms - PowerShell: Job Event Action with Form not executed

If I run the following code, the Event Action is executed:

$Job = Start-Job {'abc'}
Register-ObjectEvent -InputObject $Job -EventName StateChanged `
    -Action {
             Start-Sleep -Seconds 1
             Write-Host '*Event-Action*'
            } 

The string 'Event-Action' is displayed.

If I use a Form and start the above code by clicking a button,

the Event Action is not executed:

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Form1 = New-Object Windows.Forms.Form
$Form1.Add_Shown({
   $Form1.Activate()
})
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Text = 'Test'
$Form1.Controls.Add($Button1)
$Button1.Add_Click({
   Write-Host 'Test-Button was clicked'
   $Job = Start-Job {'abc'}
   Register-ObjectEvent -InputObject $Job -EventName StateChanged `
       -Action {
                Start-Sleep -Seconds 1
                Write-Host '*Event-Action*'
               }
})
$Form1.ShowDialog()

Only when I click the button again, the first Event Action is executed.

With the third click the second Event Action is executed and so on.

If I do multiple clicks in rapid succession, the result is unpredictable.

Furthermore when I close the form with the button in the upper right corner,

the last "open" Event Action is executed.

Note: For testing PowerShell ISE is to be preferred, because PS Console displays

the string only under certain circumstances.

Can someone please give me a clue what's going on here?

Thanks in advance!


nimizen.

Thanks for your explanation, but I don't really understand, why the StateChanged event is not fired or visible to the main script until there is some action with the Form. I'd appreciate another attempt to explain it to me.

What I want to accomplish is a kind of multithreading with PowerShell and Forms.

My plan is the following:

'

The script shows a Form to the user.

The user does some input and clicks a button. Based on the user's input a set of Jobs are started with Start-Job and a StateChanged event is registered for each job.

While the Jobs are running, the user can perform any action on the Form (including stop the Jobs via a button) and the Form is repainted when necessary.

The script reacts to any events which are fired by the Form or its child controls.

Also the script reacts to each job's StateChanged event.

When a StateChanged event occurs, the state of each job is inspected, and if all jobs have the state 'Completed', the jobs' results are fetched with Receive-Job and displayed to the user.

'

All this works fine except that the StateChanged event is not visible to the main script.

The above is still my favorite solution and if you have any idea how to implement this, please let me know.

Otherwise I'll most likely resort to a workaround, which at least gives the user a multithreading feeling. It is illustrated in the following example:

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Form1 = New-Object Windows.Forms.Form
$Form1.Add_Shown({
   $Form1.Activate()
})
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Text = 'Test'
$Form1.Controls.Add($Button1)
$Button1.Add_Click({
   $Form1.Focus()
   Write-Host 'Test-Button was clicked'
   $Job = Start-Job {Start-Sleep -Seconds 1; 'abc'}
   Do {
      Start-Sleep -Milliseconds 100
      Write-Host 'JobState: ' $Job.State
      [System.Windows.Forms.Application]::DoEvents()
   }
   Until ($Job.State -eq 'Completed')
   Write-Host '*Action*'
})
$Form1.ShowDialog()
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The state property of Powershell jobs is read-only; this means that you can't configure the job state to be anything before you actually start the job. When you're monitoring for the statechanged event, it doesn't fire until the click event comes around again and the state is 'seen' to change from 'running' to 'completed' at which point your script block executes. This is also the reason why the scriptblock executes when closing the form.

The following script removes the need to monitor the event and instead monitors the state. I assume you want to fire the on 'statechanged' code when the state is 'running'.

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Form1 = New-Object Windows.Forms.Form
$Form1.Add_Shown({
   $Form1.Activate()
})
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Text = 'Test'
$Form1.Controls.Add($Button1)

$Button1.Add_Click({
$this.Enabled = $false
   Write-Host $Job.State " - (Before job started)"
    $Job = Start-Job {'abc'}
   Write-Host $Job.State " - (After job started)"
         If ($Job.State -eq 'Running') {

                Start-Sleep -Seconds 1
                Write-Host '*Doing Stuff*'
               }

   Write-Host $Job.State " - (After IF scriptblock finished)"
[System.Windows.Forms.Application]::DoEvents()
$this.Enabled = $true

})

$Form1.ShowDialog()

In addition, note the lines:

$this.Enabled = $false
[System.Windows.Forms.Application]::DoEvents()
$this.Enabled = $true

These lines ensure the button doesn't queue click events. You can obviously remove the 'write-host' lines, I've left those in so you can see how the state changes as the script executes.

Hope this helps.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...