PowerShell: действие события задания с формой не выполнено - PullRequest
1 голос
/ 14 сентября 2011

Если я запускаю следующий код, выполняется действие события:

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

Строка ' Событие-действие ' отображается.

Если я используюa Сформируйте и запустите указанный выше код, нажав кнопку

Действие события не выполнено:

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

Только когда я снова нажму кнопку, будет выполнено первое действие события.

Третьим щелчком мыши выполняется второе действие события и т. Д.

Если я сделаю несколько кликов в быстрой последовательности, результат непредсказуем.

Кроме того, когда я закрываю форму кнопкой в ​​верхнем правом углу,

выполняется последнее «открытое» событие Event.

Примечание. Для тестирования PowerShell ISE предпочтительнее, поскольку консоль PS отображает

строку только при определенных обстоятельствах.

Может кто-нибудь подсказать, что здесь происходит?

Заранее спасибо!


nimizen.

Спасибо за ваше объяснение, но я не совсем понимаю, почему событие StateChanged не запускается или не отображается для основного сценария, пока не произойдет какое-либо действие сФорма.Я был бы признателен за еще одну попытку объяснить это мне.

То, чего я хочу достичь, - это своего рода многопоточность с помощью PowerShell и форм.

Мой план следующий:

'

Скрипт показывает форму пользователю.

Пользователь вводит данные и нажимает кнопку.На основании введенных пользователем данных набор заданий запускается с помощью Start-Job, и для каждого задания регистрируется событие StateChanged.

Во время выполнения заданий пользователь может выполнять любые действия в форме (включая остановкуЗадания с помощью кнопки) и при необходимости перекрашивается форма.

Сценарий реагирует на любые события, запускаемые формой или ее дочерними элементами управления.

Также сценарий реагирует на StateChanged каждого задания.событие.

Когда происходит событие StateChanged, проверяется состояние каждого задания, и если все задания имеют состояние «Завершено», результаты заданий извлекаются с помощью Receive-Job и отображаются для пользователя.

'

Все это прекрасно работает, за исключением того, что событие StateChanged не видимо для основного сценария.

Выше все равно мое любимое решение, и если у вас есть какие-либо идеи, какреализовать это, пожалуйста, дайте мне знать.

В противном случае я, скорее всего, прибегну к обходному пути, который, по крайней мере, дает пользователю многопоточностьнг.Это иллюстрируется в следующем примере:

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

Ответы [ 2 ]

0 голосов
/ 25 ноября 2016

Существует множество вопросов и ответов (StackOverflow) об этом ' непреходящем мистике ' объединения событий формы (или WPF) с событиями .NET (например, EngineEvents, ObjectEvents и * 1005). *) в PowerShell:

Все они сводятся к одному: даже если настроено несколько потоков, в одном потоке есть два разных «слушателя». Когда ваш сценарий готов к приему событий формы (используя ShowDialog или DoEvents), он не может одновременно прослушивать события .NET. И наоборот: если сценарий открыт для событий .NET во время обработки команд (например, Start-Sleep или специально для прослушивания событий .NET с использованием таких команд, как Wait-Event или Wait-Job), ваша форма не сможет прослушивать события формы. , Это означает, что либо события .NET, либо события формы ставятся в очередь просто потому, что ваша форма находится в том же потоке, что и прослушиватели .NET, которые вы пытаетесь создать. Как и в примере с nimizen, при правильной внешности на первых этапах, ваша форма будет не реагировать на все другие события формы (нажатия кнопок) в тот момент, когда вы проверяете состояние фонового работника, и вам приходится нажимать кнопку снова и снова. еще раз, чтобы узнать, все еще ли это ‘*Doing Stuff’. Чтобы обойти это, вы можете подумать о том, чтобы объединить метод DoEvents в цикле, в то время как вы постоянно проверяете состояние фонового работника, но это тоже не выглядит хорошим способом, см .: Использование Application.DoEvents () Таким образом, единственный выход (я вижу) состоит в том, чтобы один поток запускал форму в другом потоке, что, я думаю, можно сделать только с помощью [runspacefactory]::CreateRunspace(), поскольку он способен синхронизировать управление формой между Лечит и тем самым непосредственно вызывает событие формы (например, TextChanged).

(если есть другой способ, я хочу узнать, как и увидеть рабочий пример.)

Форма Пример:

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()

Пример WPF см. Запись вывода PowerShell (как это происходит) в элемент управления пользовательского интерфейса WPF

0 голосов
/ 14 сентября 2011

Состояние свойства заданий Powershell доступно только для чтения; это означает, что вы не можете настроить состояние задания на что-либо до того, как фактически начнете задание. Когда вы отслеживаете событие с измененным состоянием, оно не срабатывает до тех пор, пока событие щелчка не появится снова и состояние «не увидит», изменится с «выполняется» на «завершено», и в этот момент выполняется ваш блок скрипта. Это также причина, почему скрипт-блок выполняется при закрытии формы.

Следующий скрипт избавляет от необходимости отслеживать событие и вместо этого отслеживает состояние. Я предполагаю, что вы хотите запустить «измененный код», когда состояние «работает».

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

Кроме того, обратите внимание на строки:

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

Эти строки гарантируют, что кнопка не ставит в очередь события щелчка. Очевидно, что вы можете удалить строки 'write-host', я оставил их, чтобы вы могли видеть, как изменяется состояние при выполнении скрипта.

Надеюсь, это поможет.

...