我正在使用小型 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的动态作用域,它允许后代作用域查看来自祖先作用域的变量。
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
按钮时,两个事件处理程序都应将它们各自收到的事件参数对象添加到文本框中,如以下屏幕截图所示:
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()