PowerShell: Job Event Action with Form not executed

后端 未结 2 2104
情话喂你
情话喂你 2020-11-30 14:46

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

$Job = Start-Job {\'abc\'}
Register-ObjectEvent -InputObject $Job -EventName StateChanged `
    -         


        
相关标签:
2条回答
  • 2020-11-30 15:24

    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.

    0 讨论(0)
  • 2020-11-30 15:43

    There are a lot of (StackOverflow) questions and answers about this ‘enduring mystique’ of combining form (or WPF) events with .NET events (like EngineEvents, ObjectEvents and WmiEvents) in PowerShell:

    • Do Jobs really work in background in powershell?
    • WPF events not working in Powershell - Carousel like feature in multi-threaded script
    • is it possible to control WMI events though runspace and the main form?
    • Is there a way to send events to the parent job when using Start-WPFJob?
    • Update WPF DataGrid ItemSource from Another Thread in PowerShell

    They are all come down two one point: even there are multiple threads setup, there are two different 'listeners' in one thread. When your script is ready to receive form events (using ShowDialog or DoEvents) it can’t listen to .NET events at the same time. And visa versa: if script is open for .NET events while processing commands (like Start-Sleep or specifically listen for .NET events using commands like Wait-Event or Wait-Job), your form will not be able to listen to form events. Meaning that either the .NET events or the form events are being queued simply because your form is in the same thread as the .NET listener(s) your trying to create. As with the nimizen example, with looks to be correct at the first glans, your form will be irresponsive to all other form events (button clicks) at the moment you’re checking the backgroundworker’s state and you have to click the button over and over again to find out whether it is still ‘*Doing Stuff’. To work around this, you might consider to combine the DoEvents method in a loop while you continuously checking the backgroundworker’s state but that doesn’t look to be a good way either, see: Use of Application.DoEvents() So the only way out (I see) is to have one thread to trigger the form in the other thread which I think can only be done with using [runspacefactory]::CreateRunspace() as it is able to synchronize a form control between the treats and with that directly trigger a form event (as e.g. TextChanged).

    (if there in another way, I eager to learn how and see a working example.)

    Form example:

    Function Start-Worker {
        $SyncHash = [hashtable]::Synchronized(@{TextBox = $TextBox})
        $Runspace = [runspacefactory]::CreateRunspace()
        $Runspace.ThreadOptions = "UseNewThread"                    # Also Consider: ReuseThread  
        $Runspace.Open()
        $Runspace.SessionStateProxy.SetVariable("SyncHash", $SyncHash)          
        $Worker = [PowerShell]::Create().AddScript({
            $ThreadID = [appdomain]::GetCurrentThreadId()
            $SyncHash.TextBox.Text = "Thread $ThreadID has started"
            for($Progress = 0; $Progress -le 100; $Progress += 10) {
                $SyncHash.TextBox.Text = "Thread $ThreadID at $Progress%"
                Start-Sleep 1                                       # Some background work
            }
            $SyncHash.TextBox.Text = "Thread $ThreadID has finnished"
        })
        $Worker.Runspace = $Runspace
        $Worker.BeginInvoke()
    }
    
    [Void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    $Form = New-Object Windows.Forms.Form
    $TextBox = New-Object Windows.Forms.TextBox
    $TextBox.Visible = $False
    $TextBox.Add_TextChanged({Write-Host $TextBox.Text})
    $Form.Controls.Add($TextBox)
    $Button = New-Object System.Windows.Forms.Button
    $Button.Text = "Start worker"
    $Button.Add_Click({Start-Worker})
    $Form.Controls.Add($Button)
    $Form.ShowDialog()
    

    For a WPF example, see: Write PowerShell Output (as it happens) to WPF UI Control

    0 讨论(0)
提交回复
热议问题