PowerShell 类和 .NET 事件

问题描述 投票:0回答:2

我正在使用小型 WPF GUI 编写 PowerShell 脚本。我的代码被组织在一个类中,从中创建一个单例。我读到事件处理程序脚本块内的 $this 指向事件发送者而不是我的包含类实例。如何从事件处理程序访问我的类实例?

例如。

class MyClass {

    $form  #Reference to the WPF form


    [void] StartAction([object] $sender, [System.Windows.RoutedEventArgs] $e) {
        
        ...
    }


    [void] SetupEventHandlers() {

        $this.form.FindName("BtnStartAction").add_Click({ 
            param($sender, $e) 
            # !!!! Does not work, $this is not my class instance but the event sender !!!!
            $this.StartAction($sender, $e) 
        })

    }


    [void] Run() {

        $this.InitWpf() #Initializes the form from a XAML file.

        $this.SetupEventHandlers()

        ...
    }
}


$instance = [MyClass]::new()
$instance.Run()
powershell
2个回答
4
投票
  • 确实,脚本块中的

    自动$this变量充当.NET事件处理程序指的是事件发送者

  • 如果从 PowerShell 自定义 class

    的方法内部设置 事件处理程序脚本块,$this
    阴影
    事件发送者定义类中的常用定义
    方法(指手头的类实例)。

有两个解决方法,都依赖于PowerShell的动态作用域,它允许后代作用域查看来自祖先作用域的变量。

  • 最好使用
    Get-Variable
    -Scope 1
    引用 parent 作用域的
    $this
    (事件处理程序脚本块在调用者的 子作用域中运行)。
    [void] SetupEventHandlers() {

        $this.form.FindName("BtnStartAction").add_Click({ 
            param($sender, $e) 
            # Get the value of $this from the parent scope.
            (Get-Variable -ValueOnly -Scope 1 this).StartAction($sender, $e) 
        })

    }
  • 或者,更直接地利用动态作用域,您可以采用 Abdul Niyas P M 的建议,即 在调用者的作用域中定义一个 辅助变量,以不同的名称引用自定义类实例 ,您可以在从同一个设置的可能多个事件处理程序脚本块中引用它方法:

    • 请注意,脚本块上需要调用

      .GetNewClosure()
      ,以便使辅助变量在脚本块内可用。Sven 致敬。

    • 请注意,您需要将此技术应用于设置事件处理程序的每个方法,因此如果有多个方法(不是您的情况),则

      (Get-Variable -ValueOnly -Scope 1 this)
      选项是更简单的解决方案。

    [void] SetupEventHandlers() {

        # Set up a helper variable that points to $this
        # under a different name.
        $thisClassInstance = $this

        # Note the .GetNewClosure() call.
        $this.form.FindName("BtnStartAction").add_Click({ 
            param($sender, $e) 
            # Reference the helper variable.             
            $thisClassInstance.StartAction($sender, $e) 
        }.GetNewClosure())

    }

另请注意,从 PowerShell 7.2 开始,您不能直接使用自定义类 方法 作为事件处理程序 - 此答案 显示了解决方法,这也需要解决

$this
阴影问题。


独立的示例代码

  • 重要:在运行下面的代码之前,请确保您已在会话中运行以下命令:

    # Load WPF assemblies.
    Add-Type -AssemblyName PresentationCore, PresentationFramework
    
  • 不幸的是,将此调用放在包含以下代码的脚本中不起作用,因为

    class
    定义是在解析时处理的,即Add-Type
    命令运行之前,而所有类引用的 .NET 类型必须已经是
    已加载 - 请参阅
    此答案

      虽然用
    • using assembly
       语句代替 
      Add-Type
       调用可能会在未来某一天
      成为一种解决方案(它们也在解析时处理),但它们的类型目前直到运行时才被发现,导致同样的问题 - 请参阅 GitHub问题#3641;作为第二个问题,众所周知的程序集当前不能仅通过文件name引用;例如,与 using assembly PresentationCore.dll 不同,
      Add-Type -AssemblyName PresentationCore
      有效 - 请参阅
      GitHub 问题 #11856
  • # IMPORTANT: # Be sure that you've run the following to load the WPF assemblies # BEFORE calling this script: # Add-Type -AssemblyName PresentationCore, PresentationFramework class MyClass { $form #Reference to the WPF form [void] InitWpf() { [xml] $xaml=@" <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:Test" Title="MainWindow" Height="500" Width="500"> <Grid> <Button x:Name="BtnStartAction" Content="StartAction" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" IsDefault="True" Height="22" Margin="170,0,0,0" /> <TextBox x:Name="Log" Height="400" TextWrapping="Wrap" VerticalAlignment="Top" AcceptsReturn="True" AcceptsTab="True" Padding="4" VerticalScrollBarVisibility="Auto" Margin="0,40,0,0"/> </Grid> </Window> "@ $this.form = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml)) } [void] StartAction([object] $sender, [System.Windows.RoutedEventArgs] $e) { $tb = $this.form.FindName("Log") $tb.Text += $e | Out-String } [void] SetupEventHandlers() { $btn = $this.form.FindName("BtnStartAction") # -- Solution with Get-Variable $btn.add_Click({ param($sender, $e) (Get-Variable -ValueOnly -Scope 1 this).StartAction($sender, $e) }) # -- Solution with helper variable. # Note the need for .GetNewClosure() # Helper variable that points to $this under a different name. $thisClassInstance = $this $btn.add_Click({ param($sender, $e) $thisClassInstance.StartAction($sender, $e) }.GetNewClosure()) } [void] Run() { $this.InitWpf() #Initializes the form from a XAML file. $this.SetupEventHandlers() $this.form.ShowDialog() } } $instance = [MyClass]::new() $instance.Run()
上面演示了两种解决方法:当您按下 
StartAction

按钮时,两个事件处理程序都应将它们各自收到的事件参数对象添加到文本框中,如以下屏幕截图所示:

screenshot


0
投票

class MyClass { $form #Reference to the WPF form [void] StartAction([System.Windows.RoutedEventArgs] $e) { #$this ... } [void] SetupEventHandlers() { $this.form.FindName("BtnStartAction").add_Click({ param($sender, $e) ([MyClass]$sender).StartAction($e) }) } [void] Run() { $this.InitWpf() $this.SetupEventHandlers() } } $instance = [MyClass]::new() $instance.Run()

编辑1:
您可以尝试创建一个委托来引用此类的专用方法吗?

class MyClass { $form #Reference to the WPF form [void] StartAction() { #$this ... } [void] SetupEventHandlers() { $handler = [System.EventHandler]::CreateDelegate([System.EventHandler], $this, "Handler") $this.form.FindName("BtnStartAction").add_Click($handler) } [void] Handler ([System.Object]$sender, [System.EventArgs]$e) { $this.StartAction() } [void] Run() { $this.InitWpf() $this.SetupEventHandlers() } } $instance = [MyClass]::new() $instance.Run()

	
© www.soinside.com 2019 - 2024. All rights reserved.