Добавление элементов в форму из другого пространства выполнения - PullRequest
2 голосов
/ 08 февраля 2020

У меня есть форма, в которой, как только будет готово, будет добавлено несколько элементов (например, список). Их добавление может занять некоторое время (от долей секунды до нескольких минут). Поэтому я хочу добавить обработку в отдельный поток (дочерний). Количество элементов заранее неизвестно (например, сколько файлов находится в папке), поэтому они создаются в дочернем потоке. Когда обработка в дочернем потоке заканчивается, я хочу отобразить эти элементы в главной форме (до этого форма не имела этих элементов и выполняла другие задачи).

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

$Main = New-Object System.Windows.Forms.Form

$Run = {

    # The form is busy while adding elements (buttons here)

    $Top = 0
    1..5 | % {

        $Button = New-Object System.Windows.Forms.Button
        $Button.Top = $Top        
        $Main.Controls.Add($Button)
        $Top += 30
        Sleep 1        
    }
}

$Main.Add_Shown($Run)

# Adding and performing other tasks on the form here

[void]$Main.ShowDialog()

Но, добавив то же самое в дочерний поток, я не получил кнопку для отображения в основной форме. Я не понимаю, почему.

$Main = New-Object System.Windows.Forms.Form

$Run = {

    $RS = [Runspacefactory]::CreateRunspace()
    $RS.Open()
    $RS.SessionStateProxy.SetVariable('Main', $Main)
    $PS = [PowerShell]::Create().AddScript({

        # Many items will be added here. Their number and processing time are unknown in advance
        # Now an example with the addition of five buttons.

        $Top = 0        
        1..5 | % {

            $Button = New-Object System.Windows.Forms.Button
            $Button.Top = $Top        
            $Main.Controls.Add($Button)
            $Top += 30
            Sleep 1        
        }   
    })

    $PS.Runspace = $RS; $Null = $PS.BeginInvoke()
}

$Main.Add_Shown($Run)

[void]$Main.ShowDialog()

Как добавить в основную форму элементы, созданные в дочернем потоке? спасибо

Ответы [ 4 ]

1 голос
/ 14 февраля 2020

Хотя вы можете создавать элементы управления в потоке 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 не будет вызываться до после закрытия формы .

    • Обновление: вы упоминаете обходной путь для запуска событий PowerShell во время отображения формы:

      • Подписаться на событие MouseMove с помощью фиктивного обработчика событий, вызов которого дает PowerShell - возможность запускать свои собственные события, пока форма отображается модально; например: $Main.Add_MouseMove({ Out-Host }); обратите внимание, что этот обходной путь эффективен только в том случае, если блок скрипта вызывает команду , например, в этом примере Out-Host (которая фактически не используется); простое выражение или. NET вызова метода недостаточно .

      • Однако этот обходной путь является неоптимальным в том смысле, что он полагается на пользователя (непрерывно), перемещающегося по форме для PowerShell события, чтобы стрелять; Кроме того, это немного неясно и неэффективно.

1 голос
/ 10 февраля 2020

Я думаю, вы не можете создавать формы и элементы управления в разных потоках. Но вы можете получить доступ к свойствам управления, хотя. Таким образом, вы можете создать форму с контрольными заполнителями в пространстве выполнения, а затем изменить их в главном потоке после завершения вычислений. Пример:

$form = New-Object System.Windows.Forms.Form

$rs = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$rs.ApartmentState = [System.Threading.ApartmentState]::MTA
$ps = [powershell]::create()
$ps.Runspace = $rs 
$rs.Open()

$out = $ps.AddScript({param($form)

  $button1 = New-Object System.Windows.Forms.Button
  $button1.Name = "button1"
  $form.Controls.Add($button1) 
  $form.ShowDialog()

}).AddArgument($form).BeginInvoke()


#-----------------------------


sleep 1; 


$form.Controls["button1"].Text = "some button"
1 голос
/ 11 февраля 2020

Это способ, которым я сейчас пользуюсь. Спасибо @ mklement0 за разговор о методе Register-ObjectEvent ( здесь ). Я применил это здесь. Суть метода заключается в том, что элементы создаются в дочернем потоке (в данном случае Button), а когда дочернее пространство завершило работу, Register-ObjectEvent обрабатывается. Register-ObjectEvent находится в основном пространстве и поэтому позволяет добавить элемент (Button здесь) в форму.

$Main = New-Object System.Windows.Forms.Form

$Run = {

    $RS = [Runspacefactory]::CreateRunspace()
    $RS.Open()
    $RS.SessionStateProxy.SetVariable('Main', $Main)
    $PS = [PowerShell]::Create().AddScript({        

            $Button = New-Object System.Windows.Forms.Button
        }   
    })

    $PS.Runspace = $RS

    $Null = Register-ObjectEvent -InputObject $PS -EventName InvocationStateChanged -Action {    
        if ($EventArgs.InvocationStateInfo.State -in 'Completed', 'Failed') {
            $Main.Controls.Add($Button)
        }
    }

    $Null = $PS.BeginInvoke()
}

$Main.Add_Shown($Run)

[void]$Main.ShowDialog()

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

0 голосов
/ 09 февраля 2020

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

Однако, из всех моих предыдущих чтений и заметок, которые я сохранил, это звучит как сценарий использования для RunSpace Pools. Вот три из моих сохраненных ресурсов. Работая в предположении, что вы, возможно, не видели их всех, конечно. Я бы тоже выложил их код, но все они очень длинные, так что вот так. Исходя из вашего варианта использования, его можно рассматривать как дубликат последнего ресурса ссылки.

PowerShell и WPF: запись данных в пользовательский интерфейс из другого пространства выполнения

Совет по PowerShell: использование пространств выполнения для отзывчивых приложений WPF GUI

Совместное использование переменных и живых объектов между пространствами выполнения PowerShell

Как получить доступ к другому пространству выполнения powershell без WPF-объекта

...