我一直在寻找一种方法,在不同的runspace之间传递事件,但是没有找到任何方法。以下代码段创建了一个后台runspace,其中显示了一个仅有一个按钮的小窗口。点击该按钮后,它将发布一个事件,主要的runspace应该接收到该事件:
但是那样行不通。我将最后几行改成了这样:
需要注意的是,$ui是一个全局的SyncHashTable。奇怪的是,那些"$ParentHost.UI.WriteLine()"调用能够在父控制台上工作并产生输出。而"GenerateEvent"调用似乎根本不起作用。既没有Get-Event显示任何事件,也没有通过Register-EngineEvent设置的操作被触发。
这方面有什么想法吗?
$Global:x = [Hashtable]::Synchronized(@{})
$x.Host = $Host
$Global:rs = [RunspaceFactory]::CreateRunspace()
$rs.ApartmentState,$rs.ThreadOptions = "STA","ReUseThread"
$rs.Open()
$rs.SessionStateProxy.SetVariable("x",$x)
$Global:cmd = [PowerShell]::Create().AddScript(@'
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
$x.w = [Windows.Markup.XamlReader]::Parse(@"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MaxWidth="800" WindowStartupLocation="CenterScreen" WindowStyle="None" SizeToContent="WidthAndHeight">
<Button Name="test" Content="Starte Installation"/>
</Window>
"@)
$x.test = $x.w.Content.FindName('test')
$x.test.Add_Click( {New-Event -SourceIdentifier "TestClicked" -MessageData "test event"} )
$x.w.ShowDialog()
'@)
$cmd.Runspace = $rs
$null = $cmd.BeginInvoke()
while(!($x.ContainsKey("test"))) {Sleep -Milliseconds 500}
Register-EngineEvent -SourceIdentifier "TestClicked" -Action {$event}
但是那样行不通。我将最后几行改成了这样:
$x.test.Add_Click( {$x.Host.Runspace.Events.GenerateEvent( "TestClicked", $x.test, $null, "test event") } )
$x.w.ShowDialog()
'@)
$cmd.Runspace = $rs
$null = $cmd.BeginInvoke()
Wait-Event -SourceIdentifier "TestClicked"
...这个方法也没有起作用。我猜是因为我不能在子RS中调用父RS中的函数。很奇怪,我遇到过一些情况,Get-Event返回了一些“TestClicked”事件,但是我无法回忆起来或重现...
编辑:显然上述方法有些作用——我又遇到了问题,与一些函数有关。大多数人都知道由Powershell-Blog上的Scripting Guy发布的Show-Control函数。由于我宁愿显示整个GUI而不是单个控件,我对其进行了修改:
Add-Type –assemblyName PresentationFramework,PresentationCore,WindowsBase,"System.Windows.Forms"
<# Die folgende Funktion zeigt eine GUI an. Die Informationen über die GUI
müssen in XAML formuliert sein. Sie können als String oder als Dateiname
übergeben werden.
Die Funktion erlaubt die Übergabe von WindowProperties als Hashtable
(-> siehe [System.Windows.Window]), von gemeinsamen Objekten in einer syn-
chronized HashTable und von Ereignissen, die mit den entsprechenden im xaml
definierten Objekten verbunden werden.
Der Switch "backgroundrunspace" macht, was sein Name sagt: er öffnet die GUI
im Hintergrund, sodass das Hauptprogramm weiterlaufen kann.
#>
function Show-Control {
param(
[Parameter(Mandatory=$true,ParameterSetName="XamlString",ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string] $xaml,
[Parameter(Mandatory=$true,ParameterSetName="XamlFile",ValueFromPipeline=$false,ValueFromPipelineByPropertyName=$true)]
[string] $xamlFile,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Hashtable] $event,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Hashtable] $windowProperties,
# If this switch is set, Show-Control will run the control in the background runspace
[switch] $backgroundRunspace,
# To share Variables with the background runspace
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Hashtable] $sharedVariables
)
Begin
{ # If it's in a background runspace, create a runspace and populate the runspace with Show-Control.
if ($backgroundRunspace) {
$newRunspace =[RunspaceFactory]::CreateRunspace()
$newRunspace.ApartmentState,$newRunspace.ThreadOptions = "STA","ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("ParentHost",$Host)
if ($sharedVariables) {
$newRunspace.SessionStateProxy.SetVariable("sharedVariables",$sharedVariables)
}
$selfDefinition = "function Show-Control { $((Get-Command Show-Control).Definition) }"
$psCmd = [PowerShell]::Create().AddScript($selfDefinition, $false)
$psCmd.Runspace = $newRunspace
$null = $psCmd.Invoke()
} else {
$window = New-Object Windows.Window
$window.SizeToContent = "WidthAndHeight"
# das Fenster in die sharedVariables aufnehmen
if ($sharedVariables) {
$sharedVariables.window=$window
}
if ($windowProperties) {
foreach ($kv in $windowProperties.GetEnumerator()) {
$window."$($kv.Key)" = $kv.Value
}
}
$visibleElements = @()
$windowEvents = @()
}
}
Process
{
if ($backgroundRunspace) { # Invoke the command, using each parameter from commandlineparameters
$psCmd = [Powershell]::Create().AddCommand("Show-Control",$false)
$null = $psBoundParameters.Remove("BackgroundRunspace")
$null = $psCmd.AddParameters($psBoundParameters)
<# foreach ($namedArg in $psBoundParameters.GetEnumerator()) {
$null = $psCmd.AddParameter($namedArg.Key, $namedArg.Value)
}#>
$psCmd.Runspace = $newRunspace
$null = $psCmd.BeginInvoke()
} else {
# falls eine xaml-datei, dann diese in den xaml-string laden
if($PSCmdlet.ParameterSetName -eq "xamlFile") {
$xaml = [string](Get-Content -Encoding UTF8 -ReadCount 0 -Path $xamlFile)
}
# XAML parsen und so zu Objekten machen
$window.Content=([system.windows.markup.xamlreader]::parse($xaml))
# wir merken uns, ob wir ein Loaded-Event verknüpft haben
$guiloaded_notadded = $true
# event-hashtable parsen
if($event) {
foreach ($singleEvent in $event.GetEnumerator()) {
if ($singleEvent.Key.Contains(".")) {
# auseinander nehmen von Objektname und Eventname
$targetName = $singleEvent.Key.Split(".")[0].Trim()
$eventName = $singleEvent.Key.Split(".")[1].Trim()
if ($singleEvent.Key -like "Window.*") {
$target = $window
} else {
$target = $window.Content.FindName($targetName)
}
} else { # kein Objektname -> das Fenster selbst ist das Objekt...
$target = $window
$eventName = $singleEvent.Key
}
# Prüfe, ob dieses Objekt auch dieses Event unterstützt, wenn ja: Skriptblock mit dem Event verheiraten
if( Get-Member -InputObject $target -MemberType Event -Name $eventName ) {
$eventMethod = $target."add_$eventName"
if( ($targetName -eq "Window") -and ($eventName -eq "Loaded") -and ($ParentHost)) {
$eventScript = [ScriptBlock]::Create( $singleEvent.Value.ToString() + "`n`$null = `$ParentHost.Runspace.Events.GenerateEvent('GUIloaded',$null,$null,$null)" )
$eventMethod.Invoke( $ExecutionContext.InvokeCommand.NewScriptBlock($eventScript) )
$guiloaded_notadded = $false
} else {
$eventMethod.Invoke( $ExecutionContext.InvokeCommand.NewScriptBlock($singleEvent.Value) )
}
}
}
}
# wenn background (können wir hier nur durch Abfragen von "ParentHost" prüfen) und kein "Loaded" event,
# dann das GUIloaded-event mit dem window.loaded event senden.
if(($guiloaded_notadded) -and ($ParentHost)) {
$window.add_Loaded( {
$null = $ParentHost.Runspace.Events.GenerateEvent('GUIloaded',$null,$null,$null)
} )
}
# benannte xaml-Objekte in die sharedVariables bringen...
if($sharedVariables) {
$match = [regex]::Matches($xaml,' [x]?[:]?Name="(\w+)"')
foreach ($m in $match)
{
$name = [string]($m.Groups[1].Value)
$sharedVariables.Add($name,$window.Content.FindName($name))
}
}
}
}
End
{
if ($backgroundRunspace) {
$newRunspace
} else {
$null = $window.ShowDialog()
$window.Tag
if($ParentHost) {
$null = $ParentHost.Runspace.Events.GenerateEvent('WindowClosed',$null,$null,$window.Tag)
}
}
}
}
非常抱歉之前用德语评论了。
现在使用这个函数(它还使用发送“GUIloaded”和“WindowClosed”事件的技术)并在函数调用中使用“GuI-events”,似乎无法从GUI事件中发送事件。就像这样:
Show-Control -xamlfile ($PSScriptRoot+"\WimMounter.xaml") -backgroundRunspace -sharedVariables $ui -event @{
"Loaded" = {
$Global:fdlg = New-Object System.Windows.Forms.OpenFileDialog
$fdlg.CheckFileExists = $true
$fdlg.Filter = "WIM-Image Files|*.wim"
$fdlg.Title = "Bitte WIM-Datei auswählen"
$Global:ddlg = New-Object System.Windows.Forms.FolderBrowserDialog
$ddlg.Description = "Bitte Verzeichnis zum Mounten des Images auswählen"
$ui.fn = ""
$ui.in = ""
$ui.md = ""
}
"selectFile.Click" = {
if($Global:fdlg.ShowDialog() -eq "OK") {
$sharedVariables.ImageFile.Text = $fdlg.FileName.Trim()
$sharedVariables.pl.Content = ("Ausgewählt: `""+$fdlg.FileName.Trim()+"`" - wird untersucht...")
$sharedVariables.pb.IsIndeterminate = $true
$sharedVariables.ImageName.Items.Clear()
$ParentHost.UI.WriteLine("gleich gibbs 'ImageSelected'")
$ParentHost.Runspace.Events.GenerateEvent("ImageSelected",$null,$null,($fdlg.FileName.Trim()))
}
}
}
需要注意的是,$ui是一个全局的SyncHashTable。奇怪的是,那些"$ParentHost.UI.WriteLine()"调用能够在父控制台上工作并产生输出。而"GenerateEvent"调用似乎根本不起作用。既没有Get-Event显示任何事件,也没有通过Register-EngineEvent设置的操作被触发。
这方面有什么想法吗?