public/dialogs/Show-UiMessageDialog.ps1
|
function Show-UiMessageDialog { <# .SYNOPSIS Displays a themed message dialog with customizable buttons and icons. .DESCRIPTION Shows a custom WPF dialog that respects the current theme. Replaces standard MessageBox. .PARAMETER Title Dialog window title. .PARAMETER Message Message text to display. .PARAMETER Buttons Button configuration: OK, OKCancel, YesNo, or YesNoCancel. .PARAMETER Icon Icon type: Info, Warning, Error, Question, or None. .PARAMETER PowerShell When used, displays the message in a PowerShell console-styled code viewer. Uses Consolas font, blue background (#012456), white text, and makes the dialog larger (700x500) and resizable with horizontal/vertical scrollbars. Ideal for displaying source code or command snippets. .PARAMETER CustomButtons Array of hashtables defining custom buttons. Each hashtable should have: - Label: The button text (required) - Value: The value returned when clicked (required) - IsDefault: If true, this button is the default (optional) - IsAccent: If true, button uses accent color (optional) When provided, the -Buttons parameter is ignored. .PARAMETER ThemeColors Override theme colors for this dialog. Pass a colors hashtable directly. .EXAMPLE Show-UiMessageDialog -Title 'Confirmation' -Message 'Are you sure?' -Buttons YesNo -Icon Question .EXAMPLE $buttons = @( @{ Label = 'Save'; Value = 'Save'; IsAccent = $true; IsDefault = $true } @{ Label = 'Discard'; Value = 'Discard' } @{ Label = 'Cancel'; Value = 'Cancel' } ) Show-UiMessageDialog -Title 'Unsaved Changes' -Message 'Save changes?' -CustomButtons $buttons -Icon Question .EXAMPLE $result = Show-UiMessageDialog -Title 'Success' -Message 'Operation completed!' -Buttons OK -Icon Info .EXAMPLE Show-UiMessageDialog -Title 'Source Code' -Message $scriptBlock.ToString() -PowerShell #> [CmdletBinding()] param( [string]$Title = 'Message', [Parameter(Mandatory)] [string]$Message, [ValidateSet('OK', 'OKCancel', 'YesNo', 'YesNoCancel')] [string]$Buttons = 'OK', [ValidateSet('Info', 'Warning', 'Error', 'Question', 'None')] [string]$Icon = 'Info', [object]$ThemeColors, [switch]$PowerShell, [array]$CustomButtons ) Write-Debug "Title='$Title' Buttons='$Buttons' Icon='$Icon' PowerShell=$PowerShell" # Calculate width based on button count - each button is ~90px wide $buttonCount = if ($CustomButtons) { $CustomButtons.Count } else { 3 } $minForButtons = [Math]::Max(420, ($buttonCount * 90) + 50) # PowerShell mode uses fixed size; standard mode sizes to content $dialogParams = @{ Title = $Title Width = if ($PowerShell) { 700 } else { $minForButtons } Height = if ($PowerShell) { 500 } else { 0 } MaxHeight = if ($PowerShell) { 10000 } else { 800 } SizeToContent = if ($PowerShell) { 'Manual' } else { 'Height' } ResizeMode = if ($PowerShell) { 'CanResizeWithGrip' } else { 'NoResize' } AppIdSuffix = 'Message' ThemeColors = $ThemeColors } # Set overlay icon based on message type $overlayGlyph = switch ($Icon) { 'Info' { [PsUi.ModuleContext]::GetIcon('Info') } 'Warning' { [PsUi.ModuleContext]::GetIcon('Alert') } 'Error' { [PsUi.ModuleContext]::GetIcon('Error') } 'Question' { [PsUi.ModuleContext]::GetIcon('Help') } default { [PsUi.ModuleContext]::GetIcon('Info') } } $dialogParams['OverlayGlyph'] = $overlayGlyph # Use standard helper to create the window shell $dialog = New-DialogWindow @dialogParams $window = $dialog.Window $contentPanel = $dialog.ContentPanel $colors = $dialog.Colors # Color for icon matches overlay $iconColor = switch ($Icon) { 'Info' { $colors.Accent } 'Warning' { $colors.Warning } 'Error' { $colors.Error } 'Question' { $colors.Accent } default { $colors.Accent } } # Button panel at bottom using Grid for left/right alignment $buttonBar = [System.Windows.Controls.Grid]::new() $buttonBar.Margin = [System.Windows.Thickness]::new(0, 12, 0, 0) [void]$buttonBar.ColumnDefinitions.Add([System.Windows.Controls.ColumnDefinition]@{ Width = '*' }) [void]$buttonBar.ColumnDefinitions.Add([System.Windows.Controls.ColumnDefinition]@{ Width = 'Auto' }) [System.Windows.Controls.DockPanel]::SetDock($buttonBar, 'Bottom') [void]$contentPanel.Children.Add($buttonBar) # Add copy button for informational dialogs (not Question/choice dialogs) $showCopyButton = $Icon -in @('Info', 'Warning', 'Error') -or $PowerShell if ($showCopyButton) { $copyBtn = [System.Windows.Controls.Button]@{ Height = 28 Padding = [System.Windows.Thickness]::new(10, 4, 10, 4) ToolTip = 'Copy message to clipboard' HorizontalAlignment = 'Left' } # Icon + text content for copy button $copyContent = [System.Windows.Controls.StackPanel]@{ Orientation = 'Horizontal' } $copyIcon = [System.Windows.Controls.TextBlock]@{ Text = [PsUi.ModuleContext]::GetIcon('Copy') FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets') FontSize = 12 Margin = [System.Windows.Thickness]::new(0, 0, 6, 0) VerticalAlignment = 'Center' } $copyText = [System.Windows.Controls.TextBlock]@{ Text = 'Copy' VerticalAlignment = 'Center' } [void]$copyContent.Children.Add($copyIcon) [void]$copyContent.Children.Add($copyText) $copyBtn.Content = $copyContent # Apply standard styling and wire up click Set-ButtonStyle -Button $copyBtn $copyBtn.Tag = $Message $copyBtn.Add_Click({ [System.Windows.Clipboard]::SetText($this.Tag) # Brief visual feedback - change icon to checkmark $panel = $this.Content $iconBlock = $panel.Children[0] $originalIcon = $iconBlock.Text $iconBlock.Text = [PsUi.ModuleContext]::GetIcon('Accept') # Reset after 1.5 seconds $timer = [System.Windows.Threading.DispatcherTimer]::new() $timer.Interval = [TimeSpan]::FromMilliseconds(1500) $capturedIconBlock = $iconBlock $capturedOriginal = $originalIcon $timer.Add_Tick({ $capturedIconBlock.Text = $capturedOriginal $this.Stop() }.GetNewClosure()) $timer.Start() }) [System.Windows.Controls.Grid]::SetColumn($copyBtn, 0) [void]$buttonBar.Children.Add($copyBtn) } # Right-aligned button panel for action buttons $buttonPanel = [System.Windows.Controls.StackPanel]@{ Orientation = 'Horizontal' HorizontalAlignment = 'Right' } [System.Windows.Controls.Grid]::SetColumn($buttonPanel, 1) [void]$buttonBar.Children.Add($buttonPanel) # Build message content area - different for PowerShell vs standard mode if ($PowerShell) { # PowerShell console-styled TextBox $codeBox = [System.Windows.Controls.TextBox]@{ Text = $Message IsReadOnly = $true AcceptsReturn = $true TextWrapping = 'NoWrap' VerticalContentAlignment = 'Top' HorizontalScrollBarVisibility = 'Auto' VerticalScrollBarVisibility = 'Auto' FontFamily = [System.Windows.Media.FontFamily]::new('Consolas') FontSize = 13 Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#012456') Foreground = [System.Windows.Media.Brushes]::White BorderThickness = [System.Windows.Thickness]::new(1) BorderBrush = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1E3A5F') Padding = [System.Windows.Thickness]::new(5) Margin = [System.Windows.Thickness]::new(0, 0, 0, 12) } # Add themed context menu for copy/select all $codeBox.ContextMenu = New-TextBoxContextMenu -ReadOnly [void]$contentPanel.Children.Add($codeBox) } else { # Standard message area with optional icon, wrapped in ScrollViewer $scrollViewer = [System.Windows.Controls.ScrollViewer]@{ VerticalScrollBarVisibility = 'Auto' HorizontalScrollBarVisibility = 'Disabled' Padding = [System.Windows.Thickness]::new(0, 0, 8, 0) } $messageGrid = [System.Windows.Controls.Grid]::new() [void]$messageGrid.ColumnDefinitions.Add([System.Windows.Controls.ColumnDefinition]::new()) [void]$messageGrid.ColumnDefinitions.Add([System.Windows.Controls.ColumnDefinition]::new()) $hasIcon = $Icon -ne 'None' if ($hasIcon) { $messageGrid.ColumnDefinitions[0].Width = [System.Windows.GridLength]::new(48) } else { $messageGrid.ColumnDefinitions[0].Width = [System.Windows.GridLength]::new(0) } $messageGrid.ColumnDefinitions[1].Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star) # Add icon if needed if ($hasIcon) { $iconBlock = [System.Windows.Controls.TextBlock]@{ Text = $overlayGlyph FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets') FontSize = 32 Foreground = ConvertTo-UiBrush $iconColor VerticalAlignment = 'Center' HorizontalAlignment = 'Center' Margin = [System.Windows.Thickness]::new(0, 0, 12, 0) } [System.Windows.Controls.Grid]::SetColumn($iconBlock, 0) [void]$messageGrid.Children.Add($iconBlock) } $messageText = [System.Windows.Controls.TextBlock]@{ Text = $Message FontSize = 13 TextWrapping = 'Wrap' Foreground = ConvertTo-UiBrush $colors.ControlFg VerticalAlignment = 'Top' Margin = [System.Windows.Thickness]::new(0, 0, 0, 0) } [System.Windows.Controls.Grid]::SetColumn($messageText, 1) [void]$messageGrid.Children.Add($messageText) $scrollViewer.Content = $messageGrid [void]$contentPanel.Children.Add($scrollViewer) } # Add buttons - either custom buttons or standard button sets if ($CustomButtons -and $CustomButtons.Count -gt 0) { foreach ($btnDef in $CustomButtons) { $btn = [System.Windows.Controls.Button]@{ Content = $btnDef.Label MinWidth = 70 Height = 28 Margin = [System.Windows.Thickness]::new(6, 0, 0, 0) Padding = [System.Windows.Thickness]::new(14, 4, 14, 4) } if ($btnDef.IsAccent) { Set-ButtonStyle -Button $btn -Accent } else { Set-ButtonStyle -Button $btn } # Store value AND window reference to avoid closure issues $btn.Tag = @{ Value = $btnDef.Value; Window = $window } $btn.Add_Click({ $this.Tag.Window.Tag = $this.Tag.Value $this.Tag.Window.Close() }) [void]$buttonPanel.Children.Add($btn) if ($btnDef.IsDefault) { $btn.IsDefault = $true } if ($btnDef.IsCancel) { $btn.IsCancel = $true } } } else { # Standard button configurations - helper to create styled button $createButton = { param($Text, $IsAccent, $Result, $TargetWindow) $btn = [System.Windows.Controls.Button]@{ Content = $Text Width = 80 Height = 28 Margin = [System.Windows.Thickness]::new(4, 0, 0, 0) } if ($IsAccent) { Set-ButtonStyle -Button $btn -Accent } else { Set-ButtonStyle -Button $btn } $btn.Tag = @{ Result = $Result; Window = $TargetWindow } $btn.Add_Click({ $this.Tag.Window.Tag = $this.Tag.Result; $this.Tag.Window.Close() }) return $btn } switch ($Buttons) { 'OK' { $okBtn = & $createButton 'OK' $true 'OK' $window $okBtn.IsDefault = $true [void]$buttonPanel.Children.Add($okBtn) } 'OKCancel' { $okBtn = & $createButton 'OK' $true 'OK' $window $okBtn.IsDefault = $true [void]$buttonPanel.Children.Add($okBtn) $cancelBtn = & $createButton 'Cancel' $false 'Cancel' $window $cancelBtn.IsCancel = $true [void]$buttonPanel.Children.Add($cancelBtn) } 'YesNo' { $yesBtn = & $createButton 'Yes' $true 'Yes' $window $yesBtn.IsDefault = $true [void]$buttonPanel.Children.Add($yesBtn) $noBtn = & $createButton 'No' $false 'No' $window $noBtn.IsCancel = $true [void]$buttonPanel.Children.Add($noBtn) } 'YesNoCancel' { $yesBtn = & $createButton 'Yes' $true 'Yes' $window $yesBtn.IsDefault = $true [void]$buttonPanel.Children.Add($yesBtn) $noBtn = & $createButton 'No' $false 'No' $window [void]$buttonPanel.Children.Add($noBtn) $cancelBtn = & $createButton 'Cancel' $false 'Cancel' $window $cancelBtn.IsCancel = $true [void]$buttonPanel.Children.Add($cancelBtn) } } } # Wire up standard fade-in behavior Initialize-UiWindowLoaded -Window $window -TitleBarBackground $colors.HeaderBackground -TitleBarForeground $colors.HeaderForeground # Position and show Set-UiDialogPosition -Dialog $window Write-Debug "Showing modal dialog" [void]$window.ShowDialog() $result = $window.Tag Write-Debug "Result: $result" return $result } |