繁体   English   中英

powershell多运行空间事件传递

[英]powershell multi-runspace event passing

我一直在寻找一种在不同的运行空间之间传递事件但尚未找到任何运行空间的方法。 以下剪辑创建了一个后台运行空间,它显示了一个只有一个按钮的小窗口。 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中的父-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设置的操作。

有什么想法吗?

我能够使用以下代码在父运行空间上接收事件:

$Global:x = [Hashtable]::Synchronized(@{})
$x.Host = $Host
$rs = [RunspaceFactory]::CreateRunspace()
$rs.ApartmentState,$rs.ThreadOptions = "STA","ReUseThread"
$rs.Open()
$rs.SessionStateProxy.SetVariable("x",$x)
$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.FindName('test')

$x.test.Add_Click({
    $x.Host.Runspace.Events.GenerateEvent( "TestClicked", $x.test, $null, "test event") 
} )  

$x.w.ShowDialog()
})
$cmd.Runspace = $rs
$handle = $cmd.BeginInvoke()
Register-EngineEvent -SourceIdentifier "TestClicked" -Action {$Global:x.host.UI.Write("Event Happened!")}

这是一篇关于使用同步哈希表在运行空间之间传递数据的文章,如果有帮助:

http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM