EasyGUI.psm1
|
Write-Verbose "Checking PowerShell version Requirement For EasyGUI . . ." # Requires PS 5.1 or 7+ (but not 6.x) $PSMajor = $PSVersionTable.PSVersion.Major $OS = [System.Environment]::OSVersion.Platform # Block unsupported OS first if ($OS -ne 'Win32NT') { Write-Error "EasyGUI only supports Windows. Your current OS: $OS" return } # Block PowerShell older than 5.1 if ($PSMajor -lt 5 -or ($PSMajor -eq 5 -and $PSVersionTable.PSVersion.Minor -lt 1)) { Write-Error "EasyGUI requires at least PowerShell 5.1." return } # Block PowerShell 6.x if ($PSMajor -eq 6) { Write-Error "EasyGUI does NOT support PowerShell 6.x. Please install PowerShell 7 or use Windows PowerShell 5.1." return } # Passed Write-Verbose "EasyGUI System and PowerShell Requirement Passed." Write-Verbose "Preparing Variables . . ." if (-not $script:Options) { $script:Options = @{} } if (-not $script:Window) { $script:Window = @{} } if (-not $script:Window.Inputs) { $script:Window.Inputs = @{} } if (-not $script:Window.InputActions) { $script:Window.InputActions = @{} } Write-Verbose "Adding Required Assembly / Type Names . . ." try { Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Windows.Forms, WindowsFormsIntegration } catch { Write-Error "Oops something went wrong: $_" } function EasyGUI.ErrorOut { param($ErrorMessage) Write-Error "a Error Have occurred: `n $ErrorMessage" [System.Windows.MessageBox]::Show( "Error: $ErrorMessage", "EasyGUI Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) return } function PrepareWindow { param( [string]$Title = "Untitled GUI", [int]$Width = 500, [int]$Height = 400 ) $xaml = @" <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="$Title" Height="$Height" Width="$Width" WindowStartupLocation="CenterScreen" Background="#1E1E1E" FontFamily="Segoe UI" ResizeMode="NoResize"> <Grid Margin="10"> <!-- ScrollViewer fills entire Grid --> <ScrollViewer VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Disabled" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#1E1E1E"> <!-- Inner StackPanel holds content --> <StackPanel x:Name="Stack" VerticalAlignment="Top" /> </ScrollViewer> </Grid> </Window> "@ try { $reader = New-Object System.Xml.XmlNodeReader ([xml]$xaml) $script:WpfWindow = [Windows.Markup.XamlReader]::Load($reader) $script:Stack = $script:WpfWindow.FindName("Stack") } catch { Write-Host "Error creating WPF window: $($_.Exception.Message)" -ForegroundColor Red return $false } if (-not ($script:WpfWindow | Get-Member -Name "Inputs")) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name "Inputs" -Value @{} -Force } return $true } function Window.Text { param( [string]$Content, [string]$foreground = "White", [string]$ID = $null # Optional unique ID for updating ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } # Make sure Inputs hash exists if (-not ($script:WpfWindow | Get-Member -Name Inputs)) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{} } if ($ID) { # Ensure the hashtable itself exists if (-not $script:WpfWindow.Inputs) { $script:WpfWindow.Inputs = @{} } # If TextBlock already exists, just update if ($script:WpfWindow.Inputs.ContainsKey($ID)) { $script:WpfWindow.Inputs[$ID].Text = $Content $script:WpfWindow.Inputs[$ID].Foreground = $foreground return } } # Create a new TextBlock $label = New-Object System.Windows.Controls.TextBlock $label.Text = $Content $label.Foreground = $foreground $label.Margin = "5,5,5,10" $label.FontSize = 16 # Store reference if ID given if ($ID) { $script:WpfWindow.Inputs[$ID] = $label } $script:Stack.Children.Add($label) | Out-Null } function Window.AddButton { param( [string]$Name, [scriptblock]$Command ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } $btn = New-Object System.Windows.Controls.Button $btn.Content = $Name $btn.Height = 35 $btn.Width = 200 $btn.HorizontalAlignment = "Left" $btn.Background = "#2A2A2A" $btn.Foreground = "White" $btn.BorderBrush = "#444" $btn.BorderThickness = 1 $btn.Padding = "10,6" $btn.Margin = "4" $btn.FontSize = 14 $btn.Cursor = "Hand" # Hover $btn.Add_MouseEnter({ $_.Source.Background = "#3A3A3A" }) $btn.Add_MouseLeave({ $_.Source.Background = "#2A2A2A" }) # Pressed $btn.Add_PreviewMouseDown({ $_.Source.Background = "#3A7BFF" }) $btn.Add_PreviewMouseUp({ if (-not $_.Source.IsMouseOver) { $_.Source.Background = "#2A2A2A" } }) $localCommand = $Command $btn.Add_Click({ if ([string]::IsNullOrWhiteSpace($localCommand)) { Write-Host "`"$Name`" Button was Pressed!" return } try { & $localCommand } catch { Write-Error $_.Exception.Message } }.GetNewClosure()) $script:Stack.Children.Add($btn) | Out-Null } function Window.AddInputBox { param( [Parameter(Mandatory=$true)] [string]$ID, [Parameter(Mandatory=$false)] [scriptblock]$Action ) # Ensure custom storage exists if (-not ($script:WpfWindow | Get-Member -Name Inputs)) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{} } if (-not ($script:WpfWindow | Get-Member -Name InputActions)) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name InputActions -Value @{} } # Ensure valid Name (must start with a letter) $nameSafe = "Input$ID" # Create TextBox $tb = New-Object System.Windows.Controls.TextBox $tb.Name = $nameSafe # Easy GUI style $tb.Background = "#222" $tb.Foreground = "White" $tb.BorderBrush = "#555" $tb.BorderThickness = 1 $tb.FontSize = 14 $tb.Padding = "6" $tb.Margin = "4" # Focus border change $tb.Add_GotFocus({ $_.Source.BorderBrush = "#3A7BFF" }) $tb.Add_LostFocus({ $_.Source.BorderBrush = "#555" }) # Hover effect $tb.Add_MouseEnter({ $_.Source.Background = "#2E2E2E" }) $tb.Add_MouseLeave({ $_.Source.Background = "#222" }) # Store input reference $script:WpfWindow.Inputs[$ID] = $tb # Store optional action if ($Action) { $script:WpfWindow.InputActions[$ID] = $Action } # Add automatically to current StackPanel if exists if ($script:Stack) { $script:Stack.Children.Add($tb) | Out-Null } } function Window.InputApply { param( [string]$InputID, [string]$ButtonLabel = "Apply", [string]$Alignment = "Left" ) # Ensure the stack exists if (-not $script:Stack) { Write-Host "GUI StackPanel not initialized"; return } # Ensure Inputs dictionary exists if (-not $script:WpfWindow.Inputs) { $script:WpfWindow.Inputs = @{} } # Ensure InputActions dictionary exists if (-not $script:WpfWindow.InputActions) { $script:WpfWindow.InputActions = @{} } # Capture local references for the closure $localInputs = $script:WpfWindow.Inputs $localActions = $script:WpfWindow.InputActions $localID = $InputID # Create the button $btn = New-Object System.Windows.Controls.Button $btn.Content = $ButtonLabel $btn.Height = 35 $btn.Width = 200 $btn.Margin = "4" $btn.FontSize = 14 $btn.HorizontalAlignment = $Alignment $btn.Background = "#2A2A2A" $btn.Foreground = "White" $btn.BorderBrush = "#444" $btn.BorderThickness = 1 $btn.Cursor = "Hand" # Hover and pressed effects $btn.Add_MouseEnter({ $_.Source.Background = "#3A3A3A" }) $btn.Add_MouseLeave({ $_.Source.Background = "#2A2A2A" }) $btn.Add_PreviewMouseDown({ $_.Source.Background = "#3A7BFF" }) $btn.Add_PreviewMouseUp({ if (-not $_.Source.IsMouseOver) { $_.Source.Background = "#2A2A2A" } }) # Click action $btn.Add_Click({ # Check if input exists if (-not $localInputs.ContainsKey($localID)) { [System.Windows.MessageBox]::Show("Input '$localID' not found.","EasyGUI Error","OK","Error") return } # Check if action exists if (-not $localActions.ContainsKey($localID)) { [System.Windows.MessageBox]::Show("No action defined for Input '$localID'.","EasyGUI Error","OK","Error") return } # Execute action & $localActions[$localID] }.GetNewClosure()) # Add to StackPanel $script:Stack.Children.Add($btn) | Out-Null } function Window.AddOption { param( [string]$Label, [string]$ID = "default", [string]$Mini = "$false", [string]$align = "Left", [string]$tooltip, [int]$Margin = 8, [bool]$Default = $false, [scriptblock]$Action = {} ) $ID = if ([string]::IsNullOrWhiteSpace($ID)) { "default" } else { [string]$ID } $Label = if ([string]::IsNullOrWhiteSpace($Label)) { "Option" } else { $Label } # Ensure group exists if (-not $script:Options.ContainsKey($ID)) { $script:Options[$ID] = @{} } [int]$i = 0 while ($script:Options[$ID].ContainsKey([string]$i)) { $i++ } $Unique = [string]$i # Store state and action in script:Options $script:Options[$ID][$Unique] = @{ Checked = $Default Action = $Action } # Create the checkbox $cb = New-Object System.Windows.Controls.CheckBox $cb.Content = $Label $cb.IsChecked = $Default $cb.HorizontalAlignment = $align if ($tooltip) { $cb.tooltip = $tooltip } $cb.Cursor = "Hand" $cb.Foreground = "White" $cb.FontSize = 14 $cb.Margin = $Margin # Box (Border) styling $cb.BorderBrush = "#777" $cb.BorderThickness = 2 $cb.Background = "#222" # Hover effect $cb.Add_MouseEnter({ $_.Source.BorderBrush = "#999" }) $cb.Add_MouseLeave({ $_.Source.BorderBrush = "#777" }) $cb.Add_Checked({ $_.Source.Background = "#00b100" $_.Source.BorderBrush = "#00b100" }) $cb.Add_Unchecked({ $_.Source.Background = "#222" $_.Source.BorderBrush = "#777" }) $cb.Tag = @{ ID = $ID; Index = $Unique } # Update global hash using the nested keys $cb.Add_Checked({ $Data = $_.Source.Tag $script:Options[$Data.ID][$Data.Index].Checked = $true }) $cb.Add_Unchecked({ $Data = $_.Source.Tag $script:Options[$Data.ID][$Data.Index].Checked = $false }) $script:Stack.Children.Add($cb) | Out-Null } function Window.AddTabControl { if (-not $script:Stack) { Write-Host "GUI not initialized"; return } $tabControl = New-Object System.Windows.Controls.TabControl $tabControl.Margin = "0,10,0,0" $tabControl.Height = [int]($script:WpfWindow.Height / 1.2) $tabControl.Width = [int]($script:WpfWindow.Width / 1.2) $tabControl.Background = "#1E1E1E" $tabControl.BorderBrush = "#1E1E1E" $tabControl.TabStripPlacement = "Top" # $tabControl.Padding = "5,2,5,2" $tabControl.Padding = "0,0,0,0" $tabControl.BorderThickness = 0 $script:Stack.Children.Add($tabControl) $script:Tabs = $tabControl } function Window.AddTab { param( [string]$Label, [scriptblock]$Script ) if (-not $script:Tabs) { Write-Host "TabControl not created"; return } $tabItem = New-Object System.Windows.Controls.TabItem $tabItem.Header = $Label $tabItem.Foreground = "#AAA" $tabItem.Background = "#1E1E1E" $tabItem.FontWeight = "Bold" # base style $tabItem.Background = "#222" $tabItem.BorderBrush = "#333" $tabItem.BorderThickness = 1 $tabItem.Margin = "2,0" $tabItem.Padding = "12,6" $tabItem.Foreground = "#CCCCCC" $tabItem.FontSize = 14 # hover $tabItem.Add_MouseEnter({ if (-not $_.Source.IsSelected) { $_.Source.Background = "#333" } }) $tabItem.Add_MouseLeave({ if (-not $_.Source.IsSelected) { $_.Source.Background = "#222" } }) # --- NEW: Create scrollable content for the tab --- $containerGrid = New-Object System.Windows.Controls.Grid $containerGrid.HorizontalAlignment = "Stretch" $containerGrid.VerticalAlignment = "Stretch" $row = New-Object System.Windows.Controls.RowDefinition $row.Height = "*" $containerGrid.RowDefinitions.Add($row) $col = New-Object System.Windows.Controls.ColumnDefinition $col.Width = "*" $containerGrid.ColumnDefinitions.Add($col) $scrollViewer = New-Object System.Windows.Controls.ScrollViewer $scrollViewer.VerticalScrollBarVisibility = "Hidden" # hides scrollbar $scrollViewer.HorizontalScrollBarVisibility = "Hidden" $scrollViewer.HorizontalAlignment = "Stretch" $scrollViewer.VerticalAlignment = "Stretch" $scrollViewer.Background = "#1E1E1E" $scrollStack = New-Object System.Windows.Controls.StackPanel $scrollStack.Margin = "5" $scrollStack.Background = "#1E1E1E" $scrollViewer.Content = $scrollStack $containerGrid.Children.Add($scrollViewer) [System.Windows.Controls.Grid]::SetRow($scrollViewer,0) [System.Windows.Controls.Grid]::SetColumn($scrollViewer,0) $tabItem.Content = $containerGrid # --- END NEW --- $script:Tabs.Items.Add($tabItem) # Temporarily redirect global stack to inner scrollable StackPanel $oldStack = $script:Stack $script:Stack = $scrollStack # Execute the user-provided scriptblock & $Script # Restore original stack $script:Stack = $oldStack } function Window.AddRadioButtonApply { param( [string]$Label, [string]$ID ) $btn = New-Object System.Windows.Controls.Button $btn.Content = $Label $btn.Height = 35 $btn.Width = 200 $btn.Margin = "5" $btn.FontSize = 13 $btn.Foreground = "White" $btn.Background = "#2D2D30" $btn.BorderBrush = "#3C3C3C" $btn.Cursor = "Hand" # Capture the options hash for this group safely $localOptions = if ($script:Options.ContainsKey($ID)) { $script:Options[$ID] } else { @{} } $localID = $ID $btn.Add_Click({ $optionsGroup = $localOptions if (-not $optionsGroup -or $optionsGroup.Count -eq 0) { Write-Debug "No Options Selected for group '$localID'" return } # Find the selected option in the hash $selected = $optionsGroup.GetEnumerator() | Where-Object { $_.Value.Checked } | Select-Object -ExpandProperty Key if (-not $selected) { Write-Debug "No Options Selected for group '$localID'" return } # Execute the action stored in the hash $action = $optionsGroup[$selected].Action if ($action) { & $action } else { Write-Host "No action defined for selected radio: $selected" } }.GetNewClosure()) $script:Stack.Children.Add($btn) | Out-Null } function Window.AddRadioOption { param( [string]$Label, [string]$ID = "default", [string]$Value = "default", [bool]$Default = $false, [scriptblock]$Action = {} ) # Ensure group exists if (-not $script:Options.ContainsKey($ID)) { $script:Options[$ID] = @{} } if ($Default) { foreach ($k in $script:Options[$ID].Keys) { $script:Options[$ID][$k].Checked = ($k -eq $Value) } } # Store the radio option in script:Options $script:Options[$ID][$Value] = @{ Checked = $Default Action = $Action } # Create the RadioButton control $rb = New-Object System.Windows.Controls.RadioButton $rb.Content = $Label $rb.GroupName = $ID $rb.IsChecked = $Default $rb.Margin = "0,5,0,5" $rb.Cursor = "Hand" $rb.Foreground = "White" $rb.FontSize = 14 $rb.Margin = "4" # Outer ring $rb.BorderBrush = "#777" $rb.BorderThickness = 2 $rb.Background = "#777" # Hover $rb.Add_MouseEnter({ $_.Source.BorderBrush = "#999" }) $rb.Add_MouseLeave({ $_.Source.BorderBrush = "#777" }) # Checked $rb.Add_Checked({ $_.Source.BorderBrush = "#3A7BFF" }) $rb.Add_Unchecked({ $_.Source.BorderBrush = "#777" }) # Tag stores the label and group reference (no Action here) $rb.Tag = @{ Value = $Value Group = $script:Options[$ID] } # When checked, update the Checked field in script:Options $rb.Add_Checked({ $tag = $_.Source.Tag $group = $tag.Group $value = $tag.Value foreach ($key in $group.Keys) { $group[$key].Checked = ($key -eq $value) } }) $script:Stack.Children.Add($rb) | Out-Null } function Window.AddDropDown { param( [Parameter(Mandatory)] [string]$ID, [Parameter(Mandatory)] [array]$Items, [Parameter()] [scriptblock]$Action = $null, [Parameter()] [int]$Width = 200, [Parameter()] [int]$Height = 32, [Parameter()] [string]$DisplayMember = "Display" ) # 1. Setup Colors (Pre-converted for Closure) $bc = New-Object System.Windows.Media.BrushConverter $colorMain = $bc.ConvertFromString("#222") $colorHover = $bc.ConvertFromString("#2E2E2E") $colorBorder = $bc.ConvertFromString("#555") # $colorFocus = $bc.ConvertFromString("#3A7BFF") $colorWhite = [System.Windows.Media.Brushes]::White # 2. Create the "Closed Box" (A Button) $btn = New-Object System.Windows.Controls.Button # $btn.Width = $Width $btn.Height = $Height $btn.Background = $colorMain $btn.Foreground = $colorWhite $btn.BorderBrush = $colorBorder $btn.BorderThickness = 1 $btn.HorizontalContentAlignment = "Left" $btn.Padding = "10,0,0,0" $btn.Margin = "5" # 3. Create the "Open Box" (Popup + ListBox) $popup = New-Object System.Windows.Controls.Primitives.Popup $popup.PlacementTarget = $btn $popup.Placement = [System.Windows.Controls.Primitives.PlacementMode]::Bottom $popup.StaysOpen = $false $popup.AllowsTransparency = $true $list = New-Object System.Windows.Controls.ListBox $list.Width = $Width $list.MaxHeight = 200 $list.Background = $colorMain $list.Foreground = $colorWhite $list.BorderBrush = $colorBorder $list.BorderThickness = 1 $list.ItemsSource = $Items if ($Items.Count -gt 0 -and $Items[0] -is [PSCustomObject]) { $list.DisplayMemberPath = $DisplayMember } # Style the ListBoxItems to be dark $itemStyle = New-Object System.Windows.Style -ArgumentList ([System.Windows.Controls.ListBoxItem]) $itemStyle.Setters.Add((New-Object System.Windows.Setter -ArgumentList ([System.Windows.Controls.ListBoxItem]::BackgroundProperty, $colorMain))) $itemStyle.Setters.Add((New-Object System.Windows.Setter -ArgumentList ([System.Windows.Controls.ListBoxItem]::ForegroundProperty, $colorWhite))) $list.ItemContainerStyle = $itemStyle $popup.Child = $list # 4. Create the "State Object" (To mimic SelectedItem) # Fixed Add-Member syntax $fakeCombo = New-Object PSObject $fakeCombo | Add-Member -MemberType NoteProperty -Name "SelectedItem" -Value $null $fakeCombo | Add-Member -MemberType NoteProperty -Name "InternalList" -Value $list # 5. Event Logic (Using GetNewClosure to keep variables alive) $btn.Add_Click({ $popup.IsOpen = $true }.GetNewClosure()) $list.Add_SelectionChanged({ param($s, $e) if ($null -eq $s.SelectedItem) { return } # Update the Fake Object and Button Text $fakeCombo.SelectedItem = $s.SelectedItem $displayVal = if ($s.SelectedItem.PSObject.Properties[$DisplayMember]) { $s.SelectedItem.$DisplayMember } else { $s.SelectedItem.ToString() } $btn.Content = $displayVal # Close the popup $popup.IsOpen = $false # Execute User Action if ($Action) { & $Action.GetNewClosure() } }.GetNewClosure()) # Visual Feedback $btn.Add_MouseEnter({ $this.Background = $colorHover }.GetNewClosure()) $btn.Add_MouseLeave({ $this.Background = $colorMain }.GetNewClosure()) # 6. Initialize Selection if ($Items.Count -gt 0) { $list.SelectedIndex = 0 $fakeCombo.SelectedItem = $Items[0] $btn.Content = if ($Items[0].PSObject.Properties[$DisplayMember]) { $Items[0].$DisplayMember } else { $Items[0].ToString() } } # 7. Registration in EasyGUI $script:WpfWindow.Inputs[$ID] = $fakeCombo $script:Stack.Children.Add($btn) } function Window.AddSuperApplyButton { param( [string]$Label, [string[]]$Groups # Array of Option/Radio group IDs ) $btn = New-Object System.Windows.Controls.Button $btn.Content = $Label $btn.Height = 35 $btn.Width = 250 $btn.Margin = "5" $btn.FontSize = 13 $btn.Background = "#2D2D30" $btn.Foreground = "White" $btn.BorderBrush = "#3C3C3C" $btn.HorizontalAlignment = "Center" $btn.Cursor = "Hand" # Capture references to all option groups inside the closure $groupsSnapshot = @{} foreach ($ID in $Groups) { $groupsSnapshot[$ID] = if ($script:Options -and $script:Options.ContainsKey($ID)) { $script:Options[$ID] } else { @{} } } $btn.Add_Click({ foreach ($ID in $groupsSnapshot.Keys) { $optionsGroup = $groupsSnapshot[$ID] if (-not $optionsGroup -or $optionsGroup.Count -eq 0) { Write-Debug "No Options Selected for group '$ID'" continue # skip this group, don't abort all } # Apply all checked options in this group $selected = $optionsGroup.GetEnumerator() | Where-Object { $_.Value.Checked } | Select-Object -ExpandProperty Key if (-not $selected) { Write-Debug "No Options Selected for group '$ID'" continue } foreach ($opt in $selected) { $action = $optionsGroup[$opt].Action if ($action) { & $action } } Write-Verbose "Group '$ID' applied: $($selected -join ', ')" } }.GetNewClosure()) $script:Stack.Children.Add($btn) | Out-Null } function Window.AddApplyOptionButton { param( [string]$Label = "Apply Options", [string]$ID = "default", [string]$Alignment = "Left" ) $btn = New-Object System.Windows.Controls.Button $btn.Content = $Label $btn.Margin = "5" $btn.Height = 35 $btn.Width = 200 $btn.FontSize = 13 $btn.Background = "#2D2D30" $btn.Foreground = "White" $btn.BorderBrush = "#3C3C3C" $btn.HorizontalAlignment = $Alignment $btn.Cursor = "Hand" # Capture the ID for the closure $TargetID = $ID $EntireOptions = $script:Options $btn.Add_Click({ Write-Verbose "Applying options for: $TargetID" # 2. Check if the SPECIFIC group exists inside the global variable if (-not $EntireOptions.ContainsKey($TargetID)) { Write-Debug "No Options found in group '$TargetID'" return } # 3. Safe to proceed $group = $EntireOptions[$TargetID] $anySelected = $false foreach ($key in $group.Keys) { $item = $group[$key] if ($item.Checked -eq $true) { $anySelected = $true if ($item.Action) { & $item.Action } } } if (-not $anySelected) { Write-Debug "No Options Selected for group '$TargetID'" } }.GetNewClosure()) $script:Stack.Children.Add($btn) | Out-Null } function Window.GetID { param( [Parameter(Mandatory)] [string]$ID, [string]$Value ) if (-not $script:Options.ContainsKey($ID)) { Write-Verbose "Legacy Inputs Does not exist" } $entry = $script:Options[$ID] # Radio group if ($Value) { if ($entry -is [hashtable] -and $entry.ContainsKey($Value)) { return [bool]$entry[$Value].Checked } return $false } # Checkbox if ($entry -is [hashtable] -and $entry.ContainsKey('Checked')) { return [bool]$entry.Checked } # Modern Inputs if (-not $script:WpfWindow.Inputs.ContainsKey($ID)) { Write-Verbose "Modern Inputs Does not exist" return $false } $entry = $script:WpfWindow.Inputs[$ID] # Some GUI Functions if ($entry -is [hashtable] -and $entry.ContainsKey('Value')) { return $entry.Value } # DropDown if ($entry -is [hashtable] -and $entry.ContainsKey('SelectedItem')) { return $entry.SelectedItem } # Input Box and Text Related GUI Functions if ($entry -is [hashtable] -and $entry.ContainsKey('Text')) { return $entry.Text } # Media and File related GUI Functions if ($entry -is [hashtable] -and $entry.ContainsKey('Source')) { return $entry.Source } # Numeric Up Down and more if ($entry -is [hashtable] -and $entry.ContainsKey('State')) { if ($entry.State -is [hashtable] -and $entry.ContainsKey('Value')) { return $entry.State.Value } } Write-Debug "All Failed in Window.GetID, Returning false" return $false } function Select-Path { param ( [Parameter(Mandatory=$true)] [ValidateSet("OpenFile","SaveFile","PickFolder")] [string]$Mode, [string]$Title = "Select item", [string]$Filter = "All files (*.*)|*.*", [string]$DefaultFileName = "NewFile.txt", [string]$InitialDirectory = [Environment]::GetFolderPath("Desktop") ) switch ($Mode) { "OpenFile" { $dlg = New-Object System.Windows.Forms.OpenFileDialog $dlg.Title = $Title $dlg.Filter = $Filter $dlg.InitialDirectory = $InitialDirectory if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { return $dlg.FileName } } "SaveFile" { $dlg = New-Object System.Windows.Forms.SaveFileDialog $dlg.Title = $Title $dlg.Filter = $Filter $dlg.FileName = $DefaultFileName $dlg.InitialDirectory = $InitialDirectory if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { return $dlg.FileName } } "PickFolder" { $dlg = New-Object System.Windows.Forms.FolderBrowserDialog $dlg.Description = $Title $dlg.SelectedPath = $InitialDirectory if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { return $dlg.SelectedPath } } } } function ShowWindow { param([bool]$DoNotBlock = $false) if (-not $script:WpfWindow) { Write-Host "No window created."; return } if ($DoNotBlock) { Write-Debug "Loading Non-Blocking ShowWindow" $null = $script:WpfWindow.Show() } else { Write-Debug "Loading Blocking ShowWindow" $null = $script:WpfWindow.ShowDialog() } } function Get-GUIWindow { return $script:WpfWindow } function Set-GUIWindow { param([Parameter(Mandatory)]$NewValue) $script:WpfWindow = $NewValue } function Adjust.ControlSizes { [CmdletBinding()] param( [Parameter()] [double]$Increase, [Parameter()] [double]$Decrease, [switch]$SetDefault ) Write-Warning "Window.ControlSizes is experimental and may break the GUI. Use at your own risk." # Validate that Increase and Decrease are not used together if ($Increase -and $Decrease) { Write-Error "Use either -Increase or -Decrease, not both." return } function AdjustRecursive { param([Parameter(Mandatory = $true)]$Control) # --- WPF and WinForms --- # FontSize / Font if ($Control.PSObject.Properties.Name -contains 'FontSize') { # Save default if it doesn't exist if (-not $Control.PSObject.Properties.Match('FontSize_Default')) { $Control | Add-Member -MemberType NoteProperty -Name FontSize_Default -Value $Control.FontSize } if ($SetDefault) { $Control.FontSize = $Control.FontSize_Default $Control.PSObject.Properties.Remove('FontSize_Default') } elseif ($Increase) { $Control.FontSize *= $Increase } elseif ($Decrease) { $Control.FontSize /= $Decrease } } elseif ($Control.PSObject.Properties.Name -contains 'Font') { if (-not $Control.PSObject.Properties.Match('FontSize_Default')) { $Control | Add-Member -MemberType NoteProperty -Name FontSize_Default -Value $Control.Font.Size } if ($SetDefault) { $Control.Font = New-Object System.Drawing.Font($Control.Font.FontFamily, $Control.FontSize_Default) $Control.PSObject.Properties.Remove('FontSize_Default') } elseif ($Increase) { $Control.Font = New-Object System.Drawing.Font($Control.Font.FontFamily, $Control.Font.Size * $Increase) } elseif ($Decrease) { $Control.Font = New-Object System.Drawing.Font($Control.Font.FontFamily, $Control.Font.Size / $Decrease) } } # Width if (($Control.PSObject.Properties.Name -contains 'Width') -and (-not ($Control.PSObject.Properties.Name -contains "WindowStartupLocation"))) { if (-not $Control.PSObject.Properties.Match('Width_Default')) { $Control | Add-Member -MemberType NoteProperty -Name Width_Default -Value $Control.Width } if ($SetDefault) { $Control.Width = $Control.Width_Default $Control.PSObject.Properties.Remove('Width_Default') } elseif ($Increase) { $Control.Width *= $Increase } elseif ($Decrease) { $Control.Width /= $Decrease } } # Height if (($Control.PSObject.Properties.Name -contains 'Height') -and (-not ($Control.PSObject.Properties.Name -contains "WindowStartupLocation"))) { if (-not $Control.PSObject.Properties.Match('Height_Default')) { $Control | Add-Member -MemberType NoteProperty -Name Height_Default -Value $Control.Height } if ($SetDefault) { $Control.Height = $Control.Height_Default $Control.PSObject.Properties.Remove('Height_Default') } elseif ($Increase) { $Control.Height *= $Increase } elseif ($Decrease) { $Control.Height /= $Decrease } } # --- Recursively process child controls --- if ($Control.PSObject.Properties.Name -contains 'Children') { foreach ($child in $Control.Children) { AdjustRecursive -Control $child } } elseif ($Control.PSObject.Properties.Name -contains 'Controls') { foreach ($child in $Control.Controls) { AdjustRecursive -Control $child } } } # Loop through all windows foreach ($win in $script:WpfWindow) { AdjustRecursive -Control $win } } function Set.WindowSize { param( [int]$Width, [int]$Height ) # Ensure the EasyGUI form object exists if (-not $script:WpfWindow) { Write-Error "EasyGUI window is not initialized." return } # Set new dimensions $script:WpfWindow.Width = $Width $script:WpfWindow.Height = $Height } function Window.Title { param([string]$Title) if ($script:WpfWindow) { $script:WpfWindow.Text = $Title } } function Window.AddImage { param( [Parameter(Mandatory=$true)] [string]$Path, # Path to image file [string]$ID = $null, # Optional unique ID for updating [int]$Width = 100, # Optional width [int]$Height = 100 # Optional height ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } # Make sure Inputs hash exists if (-not ($script:WpfWindow | Get-Member -Name Inputs)) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{} } # If ID is provided, update existing Image if it exists if ($ID -and $script:WpfWindow.Inputs.ContainsKey($ID)) { $existing = $script:WpfWindow.Inputs[$ID] $bitmap = New-Object System.Windows.Media.Imaging.BitmapImage $bitmap.BeginInit() $bitmap.UriSource = [Uri]::new($Path, [UriKind]::Absolute) $bitmap.EndInit() $existing.Source = $bitmap return } # Create a new Image element $image = New-Object System.Windows.Controls.Image $bitmap = New-Object System.Windows.Media.Imaging.BitmapImage $bitmap.BeginInit() $bitmap.UriSource = [Uri]::new($Path, [UriKind]::Absolute) $bitmap.DecodePixelWidth = $Width $bitmap.DecodePixelHeight = $Height $bitmap.EndInit() $image.Source = $bitmap $image.Width = $Width $image.Height = $Height $image.Margin = "5,5,5,10" # Store reference if ID is given if ($ID) { $script:WpfWindow.Inputs[$ID] = $image } $script:Stack.Children.Add($image) } function Window.ProgressBar { param( [string]$ID = $null, [int]$Width = 300, [int]$Height = 20, [bool]$IsIndeterminate = $true # Looping style by default ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } # Make sure Inputs hash exists if (-not ($script:WpfWindow | Get-Member -Name Inputs)) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{} } # If ID is provided and al y exists, return existing if ($ID -and $script:WpfWindow.Inputs.ContainsKey($ID)) { return $script:WpfWindow.Inputs[$ID] } # Create a new ProgressBar $progressBar = New-Object System.Windows.Controls.ProgressBar $progressBar.Width = $Width $progressBar.Height = $Height $progressBar.Margin = "5,5,5,10" $progressBar.Minimum = 0 $progressBar.Maximum = 100 $progressBar.Value = 0 $progressBar.IsIndeterminate = $IsIndeterminate # Store reference if ID is given if ($ID) { $script:WpfWindow.Inputs[$ID] = $progressBar } $script:Stack.Children.Add($progressBar) | Out-Null # return $progressBar } function Window.GroupBox { param( [string]$Header = "Group", [string]$ID = $null, # Optional unique ID [scriptblock]$Action = $null # Script block to add child controls ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } # Make sure Inputs hash exists if (-not ($script:WpfWindow | Get-Member -Name Inputs)) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{} } if ($ID) { # If GroupBox already exists, just return it if ($script:WpfWindow.Inputs.ContainsKey($ID)) { # return $script:WpfWindow.Inputs[$ID] } } # Create the GroupBox $groupBox = New-Object System.Windows.Controls.GroupBox $groupBox.Header = $Header $groupBox.Margin = "5,5,5,5" $groupBox.Padding = "5" $GroupBox.Background = "#222222" $GroupBox.Foreground = "White" $GroupBox.BorderBrush = "#444444" # Internal StackPanel to hold child controls $stackPanel = New-Object System.Windows.Controls.StackPanel $groupBox.Content = $stackPanel # Store reference if ID is given if ($ID) { $script:WpfWindow.Inputs[$ID] = $groupBox } # Add GroupBox to current Stack $script:Stack.Children.Add($groupBox) | Out-Null # Run the action script block in the context of the internal StackPanel if ($Action) { # Temporarily set $script:Stack to the new internal panel $oldStack = $script:Stack $script:Stack = $stackPanel & $Action $script:Stack = $oldStack } # return $stackPanel } # --- Helpers --- function Window.GetRawID { param([string]$ID) if (-not $script:WpfWindow.Inputs.ContainsKey($ID)) { Write-Verbose "Windows.GetRawID Failed because the ID Does not exist" return $null } $entry = $script:WpfWindow.Inputs[$ID] return $entry } function Window.SetRawID { param( [string]$ID, $Value ) $item = Window.GetRawID $ID if ($null -eq $item) { # This triggers your error box [System.Windows.MessageBox]::Show("Error: Window.SetRawID Failed, ID '$ID' not found.") return } try { $ID = $Value } catch { [System.Windows.MessageBox]::Show("Error: Window.SetRawID Failed, Cannot Set ID $ID to $Value") } } # --- ContextMenu Control --- function Window.ContextMenu { param( [string]$ID = $null, [scriptblock]$Items # ScriptBlock to define menu items ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } # Ensure Inputs hash exists if (-not ($script:WpfWindow | Get-Member -Name Inputs)) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{} } if ($ID) { # Replace existing ContextMenu if it exists if ($script:WpfWindow.Inputs.ContainsKey($ID)) { $existingMenu = $script:WpfWindow.Inputs[$ID] $existingMenu.Items.Clear() $contextMenu = $existingMenu } else { $contextMenu = New-Object System.Windows.Controls.ContextMenu $script:WpfWindow.Inputs[$ID] = $contextMenu } } else { $contextMenu = New-Object System.Windows.Controls.ContextMenu } $contextMenu.Background = "#2b2b2b" $contextMenu.Foreground = "#2b2b2b" # Temporarily store $script:CurrentMenu to let Items script block add menu items $script:CurrentMenu = $contextMenu & $Items Remove-Variable CurrentMenu -Scope Script -ErrorAction SilentlyContinue # Attach to the last added control in the stack, if exists if ($script:Stack.Children.Count -gt 0) { $lastControl = $script:Stack.Children[$script:Stack.Children.Count - 1] $lastControl.ContextMenu = $contextMenu } } # --- Helper to add MenuItem inside ContextMenu --- function Window.MenuItem { param( [string]$Header, [scriptblock]$Action = $null ) if (-not $script:CurrentMenu) { Write-Host "No ContextMenu is active"; return } $menuItem = New-Object System.Windows.Controls.MenuItem $menuItem.Header = $Header $menuItem.Background = "#2b2b2b" $menuItem.Foreground = "White" if ($Action) { $menuItem.Add_Click($Action) } $script:CurrentMenu.Items.Add($menuItem) } function Window.NumericUpDown { param( [string]$ID = $null, [decimal]$Value = 0, [decimal]$Minimum = 0, [decimal]$Maximum = 100, [decimal]$Increment = 1, [string]$Label = $null ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } if ($null -eq $script:WpfWindow.Inputs) { $script:WpfWindow.Inputs = @{} } # 1. Main Container $panel = New-Object System.Windows.Controls.StackPanel $panel.Orientation = "Horizontal" $panel.Margin = "5,2,5,2" # 2. Label styling if ($Label) { $textBlock = New-Object System.Windows.Controls.TextBlock $textBlock.Text = $Label $textBlock.Margin = "0,0,10,0" $textBlock.VerticalAlignment = "Center" $textBlock.Foreground = "White" $panel.Children.Add($textBlock) } # 3. Modern Dark TextBox $numInput = New-Object System.Windows.Controls.TextBox $numInput.Text = $Value.ToString() $numInput.Width = 60 $numInput.Height = 26 $numInput.Background = "#333333" $numInput.Foreground = "White" $numInput.BorderBrush = "#555555" $numInput.VerticalContentAlignment = "Center" $numInput.HorizontalContentAlignment = "Center" $numInput.BorderThickness = 1 # 4. Modern Button Stack $btnStack = New-Object System.Windows.Controls.StackPanel $btnStack.Orientation = "Vertical" $btnStack.VerticalAlignment = "Center" # Up Button Styling $btnUp = New-Object System.Windows.Controls.Button $btnUp.Content = [char]0x25B2 # Up Triangle $btnUp.FontSize = 7 $btnUp.Height = 13 $btnUp.Width = 18 $btnUp.Background = "#444444" $btnUp.Foreground = "White" $btnUp.BorderThickness = 0 # Down Button Styling $btnDown = New-Object System.Windows.Controls.Button $btnDown.Content = [char]0x25BC # Down Triangle $btnDown.FontSize = 7 $btnDown.Height = 13 $btnDown.Width = 18 $btnDown.Background = "#444444" $btnDown.Foreground = "White" $btnDown.BorderThickness = 0 # 5. Logic (Scoped via closures) $btnUp.Add_Click({ [decimal]$v = 0 if ([decimal]::TryParse($numInput.Text, [ref]$v)) { if (($v + $Increment) -le $Maximum) { $numInput.Text = ($v + $Increment).ToString() } } }.GetNewClosure()) $btnDown.Add_Click({ [decimal]$v = 0 if ([decimal]::TryParse($numInput.Text, [ref]$v)) { if (($v - $Increment) -ge $Minimum) { $numInput.Text = ($v - $Increment).ToString() } } }.GetNewClosure()) # Assembly $btnStack.Children.Add($btnUp) $btnStack.Children.Add($btnDown) $panel.Children.Add($numInput) $panel.Children.Add($btnStack) # 6. REGISTER ID (Matched to your GetID .State.Value pattern) if ($ID) { $stateObj = New-Object PSObject $stateObj | Add-Member -MemberType ScriptProperty -Name "Value" -Value { return [decimal]$numInput.Text }.GetNewClosure() -SecondValue { $numInput.Text = $args[0].ToString() }.GetNewClosure() $entry = New-Object PSObject $entry | Add-Member -NotePropertyName "State" -NotePropertyValue $stateObj $script:WpfWindow.Inputs[$ID] = $entry } $script:Stack.Children.Add($panel) | Out-Null } # --- Helper usage with GetID / SetID --- # Get numeric value: $val = Window.GetID "MyNumeric" # Set numeric value: Window.SetID "MyNumeric" 50 function Window.TreeView { param( [string]$ID = $null, [hashtable]$Items = @{}, [hashtable]$Content = @{}, [int]$Width = 400, [int]$Height = 300 ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } if (-not ($script:WpfWindow | Get-Member -Name Inputs)) { $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{} } $script:TreeViewGrid = New-Object System.Windows.Controls.Grid $script:TreeViewGrid.Width = $Width $script:TreeViewGrid.Height = $Height $col1 = New-Object System.Windows.Controls.ColumnDefinition $col1.Width = "200" $col2 = New-Object System.Windows.Controls.ColumnDefinition $col2.Width = "*" $script:TreeViewGrid.ColumnDefinitions.Add($col1) $script:TreeViewGrid.ColumnDefinitions.Add($col2) $script:TreeViewControl = New-Object System.Windows.Controls.TreeView [System.Windows.Controls.Grid]::SetColumn($script:TreeViewControl, 0) $script:TreeViewGrid.Children.Add($script:TreeViewControl) | Out-Null $script:TreeViewContentPanel = New-Object System.Windows.Controls.StackPanel [System.Windows.Controls.Grid]::SetColumn($script:TreeViewContentPanel, 1) $script:TreeViewGrid.Children.Add($script:TreeViewContentPanel) | Out-Null if ($ID) { $script:WpfWindow.Inputs[$ID] = @{ Tree = $script:TreeViewControl ContentPanel = $script:TreeViewContentPanel Items = $Items Content = $Content } } function Add-TreeItems { param($parent, $items) foreach ($key in $items.Keys) { $node = New-Object System.Windows.Controls.TreeViewItem $node.Header = $key $parent.Items.Add($node) | Out-Null # Use script-scoped panel and content hash $node.Add_Selected({ $panel = $script:TreeViewContentPanel $contentHash = $Content if ($null -ne $panel) { $panel.Dispatcher.Invoke([action]{ $panel.Children.Clear() $header = $this.Header if ($contentHash.ContainsKey($header)) { $val = $contentHash[$header] if ($val -is [scriptblock]) { & $val.Invoke() } elseif ($val -is [System.Windows.UIElement]) { $panel.Children.Add($val) | Out-Null } elseif ($val -is [string]) { $lbl = New-Object System.Windows.Controls.Label $lbl.Content = $val $panel.Children.Add($lbl) | Out-Null } } else { $lbl = New-Object System.Windows.Controls.Label $lbl.Content = "No content for '$header'" $panel.Children.Add($lbl) | Out-Null } }) } }) if ($items[$key] -is [hashtable] -and $items[$key].Count -gt 0) { Add-TreeItems -parent $node -items $items[$key] } } } Add-TreeItems -parent $script:TreeViewControl -items $Items $script:Stack.Children.Add($script:TreeViewGrid) | Out-Null } function Window.GetInputBox { param( [string]$InputID ) return $script:WpfWindow.Inputs[$InputID].Text } function Window.ControlLockCheckBox { param( [string]$ID, [string]$Label, [bool]$Lock = $true # $true = lock, $false = unlock ) if (-not $script:Options.ContainsKey($ID)) { return } # Find the control in the StackPanel foreach ($child in $script:Stack.Children) { if ($child -is [System.Windows.Controls.CheckBox] -and $child.Tag.Label -eq $Label) { $child.IsEnabled = -not $Lock return } } } function Window.ControlLockEntireRadioID { param( [string]$ID, [bool]$Lock = $true ) if (-not $script:Options.ContainsKey($ID)) { return } # Iterate through all controls in the StackPanel foreach ($child in $script:Stack.Children) { if ($child -is [System.Windows.Controls.RadioButton]) { $tag = $child.Tag if ($tag.GroupID -eq $ID) { $child.IsEnabled = -not $Lock } } } } function Window.Close { param() # Ensure GUI exists if (-not $script:WpfWindow) { Write-Host "GUI not initialized" return } try { # Close on the UI thread synchronously $script:WpfWindow.Dispatcher.Invoke({ # Ensure the window is not already closed if ($script:WpfWindow -and -not $script:WpfWindow.Dispatcher.HasShutdownStarted) { $script:WpfWindow.Close() } }) # Cleanup AFTER the window has fully closed $script:Stack = $null if ($script:WpfWindow -and ($script:WpfWindow | Get-Member -Name Inputs)) { $script:WpfWindow.Inputs.Clear() } $script:WpfWindow = $null } catch { Write-Host "Error closing window: $_" } } function Window.ToggleVisibility { param( [bool]$Visible ) if (-not $script:WpfWindow) { Write-Host "GUI not initialized" return } # If user didn't specify -Visible, auto-toggle if (-not $PSBoundParameters.ContainsKey("Visible")) { $Visible = -not ($script:WpfWindow.Visibility -eq [System.Windows.Visibility]::Visible) } $script:WpfWindow.Dispatcher.Invoke({ if ($Visible) { $script:WpfWindow.Show() $script:WpfWindow.Activate() } else { $script:WpfWindow.Hide() } }) } function Window.Control { param( [bool]$TopMost, [ValidateSet("Visible","Hidden","Empty","None")] [string]$ControlBars = $null, [string]$SetTitle = $null, [bool]$Resize ) if (-not $script:WpfWindow) { Write-Host "GUI not initialized" return } # ----------------------------------------------------------- # Load Native API if control bars will require it # ----------------------------------------------------------- if ($ControlBars -and ($ControlBars -eq "Empty") -and -not ([Type]::GetType("EasyGUI.Native", $false))) { Add-Type -Name "Native" -Namespace "EasyGUI" -MemberDefinition @" [System.Runtime.InteropServices.DllImport("user32.dll")] public static extern int GetWindowLong(System.IntPtr hWnd, int nIndex); [System.Runtime.InteropServices.DllImport("user32.dll")] public static extern int SetWindowLong(System.IntPtr hWnd, int nIndex, int dwNewLong); "@ } # ----------------------------------------------------------- # Apply changes inside UI thread # ----------------------------------------------------------- $script:WpfWindow.Dispatcher.Invoke({ # ------------------------- # Title (SetTitle) # ------------------------- if ($PSBoundParameters.ContainsKey("SetTitle")) { $script:WpfWindow.Title = $SetTitle } # ------------------------- # Resize control # ------------------------- if ($PSBoundParameters.ContainsKey("Resize")) { if ($Resize) { # Enable resizing $script:WpfWindow.ResizeMode = "CanResize" # If ControlBars made WindowStyle None, restore border if ($script:WpfWindow.WindowStyle -eq "None") { $script:WpfWindow.WindowStyle = "SingleBorderWindow" } } else { # Disable resizing $script:WpfWindow.ResizeMode = "NoResize" } } # ------------------------- # TopMost control # ------------------------- if ($PSBoundParameters.ContainsKey("TopMost")) { $script:WpfWindow.Topmost = $TopMost } # ------------------------- # ControlBars logic # ------------------------- if ($ControlBars) { switch ($ControlBars) { "Visible" { $script:WpfWindow.WindowStyle = "SingleBorderWindow" if (-not $PSBoundParameters.ContainsKey("Resize")) { $script:WpfWindow.ResizeMode = "CanResize" } } "Hidden" { $script:WpfWindow.WindowStyle = "None" if (-not $PSBoundParameters.ContainsKey("Resize")) { $script:WpfWindow.ResizeMode = "NoResize" } } "Empty" { # Remove Min/Max/Close + system menu $script:WpfWindow.WindowStyle = "SingleBorderWindow" if (-not $PSBoundParameters.ContainsKey("Resize")) { $script:WpfWindow.ResizeMode = "NoResize" } $hwnd = (New-Object System.Windows.Interop.WindowInteropHelper $script:WpfWindow).Handle $GWL_STYLE = -16 $WS_SYSMENU = 0x80000 $WS_MINIMIZEBOX = 0x20000 $WS_MAXIMIZEBOX = 0x10000 $current = [EasyGUI.Native]::GetWindowLong($hwnd, $GWL_STYLE) $newStyle = $current -band (-bnot ($WS_SYSMENU -bor $WS_MINIMIZEBOX -bor $WS_MAXIMIZEBOX)) [EasyGUI.Native]::SetWindowLong($hwnd, $GWL_STYLE, $newStyle) } "None" { $script:WpfWindow.WindowStyle = "None" if (-not $PSBoundParameters.ContainsKey("Resize")) { $script:WpfWindow.ResizeMode = "NoResize" } } } } }) } function Window.AddToggleButton { param( [Parameter(Position=0)] [string]$ID = $null, [string]$Label = $null, [string]$Foreground = "White", [bool]$Checked = $false ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } # Ensure Inputs hashtable exists if (-not ($script:WpfWindow | Get-Member -Name "Inputs")) { $script:WpfWindow | Add-Member -NoteProperty -Name "Inputs" -Value @{} -Force } $panel = New-Object System.Windows.Controls.StackPanel $panel.Orientation = "Horizontal" $panel.Margin = "5" # $textBlock = New-Object System.Windows.Controls.TextBlock # $textBlock.Text = $Label # $textBlock.Foreground = "White" # $textBlock.VerticalAlignment = "Center" # $textBlock.Margin = "0,0,15,0" # $panel.Children.Add($textBlock) if ($Label) { $textBlock = New-Object System.Windows.Controls.TextBlock $textBlock.Text = $Label $textBlock.FontSize = 16 $textBlock.Margin = "0,0,10,0" $textBlock.VerticalAlignment = "Center" $textBlock.Foreground = "White" $panel.Children.Add($textBlock) } # 1. Track Setup $track = New-Object System.Windows.Controls.Border $track.Width = 40; $track.Height = 20; $track.CornerRadius = 10; $track.Cursor = "Hand" $startHex = if ($Checked) { "#0078D7" } else { "#555555" } $track.Background = New-Object System.Windows.Media.SolidColorBrush ([System.Windows.Media.ColorConverter]::ConvertFromString($startHex)) # 2. Thumb Setup $thumb = New-Object System.Windows.Controls.Border $thumb.Width = 14; $thumb.Height = 14; $thumb.CornerRadius = 7; $thumb.Background = "White" $thumb.VerticalAlignment = "Center" $thumb.Margin = if ($Checked) { "22,0,0,0" } else { "2,0,0,0" } $thumb.HorizontalAlignment = "Left" $track.Child = $thumb # 3. Define the Animation Logic as a ScriptBlock $animBlock = { param($tTrack, $tThumb, [bool]$state) $duration = [System.Windows.Duration][TimeSpan]::FromSeconds(0.15) # Margin Animation $marginTo = if ($state) { "22,0,0,0" } else { "2,0,0,0" } $mAnim = New-Object System.Windows.Media.Animation.ThicknessAnimation $mAnim.To = [System.Windows.Thickness]$marginTo $mAnim.Duration = $duration # Color Animation $colorHex = if ($state) { "#0078D7" } else { "#555555" } $cAnim = New-Object System.Windows.Media.Animation.ColorAnimation $cAnim.To = [System.Windows.Media.ColorConverter]::ConvertFromString($colorHex) $cAnim.Duration = $duration $tThumb.BeginAnimation([System.Windows.Controls.Border]::MarginProperty, $mAnim) $tTrack.Background.BeginAnimation([System.Windows.Media.SolidColorBrush]::ColorProperty, $cAnim) } # 4. Attach logic and state to the track so it's always accessible $track | Add-Member -NotePropertyName "AnimLogic" -NotePropertyValue $animBlock $track | Add-Member -NotePropertyName "IsChecked" -NotePropertyValue $Checked # Mouse Click Event $track.Add_MouseDown({ # Toggle state $this.IsChecked = -not $this.IsChecked # Run the logic attached to 'this' & $this.AnimLogic $this $this.Child $this.IsChecked }.GetNewClosure()) # 5. ID Registration if ($ID) { $stateObj = New-Object PSObject $stateObj | Add-Member -MemberType ScriptProperty -Name "Value" -Value { return [bool]$track.IsChecked }.GetNewClosure() -SecondValue { $goal = [bool]$args[0] if ($track.IsChecked -ne $goal) { $track.IsChecked = $goal & $track.AnimLogic $track $thumb $goal } }.GetNewClosure() $entry = New-Object PSObject $entry | Add-Member -NotePropertyName "State" -NotePropertyValue $stateObj $script:WpfWindow.Inputs[$ID] = $entry } $panel.Children.Add($track) $script:Stack.Children.Add($panel) | Out-Null } function Window.SetID { param( [Parameter(Mandatory)] $ID, [Parameter(Mandatory)] $Value, $DoubleID ) if (-not $script:Options.ContainsKey($ID)) { Write-Verbose "Legacy Inputs Does not exist" } $entry = $script:Options[$ID] # Radio group if ($DoubleID) { if ($entry -is [hashtable] -and $entry.ContainsKey($ID)) { if ($entry -is [hashtable] -and $entry.ContainsKey($DoubleID)) { $entry[$DoubleID].Checked = $Value return $true } } } # Checkbox if ($entry -is [hashtable] -and $entry.ContainsKey('Checked')) { $entry.Checked = $Value return $true } # Modern Inputs if (-not $script:WpfWindow.Inputs.ContainsKey($ID)) { Write-Verbose "Modern Inputs Does not exist" return $false } $entry = $script:WpfWindow.Inputs[$ID] # Some GUI Functions if ($entry -is [hashtable] -and $entry.ContainsKey('Value')) { $entry.Value = $Value return $true } # DropDown if ($entry -is [hashtable] -and $entry.ContainsKey('SelectedItem')) { $entry.SelectedItem = $Value return $true } # Input Box and Text Related GUI Functions if ($entry -is [hashtable] -and $entry.ContainsKey('Text')) { $entry.Text = $Value return $true } # Media and File related GUI Functions if ($entry -is [hashtable] -and $entry.ContainsKey('Source')) { $entry.Source = $Value return $true } # Numeric Up Down and more if ($entry -is [hashtable] -and $entry.ContainsKey('State')) { if ($entry.State -is [hashtable] -and $entry.ContainsKey('Value')) { $entry.State.Value = $Value return $true } } EasyGUI.ErrorOut "ID '$ID' is either broken or not supported." return $false } function Window.AddDatePicker { param( [Parameter(Position=0)] [string]$ID = $null, [string]$Label = "Select Date", [datetime]$SelectedDate = (Get-Date) ) if (-not $script:Stack) { Write-Host "GUI not initialized"; return } # Setup Panel & Label $panel = New-Object System.Windows.Controls.StackPanel -Property @{ Orientation = "Horizontal"; Margin = "5" } $textBlock = New-Object System.Windows.Controls.TextBlock -Property @{ Text = $Label; Foreground = "White"; VerticalAlignment = "Center"; Width = 120; Margin = "0,0,15,0" } $panel.Children.Add($textBlock) # DatePicker Control $datePicker = New-Object System.Windows.Controls.DatePicker $datePicker.SelectedDate = $SelectedDate $datePicker.Width = 150 $panel.Children.Add($datePicker) # REGISTER ID - Using Hashtable to satisfy your GetID check if ($ID) { $entry = @{} $entryObject = New-Object PSObject $entryObject | Add-Member -MemberType ScriptProperty -Name "Value" -Value { return $datePicker.SelectedDate }.GetNewClosure() -SecondValue { param($NewDate) # 1. Update the underlying data $datePicker.SelectedDate = [datetime]$NewDate # 2. FORCE the GUI to refresh the specific control $datePicker.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action]{ $datePicker.InvalidateVisual() }) }.GetNewClosure() $entry["Value"] = $entryObject.Value $entry["Control"] = $datePicker $script:WpfWindow.Inputs[$ID] = $entry } $script:Stack.Children.Add($panel) | Out-Null } Write-Verbose "All EasyGUI Functions Sucessfully loaded." |