CommonControls/Get-Input.ps1

function Get-Input
{
    <#
    .Synopsis
        Collects user input
    .Description
        Get-Input collects a series of fields.
    #>

    param(
    [Parameter(Mandatory=$true)]
    [ValidateScript({
        $in = $_
        $badKeys =$in.Keys | Where-Object { $_ -isnot [string] }
        if ($badKeys) {
            throw "Not all field names were strings. All field names must be strings."
        }
        
        $badValues = $in.Values | 
            Get-Member | 
            Select-Object -ExpandProperty TypeName -Unique |
            Where-Object { 
                ($_ -ne 'System.RuntimeType' -and
                $_ -ne 'System.Management.Automation.ScriptBlock' -and
                $_ -ne 'System.String')
            }
        if ($badValues) {
            throw "Not all values were strings, types, or script blocks. All values must be strings, types or script blocks."
        }   
        return $true             
    })]
    [Hashtable]$Field,    
    [string[]]$Order,
    [switch]$HideOKCancel,    
    # The name of the control
    [string]$Name,
    # If the control is a child element of a Grid control (see New-Grid),
    # then the Row parameter will be used to determine where to place the
    # top of the control. Using the -Row parameter changes the
    # dependency property [Windows.Controls.Grid]::RowProperty
    [Int]$Row,
    # If the control is a child element of a Grid control (see New-Grid)
    # then the Column parameter will be used to determine where to place
    # the left of the control. Using the -Column parameter changes the
    # dependency property [Windows.Controls.Grid]::ColumnProperty
    [Int]$Column,
    # If the control is a child element of a Grid control (see New-Grid)
    # then the RowSpan parameter will be used to determine how many rows
    # in the grid the control will occupy. Using the -RowSpan parameter
    # changes the dependency property [Windows.Controls.Grid]::RowSpanProperty
    [Int]$RowSpan,
    # If the control is a child element of a Grid control (see New-Grid)
    # then the RowSpan parameter will be used to determine how many columns
    # in the grid the control will occupy. Using the -ColumnSpan parameter
    # changes the dependency property [Windows.Controls.Grid]::ColumnSpanProperty
    [Int]$ColumnSpan,
    # The -Width parameter will be used to set the width of the control
    [Int]$Width, 
    # The -Height parameter will be used to set the height of the control
    [Int]$Height,
    # If the control is a child element of a Canvas control (see New-Canvas),
    # then the Top parameter controls the top location within that canvas
    # Using the -Top parameter changes the dependency property
    # [Windows.Controls.Canvas]::TopProperty
    [Double]$Top,
    # If the control is a child element of a Canvas control (see New-Canvas),
    # then the Left parameter controls the left location within that canvas
    # Using the -Left parameter changes the dependency property
    # [Windows.Controls.Canvas]::LeftProperty
    [Double]$Left,
    # If the control is a child element of a Dock control (see New-Dock),
    # then the Dock parameter controls the dock style within that panel
    # Using the -Dock parameter changes the dependency property
    # [Windows.Controls.DockPanel]::DockProperty
    [Windows.Controls.Dock]$Dock,
    # If Show is set, then the UI will be displayed as a modal dialog within the current
    # thread. If the -Show and -AsJob parameters are omitted, then the control should be
    # output from the function
    [Switch]$Show,
    # If AsJob is set, then the UI will displayed within a WPF job.
    [Switch]$AsJob
    )
    
    $uiParameters=  @{} + $psBoundParameters
    $null = $uiParameters.Remove('Field')
    $null = $uiParameters.Remove('Order')
    $null = $uiParameters.Remove('HideOKCancel')
    New-Grid -Columns 'Auto', 1* -ControlName Get-Input @uiParameters -On_Loaded {
        $this.RowDefinitions.Clear()
        $rows = ConvertTo-GridLength (@('Auto')*($field.Count + 2))
        foreach ($rd in $rows) {
            $r = New-Object Windows.Controls.RowDefinition -Property @{Height=$rd}
            $null =$this.RowDefinitions.Add($r)
        }
        $row = 0        
        
        if (-not $Order) {
            $Order = @($field.Keys |Sort-Object)
        }       
        
        foreach ($key in $Order) {            
            if ($field[$key]) {
                
                $value = $field[$key]
                New-Label $key -Row $row | 
                    Add-ChildControl -parent $this
                         
                $cueText = ""
                $validatePattern = ""
                $expectedType = [PSObject]                                     
                if ($value -is [ScriptBlock]) {
                    if ($value.Render) {
                        # If Render is set, the ScriptBlock creates the contents of a stackpanel
                        # otherwise, the scriptblock is the validation
                    } else {
                        if ($value.AllowScriptEntry) {
                        }
                    }
                } elseif ($value -is [Type]) {
                    # If a type is provided, try to find a match
                    $commands =Get-UICommand | 
                        Where-Object {
                            $outputTypes = ($_.OutputType | Select-Object -ExpandProperty Type)
                            (($outputTypes -contains $value) -or
                            ($outputTypes | Where-Object { $value.IsSubclassOf($_) })) 
                        }      
                        
                    $useTextBox = $true
                                       
                    if (-not $commands) {
                        # No match, default to primitives
                        if ($value.CueText) {
                            $cueText =  $value.CueText
                        }
                        $expectedType = $value -as [type]
                        
                        if (@([bool], [switch]) -contains $value) 
                        {
                            $useTextBox = $false
                            if ($cueText) {
                                $checkBox = New-CheckBox -Margin 5 -Content "$cueText" -FontStyle Italic -Name $key -Row $row -Column 1 
                                $this.Children.Add($checkBox)
                            } else {
                                $this.Children.Add((New-CheckBox -Margin 5 -Name $key -Row $row -Column 1))
                            }
                            $row++
                            continue
                        }
                    } else {
                        if ($commands.Count) {
                            $getKeyMatch = foreach ($_ in $commands) { 
                                if ($_.Name -eq "Get-$Key") { $_ }                                                         
                            }
                            $editKeyMatch = foreach ($_ in $commands) {
                                if ($_.Name -eq "Edit-$Key") { $_ } 
                            }
                            if ($getKeyMatch) {
                                $command = $getKeyMatch
                            } elseif ($editKeyMatch) {
                                $command = $editKeyMatch
                            } else {
                                $command = $commands | Select-Object -First 1 
                            }                     
                        } else {
                            # Only one match, use it
                            $command = $commands
                        }
                        & $command -Name $key -Row $row -Column 1 |                                 
                                Add-ChildControl -parent $this
                        $row++
                        continue 
                    }              
                } elseif ($value -is [string]) {
                    # The string is cue text
                    $expectedType = if ($value.ExpectedType -as [Type]) { $value.ExpectedType } else {[PSObject] }
                }
                
                
                New-TextBox -Name $key -Margin 4 -Column 1 -Row $row -VisualStyle CueText -Resource @{
                    ExpectedType=$expectedType
                } -Text $value -On_PreviewTextInput { 
                    if ((($this.Text + $_.Text) -as $expectedType)) {
                        $this.ClearValue([Windows.Controls.Control]::EffectProperty)
                        $toRemove = $errorList.Items | Where-Object { $_.Tag -eq $this } 
                        if ($toRemove) {
                            $errorList.Items.Remove($toRemove)                        
                        }
                        if (-not $errorList.Items.Count) { 
                            $errorList.Visibility = 'Collapsed'
                            if ($okButton) {
                                $okButton.IsEnabled = $true
                            }
                        }                         
                    } else {
                        $toUpdate = $errorList.Items | Where-Object { $_.Tag -eq $this } 
                        $errorMessage ="$($this.Name): Can't convert $($this.Text) to $($expectedType.Fullname)"
                        if ($toUpdate) {
                            $toUpdate.Content = $errorMessage
                        } else {
                            $errorLabel = New-Label -Tag $this -Content $errorMessage -Foreground Red                                                
                            $null = $errorList.Items.Add($errorLabel)
                        }
                        $errorList.Visibility = 'Visible'
                        $okButton.IsEnabled = $false
                        $this.Effect = New-DropShadowEffect -Color Red
                    }
                } | 
                    Add-ChildControl -parent $this
    
                $row++
            }
        }
        
        New-ListBox -ColumnSpan 2 -Row $row -Name 'ErrorList' -Visibility Collapsed | 
            Add-ChildControl -parent $this
        
        if (-not $HideOKCancel) {
            $row++
            New-UniformGrid -Row $row -ColumnSpan 2 {
                New-Button { "Cancel" } -Name CancelButton -IsCancel -On_Click {
                    Get-ParentControl | 
                        Close-Control
                }
                
                New-Button { "_OK" } -Column 1 -Name OKButton -IsDefault -On_Click {
                    Get-ParentControl | 
                        Set-UIValue -passThru | 
                        Close-Control
                } 
            }  | 
                Add-ChildControl -parent $this
        }
    }
}