我有一个表单,一旦准备好,就会添加几个元素(例如,列表)。添加它们可能需要一些时间(从几分之一秒到几分钟)。因此,我想将处理添加到一个单独的线程(子线程)。元素的数量事先并不知道(例如,文件夹中有多少个文件),因此它们在子流中创建。当子流中的处理结束时,我想在主窗体上显示这些元素(在此之前,窗体没有这些元素,并执行其他任务)。
然而,我面临的事实是,我不能将这些元素从子流添加到主窗体。我将给予一个简单的例子作为例子。它确实有效:
$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条答案
按热度按时间bbuxkriu1#
虽然您可以在线程B上 * 创建 * 控件,但您不能将它们 * 添加到在线程A * 中从线程B*创建的控件中。
如果你尝试这样做,你会得到以下异常:
.Add()
或.AddRange()
方法以添加其他控件作为子控件。换句话说:为了将控件添加到
$Main
表单(该表单是在 * 原始 * 线程(PowerShell运行空间)中创建并稍后显示的),$Main.Controls.Add()
调用 * 必须 * 在同一线程中发生。同样,你也应该**总是在同一个线程中附加 * 事件委托 (事件处理程序脚本块)。*
虽然your own answer试图确保在原始运行空间中将按钮添加到表单中,但它并不像编写的那样工作-请参阅底部部分。
我建议一个更简单的方法:
Start-ThreadJob
在后台创建控件。Start-ThreadJob
是ThreadJob
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个按钮,一个接一个,同时在中间休眠:
在撰写本文时,your own answer尝试通过使用
Register-ObjectEvent
订阅其他线程(运行空间)的事件来实现将控件添加到原始运行空间中的表单,因为用于事件处理的-Action
脚本块运行(在内部的动态模块中)原始线程(运行空间),但存在两个问题:-Action
脚本块既不能直接看到原始运行空间中的$Main
变量,也不能看到其他运行空间中的变量-这些问题可以克服,但是,通过-MessageData
将$Main
传递给Register-ObjectEvent
,并通过脚本块中的$Event.MessageData
访问它。以及通过$Sender.Runspace.SessionStateProxy.GetVariable()
调用访问其他运行空间的变量。.ShowDialog()
调用将 * 阻塞 * 进一步的处理;也就是说,你的事件不会触发,因此你的-Action
脚本块不会被调用,直到 * 表单关闭 * 之后。MouseMove
事件,该事件处理程序的调用使PowerShell有机会在表单以模式显示时触发自己的事件;例如:$Main.Add_MouseMove({ Out-Host })
;请注意,此解决方法仅在脚本块调用 * 命令 * 时有效,例如本例中的Out-Host
(实际上是无操作);仅仅是表达式或.NET方法调用是不够的。zysjyyx42#
我认为你不能在不同的线程上创建窗体和控件。但是您可以访问控件属性。因此,您可以在运行空间中创建带有控件占位符的窗体,然后在计算完成后在主线程中更改它们。示例:
w8f9ii693#
这是我现在用的方法。感谢@mklement0关于
Register-ObjectEvent
方法(here)的讨论。我把它应用在这里。该方法的本质是在子流(在本例中为Button
)中创建元素,当子空间完成工作时,处理Register-ObjectEvent
。Register-ObjectEvent
位于主空间,因此允许您向表单添加元素(此处为Button
)。这是我的一个变通办法。**但是,我仍然不知道原则上是否可以从子空间向表单添加子空间的元素。**当然,这是关于添加,而不是管理,因为从子空间管理是成功的。
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