e、 Open()$ps=[powershell]::Create()$ps.运行空间=$Runspace$null=$ps.AddScript((获取内容-Raw wpfDemo.ps1)).BeginInvoke()

e、 Open()$ps=[powershell]::Create()$ps.运行空间=$Runspace$null=$ps.AddScript((获取内容-Raw wpfDemo.ps1)).BeginInvoke(),wpf,powershell,xaml,runspace,powershell-sdk,Wpf,Powershell,Xaml,Runspace,Powershell Sdk,屏幕截图: 此示例屏幕截图显示一个已完成的后台操作和一个正在进行的后台操作(支持并行运行它们);注意启动正在进行的操作的按钮在操作期间是如何被禁用的,以防止再次进入: 源代码: 使用命名空间System.Windows 使用命名空间System.Windows.Threading #加载WPF程序集。 添加类型-AssemblyName PresentationCore、PresentationFramework #定义XAML文档,其中包含一对后台操作 #按钮和关联的状态文本框。 [xml]

屏幕截图

此示例屏幕截图显示一个已完成的后台操作和一个正在进行的后台操作(支持并行运行它们);注意启动正在进行的操作的按钮在操作期间是如何被禁用的,以防止再次进入:

源代码:

使用命名空间System.Windows
使用命名空间System.Windows.Threading
#加载WPF程序集。
添加类型-AssemblyName PresentationCore、PresentationFramework
#定义XAML文档,其中包含一对后台操作
#按钮和关联的状态文本框。
[xml]$xaml=@”
"@
#解析XAML,它返回一个[System.Windows.Window]实例。
$Window=[Markup.XamlReader]::加载((New Object System.Xml.XmlNodeReader$xaml))
#将窗口的相关控件保存在PowerShell变量中。
#后台操作启动按钮。
$btns=$Window.FindName('DoThing1'),$Window.FindName('DoThing2'))
#使用[哈希表]将按钮映射到相关的状态文本框。
$txtboxs=@{
$btns[0]=$Window.FindName('Status1')
$btns[1]=$Window.FindName('Status2')
}
#使用[哈希表]将按钮映射到相关背景
#操作,定义为稍后传递到启动ThreadJob的脚本块。
#这里的示例操作运行几秒钟,
#每秒发出“.”并在完成时发出消息。
$scriptBlocks=@{
$BTN[0]=
{
1..3 | ForEach对象{.';开始睡眠1}
“第一件事完成了。”
}
$BTN[1]=
{
1..2 | ForEach对象{.';开始睡眠1}
“第二件事完成了。”
}
}
#附加按钮单击事件处理程序
#启动后台操作(线程作业)。
foreach($btn中的btn){
$btn.添加\单击({
#暂时禁用此按钮以防止重新进入。
$this.IsEnabled=$false
#在关联的文本框中显示状态消息。
$txtboxs[$this].Text=“Started thing$($this.Name-replace'\D')at$(Get Date-Format T)。”
#异步启动为此按钮命名的后台线程作业。
#注意:也可以使用开始作业,但它会在*子进程*中运行代码,
#这要慢得多,而且还有其他影响。
$null=Start ThreadJob-Name$this.Name$scriptBlocks[$this]
})
}
#定义一个自定义DoEvents()类函数,该函数处理GUI WPF事件,并且可以
#在前台线程中的自定义事件循环中调用。
#改编自:https://docs.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcherframe
事件的功能{
[DispatcherFrame]$frame=[DispatcherFrame]::新建($True)
$null=[Dispatcher]::CurrentDispatcher.BeginInvoke(
“背景”,
[DispatcherOperationCallback]{
参数([对象]$f)
($f-as[DispatcherFrame])。Continue=$false
返回$null
}, 
$frame)
[调度程序]::推帧($frame)
}
#最后,以非模态方式显示窗口。。。
$Window.Show()
$null=$Windows.Activate()#确保窗口获得焦点。
# ... 并根据调用custom.DoEvents()方法输入自定义事件循环
而($Window.IsVisible){
#处理GUI事件。
道夫事件
#处理挂起的后台(线程)作业(如果有)。
获取每个对象的作业|{
#通过作业名称获取原始按钮。
$btn=$Window.FindName($\ux.Name)
#获取相应的状态文本框。
$txtBox=$txtboxs[$btn]
#测试作业是否已终止。
$completed=$\状态-在“completed”、“Failed”、“Stopped”中
#将任何新结果附加到相应的状态文本框中。
#注意使用重定向*>&1捕获所有流,特别是包括错误流。
如果($data=Receive Job$\u*>&1){
$txtBox.Text+=“`n”+($data-join“`n”)
}
#如果作业已完成,请进行清理。
若有(已完成){
删除作业$_
$btn.IsEnabled=$true#重新启用按钮。
$txtBox.Text+=“`nJob终止于:$(获取日期-格式T);状态:$($.State)。”
}
}
#注意:如果没有挂起的GUI事件,此循环将非常快速地循环。
#为了缓解这种情况,我们也会睡一点,但足够短,可以保持睡眠
#图形用户界面响应迅速。
开始睡眠-50毫秒
}
#窗户关上了;清理:
#如果窗口在所有作业完成之前关闭,
#获取未完成作业的剩余输出,等待它们完成,然后删除它们。
获取作业|接收作业-等待-自动删除作业

我一整天都在寻找解决方案,终于找到了一个,所以我将为那些有同样问题的人发布它

首先,检查这篇文章: 它解释得很好,并向您展示了如何在WPF GUI中正确使用运行空间。您只需将$Window变量替换为$Synchhash.Window:

$syncHash = [hashtable]::Synchronized(@{})
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$syncHash.window = [Windows.Markup.XamlReader]::Load( $reader )
在代码中插入运行空间函数:

function RunspaceBackupData {
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = "STA"
$Runspace.ThreadOptions = "ReuseThread"
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
$Runspace.SessionStateProxy.SetVariable("SelectedFolders",$global:SelectedFolders)
$Runspace.SessionStateProxy.SetVariable("SelectedUser",$global:SelectedUser)
$Runspace.SessionStateProxy.SetVariable("ReturnedDiskSource",$global:ReturnedDiskSource)
$Runspace.SessionStateProxy.SetVariable("ReturnedDiskDestination",$global:ReturnedDiskDestination)
$code = {
    foreach ($item in $global:SelectedFolders) {
        copy-item -Path "$global:ReturnedDiskSource\Users\$global:SelectedUser\$item" -Destination "$global:ReturnedDiskDestination\Users\$global:SelectedUser\$item" -Force -Recurse
        }
}
$PSinstance = [powershell]::Create().AddScript($Code)
$PSinstance.Runspace = $Runspace
$job = $PSinstance.BeginInvoke()
}
并使用指定的参数在所需的事件处理程序中调用它:

$var_btnStart.Add_Click( {
    RunspaceBackupData -syncHash $syncHash -SelectedFolders $global:SelectedFolders -SelectedUser $global:SelectedUser -ReturnedDiskSource $global:ReturnedDiskSource -ReturnedDiskDestination $global:ReturnedDiskDestination 
})
别忘了结束您的运行空间:

$syncHash.window.ShowDialog()
$Runspace.Close()
$Runspace.Dispose()

最好是展示改进的代码,而不是描述它应该是什么样子。链接的文章使用了高级PowerShell SDK技术,这令人印象深刻,但由于其复杂性也存在问题。我改进了我原来的方法,在以非模态方式显示窗口后,在自定义事件循环中执行所有后台处理。结合使用
Start ThreadJob
,示例代码不需要SDK(尽管它可以调整为使用SDK,但线程作业更可取),并且它显示了独立运行两个后台操作。根据您的用例调整示例应该没有问题。我完全同意这个解决方案不是最简单的。尤其是当您必须更改大部分脚本以将所有事件处理程序放入synchash变量时。。我是阿甘