winforms 从另一个运行空间向窗体添加元素

7d7tgy0s  于 2023-06-24  发布在  其他
关注(0)|答案(4)|浏览(116)

我有一个表单,一旦准备好,就会添加几个元素(例如,列表)。添加它们可能需要一些时间(从几分之一秒到几分钟)。因此,我想将处理添加到一个单独的线程(子线程)。元素的数量事先并不知道(例如,文件夹中有多少个文件),因此它们在子流中创建。当子流中的处理结束时,我想在主窗体上显示这些元素(在此之前,窗体没有这些元素,并执行其他任务)。
然而,我面临的事实是,我不能将这些元素从子流添加到主窗体。我将给予一个简单的例子作为例子。它确实有效:

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

如何将在子流中创建的元素添加到主窗体?谢谢

bbuxkriu

bbuxkriu1#

虽然您可以在线程B上 * 创建 * 控件,但您不能将它们 * 添加到在线程A * 中从线程B*创建的控件中。

如果你尝试这样做,你会得到以下异常:

Controls created on one thread cannot be parented to a control on a different thread.
  • Parenting to* 表示在控件(窗体)上调用.Add().AddRange()方法以添加其他控件作为子控件。

换句话说:为了将控件添加到$Main表单(该表单是在 * 原始 * 线程(PowerShell运行空间)中创建并稍后显示的),$Main.Controls.Add()调用 * 必须 * 在同一线程中发生。
同样,你也应该**总是在同一个线程中附加 * 事件委托 (事件处理程序脚本块)。*
虽然your own answer试图确保在原始运行空间中将按钮添加到表单中,但它并不像编写的那样工作-请参阅底部部分。
我建议一个更简单的方法:

  • 使用 * 线程作业 * 通过Start-ThreadJob在后台创建控件。
  • Start-ThreadJobThreadJob module的一部分,它为基于子进程的常规后台作业提供了一种轻量级的、基于 * 线程 * 的替代方案,也是通过PowerShell SDK创建运行空间的更方便的替代方案。它附带PowerShell [Core] v6+,在Windows中,PowerShell可以按需安装,例如Install-Module ThreadJob -Scope CurrentUser。在大多数情况下,线程作业是更好的选择,无论是从性能还是类型保真度来看-请参阅this answer的底部部分了解原因。
  • 非模态地显示表单(.Show()而不是.ShowDialog()),并在[System.Windows.Forms.Application]::DoEvents()循环中处理GUI事件。
  • 注意:[System.Windows.Forms.Application]::DoEvents()通常会有问题(这本质上是阻塞的.ShowDialog()调用在幕后所做的),但在这个受约束的场景中(假设只显示 * 一个 * 表单),它应该没问题。有关背景信息,请参见this answer
  • 在循环中,检查线程作业输出的新创建按钮,附加事件处理程序,并将它们添加到窗体中。

下面是一个工作示例,它在使窗体可见后向窗体添加了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)
    }
    # Sleep a little, to avoid a near-tight loop.
    # Note: [Threading.Thread]::Sleep() is used in lieu of Start-Sleep,
    #       to avoid the problem reported in https://github.com/PowerShell/PowerShell/issues/19796
    [Threading.Thread]::Sleep(50)
}

# Clean up.
$Main.Dispose()
Remove-Job -Job $job -Force

'Done'

在撰写本文时,your own answer尝试通过使用Register-ObjectEvent订阅其他线程(运行空间)的事件来实现将控件添加到原始运行空间中的表单,因为用于事件处理的-Action脚本块运行(在内部的动态模块中)原始线程(运行空间),但存在两个问题:

  • 与您的答案不同,-Action脚本块既不能直接看到原始运行空间中的$Main变量,也不能看到其他运行空间中的变量-这些问题可以克服,但是,通过-MessageData$Main传递给Register-ObjectEvent,并通过脚本块中的$Event.MessageData访问它。以及通过$Sender.Runspace.SessionStateProxy.GetVariable()调用访问其他运行空间的变量。
  • 然而,更重要的是,.ShowDialog()调用将 * 阻塞 * 进一步的处理;也就是说,你的事件不会触发,因此你的-Action脚本块不会被调用,直到 * 表单关闭 * 之后。
  • 更新:您提到了一个 * 解决方法 *,以便在显示表单时触发PowerShell的事件:
  • 使用虚拟事件处理程序订阅MouseMove事件,该事件处理程序的调用使PowerShell有机会在表单以模式显示时触发自己的事件;例如:$Main.Add_MouseMove({ Out-Host });请注意,此解决方法仅在脚本块调用 * 命令 * 时有效,例如本例中的Out-Host(实际上是无操作);仅仅是表达式或.NET方法调用是不够的。
  • 然而,这种解决方法是次优的,因为它依赖于用户(不断地)将鼠标悬停在窗体上以触发PowerShell事件;而且,它有些模糊和低效。
zysjyyx4

zysjyyx42#

我认为你不能在不同的线程上创建窗体和控件。但是您可以访问控件属性。因此,您可以在运行空间中创建带有控件占位符的窗体,然后在计算完成后在主线程中更改它们。示例:

$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"
w8f9ii69

w8f9ii693#

这是我现在用的方法。感谢@mklement0关于Register-ObjectEvent方法(here)的讨论。我把它应用在这里。该方法的本质是在子流(在本例中为Button)中创建元素,当子空间完成工作时,处理Register-ObjectEventRegister-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()

这是我的一个变通办法。**但是,我仍然不知道原则上是否可以从子空间向表单添加子空间的元素。**当然,这是关于添加,而不是管理,因为从子空间管理是成功的。

7fyelxc5

7fyelxc54#

  • 把这个放在这里,因为它对于常规评论部分来说太长了。*

现在,我不会花太多时间在生产环境中使用运行空间,因为我在课堂上没有真实的的需要(至少到目前为止),当然,但我离题了。
然而,从我以前的阅读材料和笔记来看,这听起来像是RunSpace Pools的一个用例。以下是我保存的三个资源。当然,假设是你可能没有看过所有的电影。现在,我也会发布他们的代码,但都很长,所以,就这样了。根据您的用例,它可以被视为最后一个链接资源的副本。
PowerShell and WPF: Writing Data to a UI From a Different Runspace
PowerShell Tip: Utilizing Runspaces for Responsive WPF GUI Applications
Sharing Variables and Live Objects Between PowerShell Runspaces
How to access a different powershell runspace without WPF-object

相关问题