wpf - 如何从 Powershell 运行空间连接回主 Powershell 线程?
问题描述
我已经开始思考 powershell 中的运行空间,我能够做到以下几点
- 创建运行空间
- 将变量传递给它
- 将预先编写的函数传递给它
- GUI/WPF 出现了,我什至可以操作上面的元素(例如,更改文本块中的文本)
问题是按钮之一(BTN_Exit)...我想在单击时实现这一点,它不仅关闭了 GUI,而且关闭了运行它的 RunSpace。我已经到了设法关闭 GUI 甚至调用 Runspace 的关闭序列的地步,但是(我假设)由于被调用的函数在 RunSpace 本身中运行,它只是挂断了(RunSpace 仍然处于“关闭”状态状态) - 显然 Powershell 无法杀死自己 :) 幸运的是,我仍然能够使用$(Get-Runspace)[-1].Dispose()
(手动)从主线程中处理它
我相信我需要连接回创建 GUI 运行空间的主线程并从那里关闭它,但我无法在函数中返回那里。如果窗口打开,我可以手动执行 close-runspace 函数中的所有 cmdlet,并达到预期的目标。
我试过添加$rs.connect($(Get-Runspace).Name -eq "Runspace1")
&$RS.disconnect()
如果我在“句柄”或实例上尝试同样的事情,它会产生相同的结果。如果不将函数传递给 RunSpace,我也无法在单击按钮时调用原始函数。
我如何以编程方式达到在单击按钮时关闭 GUI 并释放运行空间的程度?
这是代码:
#===[___VARIABLES___]===
$GUI = [hashtable]::Synchronized(@{})
$GUI.Host = $host
#===[___FUNCTIONS___]===
function Close-RunSpace {
if(!($RSI01H.Iscompleted)){
$GUI.BS.Dispatcher.invoke([action]{
$GUI.BS.Close()
})
}
$RSI01.EndInvoke($RSI01H)
$RS.Close()
$RS.Dispose()
}
#===[__RunSpaceCfg__]===
$RS_ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$RS_ISS.Commands.Add((New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList 'Close-RunSpace', (Get-Content Function:\Close-RunSpace -ErrorAction Stop)))
$RS = [runspacefactory]::CreateRunspace($RS_ISS)
$RS.ApartmentState = "STA"
$RS.ThreadOptions = "ReuseThread"
$RS.Open()
$RS.SessionStateProxy.SetVariable("GUI",$GUI)
$RS.SessionStateProxy.SetVariable("RS",$RS)
$RS.SessionStateProxy.SetVariable("RSI01",$RSI01)
$RS.SessionStateProxy.SetVariable("RSI01H",$RSI01H)
#===[___EXECUTION___]===
$RSI01 = [powershell]::Create().AddScript(
{
[void][System.Reflection.Assembly]::LoadWithPartialName("PresentationCore")
[void][System.Reflection.Assembly]::LoadWithPartialName("PresentationFramework")
[void][System.Reflection.Assembly]::LoadWithPartialName("WindowsBase")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Def_XAML = DATA {'
<Window x:Name="BS" x:Class="PST.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PST"
mc:Ignorable="d"
Title="BootStrapR"
Height="450"
Width="800"
ResizeMode="NoResize"
Topmost="True"
FontFamily="Arial"
WindowStartupLocation="CenterScreen"
WindowStyle="None">
<Grid>
<Button x:Name="BTN_Update"
Content="Update"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="109,352,0,0"
Width="75"/>
<Button x:Name="BTN_Exit"
Content="exit"
HorizontalAlignment="Left"
Margin="431,352,0,0"
VerticalAlignment="Top"
Width="75"/>
<ProgressBar x:Name="pb"
HorizontalAlignment="Left"
Height="29"
Margin="10,150,0,0"
VerticalAlignment="Top"
Width="780"
Background="White"
Foreground="Black"
BorderBrush="White"
Value="0" />
<TextBlock x:Name="pstext"
HorizontalAlignment="Left"
Height="266"
Margin="26,22,0,0"
TextWrapping="Wrap"
Text="TextBlock"
VerticalAlignment="Top"
Width="223"/>
</Grid>
</Window>
'}
[xml]$XAML = $Def_XAML -replace 'x:Class=*.*','' `
-replace 'mc:Ignorable="d"','' `
-replace "x:Name",'Name'
$WPF = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $XAML))
$XAML.SelectNodes("//*[@Name]") | ForEach-Object{
$GUI."$($_.Name)" = $WPF.FindName($_.Name)
}
$GUI.Error = $Error
$GUI.BTN_Exit.Add_Click({
#$GUI.BS.Close()
Close-RunSpace
})
$GUI.BTN_Update.Add_Click({
$GUI.pb.Value+=1
})
$GUI.BS.ShowDialog() | Out-Null
}
)
$RSI01.Runspace = $RS
$RSI01.Runspace.Name = "GUI"
$RSI01H = $RSI01.BeginInvoke()
解决方案
哦,我忘记了这篇文章!我发现的解决方案/解决方法在本文中。
基本上,本地 RunSpace 也需要传递到运行 GUI 的 RunSpace。(我做了:))然后可以引发在主线程中调用函数/脚本块等的事件。这个事件触发器然后做家务并清理/删除 GUI 运行空间注意:窗口本身需要从 GUI 运行空间内关闭/@按钮单击,否则运行空间将不会进入 IsComplete 状态并且被调用的函数将挂起(等到RS完成)
#===[___VARIABLES___]===
$Global:GUI = [hashtable]::Synchronized(@{})
$Global:VAR = [hashtable]::Synchronized(@{})
$VAR.Host = $Host
#===[______XAML_____]===
$XAML_BS = @"
<Window x:Name="BootStrapper" x:Class="SIT_SDM.Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SIT_SDM"
mc:Ignorable="d"
Height="450" Width="800" Background="#FF2F1333" WindowStyle="None">
<Grid>
<Button Name="BS_BTN_X" Content="Close" Margin="600,0,25,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
"@
#===[___FUNCTIONS___]===
function BS_Close {
begin {
$Global:GUI.BootStrapper.Add_Closing({$_.Cancel = $true})
}
process {
if ($BS_Handle.IsCompleted) {
$BS.EndInvoke($BS_Handle)
$BS.RunSpace.Close()
$BS.RunSpace.Dispose()
} else {
Write-Error -Text "Runspace job not complete!"
}
Unregister-Event -SourceIdentifier "BS_Close"
}
end {
if ((Get-Runspace).count -ge 2) {
$(Get-Runspace)[-1].Dispose()
}
}
}
function Initialize-XAML {
[CmdletBinding()]
Param(
[Parameter()]
[string]$File,
[string]$Variable
)
[void][System.Reflection.Assembly]::LoadWithPartialName("PresentationCore")
[void][System.Reflection.Assembly]::LoadWithPartialName("PresentationFramework")
if (!([string]::IsNullOrEmpty($File))) {
$inputXAML = Get-Content -Path $File -ErrorAction Stop
} elseif (!([string]::IsNullOrEmpty($Variable))) {
$inputXAML = $Variable
} else {
#Write-Error "Neither File nor Variable has been declared"
break
}
#Clean XAML for PowerShell compatibilty
[xml]$XAML = $inputXAML -replace 'x:Class=*.*','' -replace 'mc:Ignorable="d"','' -replace "x:Name",'Name'
#Create the XAML reader using XML node reader
$ReadR = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $XAML))
#Grab named objects from tree and put in a flat structure using Xpath
$NamedNodes = $XAML.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]")
$NamedNodes | ForEach-Object {
$Script:GUI.Add($_.Name, $ReadR.FindName($_.Name))
}
}
#===[___EXECUTION___]===
$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$Functions = Get-Command -Module SITSD #Script is being used with import-module
foreach($Function in $Functions){
$functionDefinition = Get-Content function:\$Function
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $functionDefinition
$InitialSessionState.Commands.Add($functionEntry)
}
$RS = [runspacefactory]::CreateRunspace($InitialSessionState)
$RS.ApartmentState = "STA"
$RS.ThreadOptions = "ReuseThread"
$RS.Open()
#Passing variables to Runspace
$RS.SessionStateProxy.SetVariable("GUI",$GUI)
$RS.SessionStateProxy.SetVariable("VAR",$VAR)
$RS.SessionStateProxy.SetVariable("XAML_BS",$XAML_BS)
Register-EngineEvent -SourceIdentifier "BS_Close" -Action {BS_Close}
$BS = [PowerShell]::Create().AddScript({
Initialize-XAML -Variable $XAML_BS
$GUI.BS_BTN_X.Add_Click({
$Global:VAR.host.UI.Write("Button pressed")
$GUI.BootStrapper.Close()
$Global:VAR.Host.Runspace.Events.GenerateEvent( "BS_Close", $GUI.BS_BTN_X, $null, "BS_Close")
})
$GUI.BootStrapper.Showdialog()|Out-Null
})
$BS.RunSpace = $RS
$BS_Handle = $BS.BeginInvoke()
推荐阅读
- docker - Docker 撰写 ECS 错误:资源处理程序返回消息:“模型验证失败(#/Volumes:数组项不唯一)”
- java - 未找到 [SaajSoapMessage {http://schemas.xmlsoap.org/soap/envelope/}UpdateXXXRequest] 的端点映射
- c# - 依赖注入是一种设计模式吗?
- windows - Docker for windows 使用 WSL2 无需将文件移动到发行版驱动器以增加加载时间
- qt - 如何更改滚动视图以相反方向滚动
- rundeck - rundeck 工作流程的行为,带有“在失败的步骤处停止”和“并行策略”
- python - 如果值有新数据,则迭代熊猫数据框的列填充
- c# - 将 Level 设置为具有多个层的嵌套列表
- r - ggplot中带有单列的堆叠条形图?
- python-3.x - DBSCAN 返回 TypeError:无效的类型提升