【发布时间】:2013-03-19 14:16:06
【问题描述】:
我一直在寻找一种在不同运行空间之间传递事件的方法,但还没有找到。下面的片段创建了一个后台运行空间,它显示了一个只有一个按钮的小窗口。 OnClick 它将发布主运行空间应接收的事件:
$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"
...这也不起作用。我想因为我不能从 Child-RS 中的 parent-RS 调用函数。奇怪的是,在某些情况下,Get-Event 返回了一些“TestClicked”事件,但我无法回忆或重现......
编辑:显然上述方法在某种程度上起作用 - 我又遇到了我的问题,它与一些功能结合使用。大多数人都知道 Scripting Guy 在 Powershell-BLog 上发布的 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)
}
}
}
}
很抱歉用德语发表评论。
现在在函数调用中使用带有“GuI-events”的此函数(也使用发送“GUIloaded”和“WindowClosed”事件的技术),似乎无法从 gui-events 中发送事件.像这样:
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 是一个全局的 SyncHasTable。奇怪的是,那些“$ParentHost.UI.WriteLine()”调用工作并在父控制台上产生输出。 “GenerateEvent”调用似乎根本不起作用。 Get-Event 既不显示任何事件,也不会触发通过 Register-EngineEvent 设置的操作。
对此有什么想法吗?
【问题讨论】:
标签: events powershell inter-process-communicat runspace