Janelas PowerShell Forms Wrapper

em PowerShell é muito comum usar As formas do Windows para construir uma Interface de utilizador para pequenos cmdlets, mas a sintaxe necessária para isto é muitas vezes parcialmente redundante e verbosa silenciosa. Isto leva à questão: Existe uma maneira de minimizar o código necessário ou existe uma capa do Windows para PowerShell para reduzir a sintaxe verbal e redundante?
Eu não estou procurando o ShowUI como esta solução é muito pesada considerando-a com base em Janelas Presentation Foundation (see also: WPF vs WinForms) {[[3]} and the fact that it concerns a PowerShell module which makes it more difficult to deploy it than a wrapper function.

Author: iRon, 2017-10-28

1 answers

Em muitos casos, uma embalagem não é necessária para tornar o seu código menos descritivo, por exemplo, a longa escrita PowerShell WinForms aqui. Peças de código como esta:

$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

Pode ser facilmente simplificado em WinForms para uma única linha:

$Tab1.Padding = 3
E se o enchimento fosse diferente para cada lado, o PowerShell converter-se-ia automaticamente:
$Tab1.Padding = "4, 6, 4, 6"

Nota: o PowerShell não converte $Tab1.Padding = "3" ou $Tab1.Padding = "4, 6"

No entanto, os nativos a forma de criar um controlo da forma do Windows está longe da programação DRY (não se repita) . Embora as propriedades (múltiplas) possam ser adicionadas na criação (usando:New-Object System.Windows.Forms.Button -Property @{Location = "75, 120"; Size = "75, 23"}) , múltiplas propriedades não podem ser definidas imediatamente num estado posterior. Acima disso, não é fácil e rápido adicionar eventos1, comandos para crianças e propriedades do contentor (como por exemplo RowSpan), ou qualquer combinação, intermediadamente na criação de um comando de forma de janelas. Resumindo, tem de consultar o formulário do windows. controle uma e outra vez para definir suas propriedades e mais (com por exemplo $OKButton.<property> = ... Como neste exemplo ) :
$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"
É por isso que criei um invólucro de controle de forma de PowerShell reutilizável que vamos minimizar o código de formas de Windows (WinForms) para a sua essência.

1) a menos que utilize On<event> métodos, ver também: addEventListener vs onclick

Invólucro De Controlo De Forma De PowerShell

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

Sintaxe

Criar um controlo
<System.Windows.Forms.Control> = Form-Control [-Control <String>] [-Member <HashTable>]

Modificar um controlo
<Void> = Form-Control [-Control <System.Windows.Forms.Control>] [-Member <HashTable>]

Adicionar um (novo) controlo a um contentor
<System.Windows.Forms.Control> = Form-Control [-Control <String>|<System.Windows.Forms.Control>] [-Member <HashTable>] [-Add <System.Windows.Forms.Control[]>]

Canalizar um recipiente para um (novo) controlo
<System.Windows.Forms.Control> = <System.Windows.Forms.Control> | Form-Control [-Control <String>|<System.Windows.Forms.Control>] [-Member <HashTable>] [-Set <HashTable>] [-PassParent]

Parâmetros

-Control <String>|<System.Windows.Forms.Control> (posição 0, predefinição: Form)
O parâmetro -Control aceita um nome do tipo de controlo do formulário do Windows ([String]) ou um controlo do formulário existente ([System.Windows.Forms.Control]). Tipo de controlo do formulário do Windows nomes são como Form, Label, TextBox, Button, Panel, ..., etc. Se for fornecido um nome do tipo de controlo do formulário do Windows ([String]), o invólucro irá criar e devolver um novo controlo do formulário do Windows com propriedades e configurações definidas pelo resto dos parâmetros.
Se for fornecido um controlo de formulários do Windows ([System.Windows.Forms.Control]), o invólucro irá actualizar o controlo de formulários do Windows existente, usando as propriedades e configurações definidas pelo resto do parametro.

