public/controls/New-UiProgress.ps1
|
function New-UiProgress { <# .SYNOPSIS Creates a progress bar. .PARAMETER Variable Variable name to reference the bar later. .PARAMETER Label Optional text shown above the bar. Updateable via Set-UiProgress -Label. .PARAMETER Minimum Lower limit. Default 0. .PARAMETER Maximum Upper limit. Default 100. .PARAMETER Default Initial value. Default 0. .PARAMETER Height Bar height in pixels. .PARAMETER Indeterminate Animated bar instead of a fill. .PARAMETER ShowValue Render the current value as text beside the bar. Pointless with -Indeterminate (the bar has no real value to display), but allowed. .PARAMETER ValueFormat Format string for the value text. {0} is current, {1} is max. Defaults to '{0:N0}%'. '{0}/{1}' gives you the x/y look. .PARAMETER Severity Color tint: Info (default), Success, Warning, Error. .PARAMETER WPFProperties Hashtable of additional WPF properties. .EXAMPLE New-UiProgress -Variable 'progress' .EXAMPLE New-UiProgress -Variable 'loading' -Indeterminate -Label 'Connecting...' .EXAMPLE New-UiProgress -Variable 'files' -Maximum 250 -ShowValue -ValueFormat '{0}/{1} files' .EXAMPLE New-UiProgress -Variable 'disk' -Default 87 -ShowValue -Severity Warning -Label 'Disk usage' #> [CmdletBinding()] param( # Optional - a bare display bar needs no name. Omitted means "don't register for Set-UiProgress by name"; the body puts up a throwaway name for the registry. [string]$Variable, [string]$Label, [double]$Minimum = 0, [double]$Maximum = 100, [double]$Default = 0, [int]$Height = 6, [switch]$Indeterminate, [switch]$ShowValue, [string]$ValueFormat = '{0:N0}%', [ValidateSet('Info', 'Success', 'Warning', 'Error')] [string]$Severity = 'Info', [Parameter()] [hashtable]$WPFProperties ) $session = Assert-UiSession -CallerName 'New-UiProgress' $parent = $session.CurrentParent Write-Debug "Variable='$Variable', Min=$Minimum Max=$Maximum Default=$Default Severity=$Severity" if ($Maximum -le $Minimum) { throw "New-UiProgress: -Maximum ($Maximum) must be greater than -Minimum ($Minimum)." } $progress = [System.Windows.Controls.ProgressBar]::new() $progress.Minimum = $Minimum $progress.Maximum = $Maximum $progress.Value = [Math]::Max($Minimum, [Math]::Min($Maximum, $Default)) $progress.IsIndeterminate = $Indeterminate.IsPresent $progress.Margin = [System.Windows.Thickness]::new(4, 4, 4, 8) # ThemeEngine reads Tag.BrushTag and rebinds Foreground on theme switches. $brushKey = Get-SeverityBrushKey -Severity $Severity -UseAccentDefault # Tag goes on early so the first ApplyTheme pass picks up the right brush. # ValueBlock/LabelBlock get filled in below if the wrapper is built. $labelBlock = $null $valueBlock = $null $progress.Tag = @{ LabelBlock = $labelBlock ValueBlock = $valueBlock ValueFormat = $ValueFormat Severity = $Severity BrushTag = $brushKey } # Tag is set above, so Set-ProgressBarStyle's RegisterElement picks up the right severity brush on the first paint. Set-ProgressBarStyle -ProgressBar $progress if ($PSBoundParameters.ContainsKey('Height')) { $progress.Height = $Height } if ($WPFProperties) { # Tag is reserved - it stores the metadata that makes -Label/-ShowValue/-Severity work. Letting the caller stomp it would silently break all three. if ($WPFProperties.ContainsKey('Tag')) { Write-Warning "New-UiProgress: -WPFProperties Tag is reserved (used for label/value/severity bookkeeping). Ignoring." $WPFProperties = @{} + $WPFProperties [void]$WPFProperties.Remove('Tag') } Set-UiProperties -Control $progress -Properties $WPFProperties } # Wrap in a stack only when there's a label or value text. Bare bars stay bare so existing layouts don't shift a pixel. $needsWrapper = $Label -or $ShowValue if ($needsWrapper) { $colors = Get-ThemeColors $stack = [System.Windows.Controls.StackPanel]::new() $stack.Margin = [System.Windows.Thickness]::new(4, 4, 4, 8) if ($Label) { $labelBlock = [System.Windows.Controls.TextBlock]@{ Text = $Label FontSize = 12 Foreground = ConvertTo-UiBrush $colors.ControlFg Margin = [System.Windows.Thickness]::new(0, 0, 0, 4) Tag = 'ControlForegroundBrush' } [PsUi.ThemeEngine]::RegisterElement($labelBlock) [void]$stack.Children.Add($labelBlock) } # Bar + value text share a row so the percentage rides next to it if ($ShowValue) { $row = [System.Windows.Controls.Grid]::new() $col1 = [System.Windows.Controls.ColumnDefinition]::new() $col1.Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star) $col2 = [System.Windows.Controls.ColumnDefinition]::new() $col2.Width = [System.Windows.GridLength]::Auto $row.ColumnDefinitions.Add($col1) $row.ColumnDefinitions.Add($col2) $progress.Margin = [System.Windows.Thickness]::new(0, 0, 8, 0) $progress.VerticalAlignment = 'Center' [System.Windows.Controls.Grid]::SetColumn($progress, 0) [void]$row.Children.Add($progress) $valueBlock = [System.Windows.Controls.TextBlock]@{ Text = ($ValueFormat -f $progress.Value, $Maximum) FontSize = 12 Foreground = ConvertTo-UiBrush $colors.ControlFg VerticalAlignment = 'Center' MinWidth = 48 TextAlignment = 'Right' Tag = 'ControlForegroundBrush' } [PsUi.ThemeEngine]::RegisterElement($valueBlock) [System.Windows.Controls.Grid]::SetColumn($valueBlock, 1) [void]$row.Children.Add($valueBlock) # Update the text whenever the bar moves. Read Maximum from the sender so post-construction tweaks (yes, people do that) stay in sync. $capturedFormat = $ValueFormat $progress.Add_ValueChanged({ param($sender, $e) $valueBlock.Text = $capturedFormat -f $e.NewValue, $sender.Maximum }.GetNewClosure()) [void]$stack.Children.Add($row) } else { [void]$stack.Children.Add($progress) } [void]$parent.Children.Add($stack) } else { [void]$parent.Children.Add($progress) } # Patch the metadata with the actual wrapper blocks (if any). $progress.Tag.LabelBlock = $labelBlock $progress.Tag.ValueBlock = $valueBlock # A bare display bar has no -Variable, but Register-UiControlComplete's -Name is Mandatory - an empty string there makes PowerShell prompt for it and the whole window hangs. Hand it a throwaway name (same trick New-UiStatusBar uses for anonymous bars). $varName = if ($Variable) { $Variable } else { '_anonProgress_' + [System.Guid]::NewGuid().ToString('N').Substring(0, 8) } Write-Debug "Registered progress bar '$varName' (wrapper=$needsWrapper)" Register-UiControlComplete -Name $varName -Control $progress -InitialValue $progress.Value } |