Хотя вы можете создавать элементы управления в потоке B, вы не можете добавлять их в элемент управления, созданный в потоке A из потока B .
Если вы попытаетесь это сделать, вы получите следующее исключение:
Controls created on one thread cannot be parented to a control on a different thread.
Воспитание в означает вызов метода .Add()
или .AddRange()
в элементе управления (форме) для добавления других элементов управления в качестве дочерних элементов управления.
Другими словами: Для добавления элементов управления в форму $Main
, которая создается и впоследствии отображается в Исходный поток (пространство выполнения PowerShell), вызов $Main.Controls.Add()
должен происходить в том же потоке.
Аналогично, всегда присоединяйте событие делегирует (блоки сценариев обработчика событий) в том же потоке.
Пока ваш собственный ответ пытается обеспечить добавление кнопок к форме в исходном пространстве выполнения, он не работает как написано - см. нижний раздел.
Я предлагаю более простой подход:
* 10 41 *
Использование задания потока для создания элементов управления в фоновом режиме с помощью Start-ThreadJob
.
Start-ThreadJob
is часть модуля ThreadJob
, который предлагает легкую альтернативу thread на основе регулярных фоновых заданий на основе дочерних процессов, а также является более удобной альтернативой созданию пространств выполнения с помощью PowerShell SDK. Он поставляется с PowerShell [Core] v6 +, а в Windows PowerShell может быть установлен по требованию, например, Install-Module ThreadJob -Scope CurrentUser
. В большинстве случаев задания потоков являются лучшим выбором как по производительности, так и по точности воспроизведения - см. Нижний раздел этот ответ , почему.
Показать Ваша форма немодально (.Show()
вместо .ShowDialog()
) и обрабатывает GUI события в [System.Windows.Forms.Application]::DoEvents()
l oop.
- Примечание:
[System.Windows.Forms.Application]::DoEvents()
может быть проблематичным c в целом (по сути, это то, что блокирующий вызов .ShowDialog()
делает за кулисами), но в этом ограниченном сценарии (при условии, что должна быть показана только одна форма) это должно быть хорошо. См. этот ответ для получения справочной информации.
В l oop проверьте наличие вновь созданных кнопок в качестве вывода заданием потока, прикрепите событие обработчик, и добавьте их в форму.
Вот рабочий пример, который добавляет 3 кнопки к форме после того, как она стала видимой, одна за другой во время сна между:
Add-Type -ea Stop -Assembly System.Windows.Forms
$Main = New-Object System.Windows.Forms.Form
# Start a thread job that will create the buttons.
$job = Start-ThreadJob {
$top = 0
1..3 | % {
# Create and output a button object.
($btn = [System.Windows.Forms.Button] @{
Name = "Button$_"
Text = "Button$_"
Top = $top
})
Start-Sleep 1
$top += $btn.Height
}
}
# Show the form asynchronously
$Main.Show()
# Process GUI events in a loop, and add
# buttons to the form as they're being created
# by the thread job.
while ($Main.Visible) {
[System.Windows.Forms.Application]::DoEvents()
if ($button = Receive-Job -Job $job) {
# Add an event handler...
$button.add_Click({ Write-Host "Button clicked: $($this.Name)" })
# .. and it to the form.
$Main.Controls.AddRange($button)
}
}
# Clean up.
$Main.Dispose()
Remove-Job -Job $job -Force
'Done'
На момент написания статьи ваш собственный ответ пытается добиться добавления элементов управления в форму в исходном пространстве выполнения, используя Register-ObjectEvent
для подписки на другой поток (пространство выполнения) события, учитывая, что блок сценария -Action
, используемый для обработки событий, запускает (в модуле Dynami c внутри) исходный поток (пространство выполнения), но есть две проблемы с этим:
В отличие от вашего ответа, блок сценария -Action
не видит напрямую ни переменную $Main
из исходного пространства выполнения, ни переменные другого пространства выполнения - однако эти проблемы можно преодолеть, передав * 110 3 * до Register-ObjectEvent
через -MessageData
и доступ к нему через $Event.MessageData
в блоке сценария, а также доступ к переменным другого пространства выполнения с помощью вызовов $Sender.Runspace.SessionStateProxy.GetVariable()
.
Что более важно, однако вызов .ShowDialog()
будет блокировать дальнейшую обработку; то есть ваши события не сработают, и поэтому ваш блок сценариев -Action
не будет вызываться до после закрытия формы .