-Member <HashTable> (posição 1)
Define os valores da propriedade, invoca métodos e adiciona eventos num objecto novo ou existente.

  • Se o nome hash representar property no controle, por exemplo Size = "50, 50", o valor será atribuído ao valor da propriedade de controle.

  • Se o nome hash representar method no controle, por exemplo Scale = {1.5, 1.5}, o método de controle será invocado usando o valor para argumentos .

  • Se o nome hash representar event no controle, por exemplo Click = {$Form.Close()}, o valor ( [ScriptBlock]) será adicionado aos eventos de controle.

Duas propriedades de colecção, ColumnStyles e RowStyles, são simplificadas especialmente para o controlo de TableLayoutPanel que é considerado um substituto geral para a grelha De Controlo WPF : - O ColumnStyles propriedade, limpa todas as larguras de colunas e repõe - as com a ColumnStyle lista fornecida pelo valor de hash. - O RowStyles propriedade, limpa todos os Heigths da linha e reiniciá-los com o RowStyle lista fornecida pelo valor de hash.
Nota: se quiser adicionar ou inserir um único item específico do Colunstyle ou do RowStyle, terá de recuar na instrução nativa, como por exemplo:: [Void]$Control.Control.ColumnStyles.Add((New-Object Windows.Forms.ColumnStyle("Percent", 100)).

-Add <Array>
O parâmetro -Addadiciona um ou mais comandos para crianças ao controlo actual.
Nota: -add O parâmetro Não pode ser utilizado se o contentor for encaminhado para o controlo.

-Container <System.Windows.Forms.Control> (do gasoduto)
O contentor de origem é normalmente fornecido a partir do gasoduto: $ParentContainer | Form $ChildControl e anexado um (novo) controlo de crianças ao Contentor em causa.

-Set <HashTable>
Os conjuntos de parâmetros -Set(SetCellPosition, SetColumn, SetColumnSpan, SetRow, SetRowSpan e SetStyle as propriedades específicas de controlo de crianças relacionavam o recipiente do painel Progenitor, por exemplo: .Set RowSpan = 2
Nota: os parâmetros da coluna e linha -set só podem ser utilizados se o contentor for encaminhado para o controlo.

-GetParent
Por omissão, o controlo (filho) será devolvido pela função form-control, a menos que seja fornecido o botão -GetParent que irá devolver o contentor-mãe em alternativa. Nota: os parâmetros da coluna e linha -set só podem ser utilizados se um contentor for encaminhado para o controlo.

Exemplos

Há duas formas de configurar a hierarquia das formas do Windows:

  1. adicionar um (novo) controlo a um contentor
  2. canalizar um recipiente para um (novo) controlo

Adicionar um (novo) controlo a um contentor
Para este exemplo, eu retrabalhei o criando uma caixa de entrada personalizada em docs.microsoft.com usando o invólucro de controlo de forma de PowerShell:

$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
}

Nota 1: Embora os controles de adição aparecem mais estruturados especialmente para formas pequenas, a desvantagem é que não pode invocar métodos que se relacionam tanto com o contêiner pai e controle de crianças (como -Set RowSpan).
Nota 2: {[82] } pode facilmente perder-se entre parêntesis abertos e fechados se tentar criar filhos (ou mesmo netos) controlar directamente num contentor parental (como o controlo acima Label). Além disso, é mais difícil referenciar tal criança (por exemplo, $OKButton vs. $Form.Controls["OKButton"], presumindo que você definiu o propriedade do botãoName = "OKButton)

Canalizar um recipiente para um (novo) controlo
Para este exemplo, eu criei uma interface de usuário para testar o comportamento da propriedade dock. O formulário parece-se com isto:

enter image description here

O código de controlo de forma PowerShell necessário para isto:

$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()
 0
Author: iRon, 2017-12-19 15:55:58