在 PowerShell 中将对象传递给函数时遇到问题

Trouble Passing Objects to Functions in PowerShell

提问人:Mikepedia 提问时间:11/5/2023 更新时间:11/5/2023 访问量:51

问:

新用户在这里。我不做任何编程/编码,教育经验有限,所以我所知道的大部分都是从修补中学到的。我正在开发一个构建 GUI 的 PowerShell 脚本,并且在根据编辑另一个 GUI 项目更新一个 GUI 项目时遇到了一些问题。我已经简化了它以显示我遇到的问题。

下面是被调用的函数:

function GUITitleTextBox_TextChanged
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$True, Position=0)][System.Object]    $Sender,
        [parameter(Mandatory=$True, Position=1)][System.EventArgs] $EventArguments,
        [parameter(Mandatory=$True, Position=2)][String]           $Text,
        [parameter(Mandatory=$False, Position=3)][System.Object]   $ControlToUpdate
    )
    
    Write-Host $Sender.Text
    Write-Host $Text
    Write-Host $ControlToUpdate.Text
}

下面是创建对象并添加事件的相关代码:

#Add the GUI Title textbox
$GUITitleTextBox       = New-Object System.Windows.Forms.TextBox
$GUITitleTextBox.Text  = "Default Form"
$GUITitleTextBox.Width = 240
$GUITitleTextBox.Add_TextChanged({GUITitleTextBox_TextChanged -Sender $this -EventArguments $_ -Text $this.Text -ControlToUpdate $GUITitleTextBox}) #Add an event when text changes.
$Form.Controls.Add($GUITitleTextBox) #Add the textbox to the Form

下面是控制台输出:

Default For
Default For

Default Fo
Default Fo

Default F
Default F

传递给函数的任何内容都$this正常工作,如发送数据的两个 Write-Host 命令所示,但是当传递带有变量名称 ($GUITitleTextBox) 的对象时,它不起作用。我可以直接在创建对象的代码下方添加一个 Write-Host $GUITitletextBox.Text。如果在声明和实例化$GUITitleTextBox时,我执行 $script:GUITitleTextBox 将范围设置为整个脚本,那么我当然可以更改 Text 属性。

更复杂的是,在不同的脚本上,我能够按照相同的格式将 Windows 窗体对象传递给函数。

其他脚本中的函数:

function ContainerDimensionControlChanged()
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$True, Position=0)] [System.Object] $CallingControl,
        [parameter(Mandatory=$True, Position=1)] [System.Object] $Container,
        [parameter(Mandatory=$True, Position=2)] [string]        $Axis,
        [parameter(Mandatory=$False, Position=3)][System.Object] $ControlToUpdate
    )

    <Removed because it's not relevant>

    $ControlToUpdate.Value = $Container.$Axis
}

下面是调用它的代码($Panel 和 $WidthTextBox 都是在运行此代码的同一函数中之前/稍后创建的其他表单元素):

$WidthSlider = New-Object System.Windows.Forms.TrackBar
$WidthSlider.Top = 45
$WidthSlider.Left = 330
$WidthSlider.Name = "WidthSlider"
$WidthSlider.AutoSize = $true
$WidthSlider.Minimum = 5
$WidthSlider.Maximum = 750
$WidthSlider.Value = $PanelDefaultWidth
$WidthSlider.Add_ValueChanged({ContainerDimensionControlChanged $This $Panel "Width" $WidthTextBox})

那么,为什么第二个有效而第一个无效呢?我错过了什么?

PowerShell WinForms 按引用传递

评论


答:

1赞 suchislife 11/5/2023 #1

PowerShell 的变量在事件处理程序的脚本块中使用时并不总是你所期望的。$this

在你的第一个脚本中,不是指运行脚本块的上下文,而不是你的文本框,这就是它没有按预期工作的原因。相反,请使用传递给函数并引用引发事件的控件。$this$GUITitleTextBox$sender

下面是更正后的事件处理程序添加行:

$GUITitleTextBox.Add_TextChanged({
    GUITitleTextBox_TextChanged -Sender $GUITitleTextBox -EventArguments $_ -Text $GUITitleTextBox.Text -ControlToUpdate $GUITitleTextBox
})

根据评论更新

在 PowerShell 中,当你在事件中使用脚本块(匿名函数)时,除非强制它们与语句一起使用或适当地限定它们的作用域,否则不会在注册事件时捕获其中使用的变量。下面介绍如何在事件处理程序中正确地将 传递给函数:use$ControlToUpdate

  • 使用该方法创建一个闭包,该闭包将变量的当前状态绑定到事件处理程序的脚本块。GetNewClosure()

下面是完整的更正代码,以确保函数接收到:$ControlToUpdate

function GUITitleTextBox_TextChanged {
    param (
        [Parameter(Mandatory=$True)][System.Object] $Sender,
        [Parameter(Mandatory=$True)][System.EventArgs] $EventArguments,
        [Parameter(Mandatory=$True)][String] $Text,
        [Parameter(Mandatory=$True)][System.Windows.Forms.Control] $ControlToUpdate
    )
    
    $ControlToUpdate.Text = $Text
}

$GUITitleTextBox = New-Object System.Windows.Forms.TextBox
$GUITitleTextBox.Text = "Default Form"
$GUITitleTextBox.Width = 240

# Ensure that the $GUITitleTextBox is captured in the script block's closure
$handler = {
    GUITitleTextBox_TextChanged -Sender $GUITitleTextBox -EventArguments $_ -Text $GUITitleTextBox.Text -ControlToUpdate $GUITitleTextBox
}.GetNewClosure()

$GUITitleTextBox.Add_TextChanged($handler)

$Form = New-Object System.Windows.Forms.Form
$Form.Controls.Add($GUITitleTextBox)

# Other form setup here...

# Show the form
$Form.ShowDialog()

通过使用 ,我们确保用作事件处理程序的脚本块具有捕获的当前状态,因此当文本更改时,它会将当前控件传递给函数。GetNewClosure()$GUITitleTextBox

评论

0赞 Mikepedia 11/5/2023
感谢您的回复!我没有遇到正确传递$this的问题,但是参数ControlToUpdate。该函数未接收传递的对象。
0赞 Mikepedia 11/5/2023
感谢您在我发表评论后如此迅速地回复您的编辑。这是有道理的,我没有意识到这一点。我明天会尝试这个并报告。你真棒!
0赞 Mikepedia 11/5/2023
这正是我需要的!谢谢!我还发现,我需要稍后将 $handler 和 $GUITitleTextBox.Add_TextChanged ($handler) 放在脚本中,以确保实际创建我尝试修改的任何对象。你真棒!
0赞 suchislife 11/5/2023
真棒男人。继续codin'。PowerShell 也确实在我身上成长。尤其是具有完全透明代码的 GUI 表单,它是一个脚本等等。
0赞 Mikepedia 11/7/2023
谢谢!但是,现在我遇到了一个新问题(当我认为这是一个单独的问题时,请在此处描述:stackoverflow.com/questions/77432941/...引发的异常仅影响使用 GetNewClosure() 的侦听器,并且仅在通过右键单击 -> Run With PowerShell 运行脚本时,不会影响任何其他方式。知道为什么会这样吗?