【问题标题】:PowerShell Windows Forms WrapperPowerShell Windows 窗体包装器
【发布时间】:2018-04-10 04:37:02
【问题描述】:

在 PowerShell 中,使用 Windows Forms 为小型 cmdlet 构建用户界面是很常见的,但为此所需的语法通常是部分冗余和冗长的。这就引出了一个问题: 有没有办法最小化所需的代码,或者是否存在用于 PowerShell 的 Windows 窗体包装器来减少冗长和冗余的语法?
我不是在寻找ShowUI,因为考虑到它基于Windows Presentation Foundation(另请参阅: WPF vs WinForms) 以及它涉及 PowerShell module 的事实,这使得它变得更加困难部署它而不是包装函数。

【问题讨论】:

    标签: forms powershell controls wrapper


    【解决方案1】:

    在很多情况下,不需要包装器以使您的代码不那么冗长,例如冗长的 WinForms PowerShell 脚本here。像这样的代码片段:

    $System_Windows_Forms_Padding = New-Object System.Windows.Forms.Padding
    $System_Windows_Forms_Padding.All = 3
    $System_Windows_Forms_Padding.Bottom = 3
    $System_Windows_Forms_Padding.Left = 3
    $System_Windows_Forms_Padding.Right = 3
    $System_Windows_Forms_Padding.Top = 3
    $Tab1.Padding = $System_Windows_Forms_Padding
    

    在 WinForms 中可以很容易地简化为一行:

    $Tab1.Padding = 3
    

    如果每一边的填充不同,PowerShell 会自动转换:

    $Tab1.Padding = "4, 6, 4, 6"
    

    注意: PowerShell 不会转换 $Tab1.Padding = "3"$Tab1.Padding = "4, 6"

    尽管如此,创建 Windows 窗体控件的本地方式与 DRY (don't repeat yourself) programming 相去甚远。虽然(多个)属性可以在创建时添加(使用:New-Object System.Windows.Forms.Button -Property @{Location = "75, 120"; Size = "75, 23"}),但 多个 属性不能在以后的状态下立即设置。除此之外,在创建窗口窗体控件的中间添加事件1、子控件和容器属性(例如RowSpan)或任何组合都不是快速和容易的。最重要的是,您必须一遍又一遍地引用 windows 窗体控件来设置它的属性和更多属性(例如 $OKButton.<property> = ...,就像在这个 example 中一样):

    $OKButton = New-Object System.Windows.Forms.Button
    $OKButton.Location = New-Object System.Drawing.Point(75,120)
    $OKButton.Size = New-Object System.Drawing.Size(75,23)
    $OKButton.Text = "OK"
    

    这就是为什么我创建了一个可重用的 PowerShell 表单控件包装器,它可以让您最大限度地减少 Windows 窗体 (WinForms) 代码的本质。

    1) 除非您使用 On<event> 方法,另请参阅:addEventListener vs onclick

    PowerShell Form-Control Wrapper

    Function Form-Control {
        [CmdletBinding(DefaultParametersetName='Self')]param(
            [Parameter(Position = 0)]$Control = "Form",
            [Parameter(Position = 1)][HashTable]$Member = @{},
            [Parameter(ParameterSetName = 'AttachChild',  Mandatory = $false)][Windows.Forms.Control[]]$Add = @(),
            [Parameter(ParameterSetName = 'AttachParent', Mandatory = $false)][HashTable]$Set = @{},
            [Parameter(ParameterSetName = 'AttachParent', Mandatory = $false)][Alias("Parent")][Switch]$GetParent,
            [Parameter(ParameterSetName = 'AttachParent', Mandatory = $true, ValueFromPipeline = $true)][Windows.Forms.Control]$Container
        )
        If ($Control -isnot [Windows.Forms.Control]) {Try {$Control = New-Object Windows.Forms.$Control} Catch {$PSCmdlet.WriteError($_)}}
        $Styles = @{RowStyles = "RowStyle"; ColumnStyles = "ColumnStyle"}
        ForEach ($Key in $Member.Keys) {
            If ($Style = $Styles.$Key) {[Void]$Control.$Key.Clear()
                For ($i = 0; $i -lt $Member.$Key.Length; $i++) {[Void]$Control.$Key.Add((New-Object Windows.Forms.$Style($Member.$Key[$i])))}
            } Else {
                Switch (($Control | Get-Member $Key).MemberType) {
                    "Property"  {$Control.$Key = $Member.$Key}
                    "Method"    {Invoke-Expression "[Void](`$Control.$Key($($Member.$Key)))"}
                    "Event"     {Invoke-Expression "`$Control.Add_$Key(`$Member.`$Key)"}
                    Default     {Write-Error("The $($Control.GetType().Name) control doesn't have a '$Key' member.")}
                }
            }
        }
        $Add | ForEach {$Control.Controls.Add($_)}
        If ($Container) {$Container.Controls.Add($Control)}
        If ($Set) {$Set.Keys | ForEach {Invoke-Expression "`$Container.Set$_(`$Control, `$Set.`$_)"}}
        If ($GetParent) {$Container} Else {$Control}
    }; Set-Alias Form Form-Control
    

    语法

    创建控件
    <System.Windows.Forms.Control> = Form-Control [-Control <String>] [-Member <HashTable>]

    修改控件
    <Void> = Form-Control [-Control <System.Windows.Forms.Control>] [-Member <HashTable>]

    向容器添加(新)控件
    <System.Windows.Forms.Control> = Form-Control [-Control <String>|<System.Windows.Forms.Control>] [-Member <HashTable>] [-Add <System.Windows.Forms.Control[]>]

    将容器连接到(新)控件
    <System.Windows.Forms.Control> = <System.Windows.Forms.Control> | Form-Control [-Control <String>|<System.Windows.Forms.Control>] [-Member <HashTable>] [-Set <HashTable>] [-PassParent]

    参数

    -Control <String>|<System.Windows.Forms.Control> (位置0,默认:Form
    -Control 参数接受 Windows 表单控件类型名称 ([String]) 或现有表单控件 ([System.Windows.Forms.Control])。 Windows 窗体控件类型名称如Form, Label, TextBox, Button, Panel, ... 等。 如果提供了 Windows 窗体控件类型名称 ([String]),则包装器将创建并返回一个新的 Windows 窗体控件,其属性和设置由其余参数定义。
    如果提供了现有的 Windows 窗体控件 ([System.Windows.Forms.Control]),则包装器将使用其余参数定义的属性和设置来更新现有的 Windows 窗体控件。

    -Member <HashTable> (位置 1)
    设置属性值、调用方法并在新对象或现有对象上添加事件。

    • 如果哈希名称代表控件上的property,例如Size = "50, 50",该值将赋给控件属性值。

    • 如果哈希名称代表控件上的method,例如Scale = {1.5, 1.5},将使用参数的值调用控制方法。

    • 如果哈希名称代表控件上的event,例如Click = {$Form.Close()},值([ScriptBlock])将添加到控件事件中。

    两个集合属性 ColumnStylesRowStyles 被简化,特别是对于被认为是 WPF Grid 控件的一般替代品的 TableLayoutPanel 控件: - ColumnStyles 属性清除所有列宽并使用哈希值提供的 ColumnStyle 数组重置它们。 - RowStyles 属性,清除所有行高度并使用哈希值提供的 RowStyle 数组重置它们。
    注意:如果要添加或插入单个特定ColumnStyle 或 RowStyle 项,需要回退到原生语句,例如:[Void]$Control.Control.ColumnStyles.Add((New-Object Windows.Forms.ColumnStyle("Percent", 100)).

    -Add <Array>
    -Add参数向当前控件添加一个或多个子控件。
    注意:-add参数不能在容器是管道时使用到控件。

    -Container <System.Windows.Forms.Control> (来自管道)
    父容器通常由管道提供:$ParentContainer | Form $ChildControl 并将(新)子控件附加到相关容器。

    -Set <HashTable>
    -Set 参数设置(SetCellPositionSetColumnSetColumnSpanSetRowSetRowSpanSetStyle)与其父面板容器相关的特定子控件属性,例如.Set RowSpan = 2
    注意:-set 列和行参数只能在容器通过管道传输到控件时使用。

    -GetParent
    默认情况下,(子)控件将由form-control 函数返回,除非提供了-GetParent 开关,它将返回父容器。 注意:-set 列和行参数只能在容器通过管道传输到控件时使用。

    示例

    有两种方法可以设置 Windows 窗体层次结构:

    1. 向容器添加(新)控件
    2. 通过管道将容器连接到(新)控件

    向容器添加(新)控件
    对于此示例,我使用 PowerShell Form-Control 包装器重新设计了 docs.microsoft.com 上的 Creating a Custom Input Box

    $TextBox      = Form TextBox @{Location = "10, 40";   Size = "260, 20"}
    $OKButton     = Form Button  @{Location = "75, 120";  Size = "75, 23"; Text = "OK";     DialogResult = "OK"}
    $CancelButton = Form Button  @{Location = "150, 120"; Size = "75, 23"; Text = "Cancel"; DialogResult = "Cancel"}
    $Result = (Form-Control Form @{
            Size = "300, 200"
            Text = "Data Entry Form"
            StartPosition = "CenterScreen"
            KeyPreview = $True
            Topmost = $True
            AcceptButton = $OKButton
            CancelButton = $CancelButton
        } -Add (
            (Form Label    @{Text = "Please enter the information below:"; Location = "10, 20"; Size = "280, 20"}),
            $TextBox, $OKButton, $CancelButton
        )
    ).ShowDialog()
    
    if ($result -eq [System.Windows.Forms.DialogResult]::OK)
    {
        $x = $TextBox.Text
        $x
    }
    

    注意 1: 虽然添加控件看起来更有条理,尤其是对于小型表单,但缺点是不能调用与父容器和子控件相关的方法(如 @ 987654402@)。
    注意 2: 如果尝试直接在父容器 (像上面的Label控件)。除了引用这样的孩子更困难(例如$OKButton vs. $Form.Controls["OKButton"],假设您设置了按钮属性Name = "OKButton

    将容器连接到(新)控件
    对于这个例子,我创建了一个用户界面来测试dockproperty 的行为。表单如下所示:

    为此所需的 PowerShell 表单控制代码:

    $Form   = Form-Control Form @{Text = "Dock test"; StartPosition = "CenterScreen"; Padding = 4; Activated = {$Dock[0].Select()}}
    $Table  = $Form  | Form TableLayoutPanel @{RowCount = 2; ColumnCount = 2; ColumnStyles = ("Percent", 50), "AutoSize"; Dock = "Fill"}
    $Panel  = $Table | Form Panel @{Dock = "Fill"; BorderStyle = "FixedSingle"; BackColor = "Teal"} -Set @{RowSpan = 2}
    $Button = $Panel | Form Button @{Location = "50, 50"; Size = "50, 50"; BackColor = "Silver"; Enabled = $False}
    $Group  = $Table | Form GroupBox @{Text = "Dock"; AutoSize = $True}
    $Flow   = $Group | Form FlowLayoutPanel @{AutoSize = $True; FlowDirection = "TopDown"; Dock = "Fill"; Padding = 4}
    $Dock   = "None", "Top", "Left", "Bottom", "Right", "Fill" | ForEach {
        $Flow | Form RadioButton @{Text = $_; AutoSize = $True; Click = {$Button.Dock = $This.Text}}
    }
    $Close  = $Table | Form Button @{Text = "Close"; Dock = "Bottom"; Click = {$Form.Close()}}
    $Form.ShowDialog()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-01
      • 2018-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-10
      • 1970-01-01
      相关资源
      最近更新 更多