function Invoke-WPFUIElements { <# .SYNOPSIS Adds UI elements to a specified Grid in the WinUtil GUI based on a JSON configuration. .PARAMETER configVariable The variable/link containing the JSON configuration. .PARAMETER targetGridName The name of the grid to which the UI elements should be added. .PARAMETER columncount The number of columns to be used in the Grid. If not provided, a default value is used based on the panel. .EXAMPLE Invoke-WPFUIElements -configVariable $sync.configs.applications -targetGridName "install" -columncount 5 .NOTES Future me/contributor: If possible, please wrap this into a runspace to make it load all panels at the same time. #> param( [Parameter(Mandatory, Position = 0)] [PSCustomObject]$configVariable, [Parameter(Mandatory, Position = 1)] [string]$targetGridName, [Parameter(Mandatory, Position = 2)] [int]$columncount ) $window = $sync.form $borderstyle = $window.FindResource("BorderStyle") $HoverTextBlockStyle = $window.FindResource("HoverTextBlockStyle") $ColorfulToggleSwitchStyle = $window.FindResource("ColorfulToggleSwitchStyle") $ToggleButtonStyle = $window.FindResource("ToggleButtonStyle") if (!$borderstyle -or !$HoverTextBlockStyle -or !$ColorfulToggleSwitchStyle) { throw "Failed to retrieve Styles using 'FindResource' from main window element." } $targetGrid = $window.FindName($targetGridName) if (!$targetGrid) { throw "Failed to retrieve Target Grid by name, provided name: $targetGrid" } # Clear existing ColumnDefinitions and Children $targetGrid.ColumnDefinitions.Clear() | Out-Null $targetGrid.Children.Clear() | Out-Null # Add ColumnDefinitions to the target Grid for ($i = 0; $i -lt $columncount; $i++) { $colDef = New-Object Windows.Controls.ColumnDefinition $colDef.Width = New-Object System.Windows.GridLength([double]1, [System.Windows.GridUnitType]::Star) $targetGrid.ColumnDefinitions.Add($colDef) | Out-Null } # Convert PSCustomObject to Hashtable $configHashtable = @{} $configVariable.PSObject.Properties.Name | ForEach-Object { $configHashtable[$_] = $configVariable.$_ } $radioButtonGroups = @{} $organizedData = @{} # Iterate through JSON data and organize by panel and category foreach ($entry in $configHashtable.Keys) { $entryInfo = $configHashtable[$entry] # Create an object for the application $entryObject = [PSCustomObject]@{ Name = $entry Category = $entryInfo.Category Content = $entryInfo.Content Panel = if ($entryInfo.Panel) { $entryInfo.Panel } else { "0" } Link = $entryInfo.link Description = $entryInfo.description Type = $entryInfo.type ComboItems = $entryInfo.ComboItems ComboDescriptions = $entryInfo.ComboDescriptions Registry = $entryInfo.registry Checked = $entryInfo.Checked ButtonWidth = $entryInfo.ButtonWidth GroupName = $entryInfo.GroupName # Added for RadioButton groupings } if (-not $organizedData.ContainsKey($entryObject.Panel)) { $organizedData[$entryObject.Panel] = @{} } if (-not $organizedData[$entryObject.Panel].ContainsKey($entryObject.Category)) { $organizedData[$entryObject.Panel][$entryObject.Category] = @() } # Store application data in an array under the category $organizedData[$entryObject.Panel][$entryObject.Category] += $entryObject } # Initialize panel count $panelcount = 0 # Iterate through 'organizedData' by panel, category, and application $count = 0 foreach ($panelKey in ($organizedData.Keys | Sort-Object)) { # Create a Border for each column $border = New-Object Windows.Controls.Border $border.VerticalAlignment = "Stretch" [System.Windows.Controls.Grid]::SetColumn($border, $panelcount) $border.style = $borderstyle $targetGrid.Children.Add($border) | Out-Null # Use a DockPanel to contain the content $dockPanelContainer = New-Object Windows.Controls.DockPanel $border.Child = $dockPanelContainer # Create a StackPanel for application content controls $stackPanelContainer = New-Object Windows.Controls.StackPanel $stackPanelContainer.HorizontalAlignment = 'Stretch' $stackPanelContainer.VerticalAlignment = 'Stretch' # Check if the target grid (or any ancestor) is already inside a ScrollViewer $hasOuterScrollViewer = $false $currentElement = $targetGrid while ($null -ne $currentElement) { if ($currentElement -is [System.Windows.Controls.ScrollViewer] -or $currentElement.GetType().Name -eq "ScrollViewer") { $hasOuterScrollViewer = $true break } $currentElement = $currentElement.Parent } if ($hasOuterScrollViewer) { # Add StackPanel directly to DockPanel without nesting a ScrollViewer [Windows.Controls.DockPanel]::SetDock($stackPanelContainer, [Windows.Controls.Dock]::Bottom) $dockPanelContainer.Children.Add($stackPanelContainer) | Out-Null } else { # Create a ScrollViewer for targets that do not already have an outer ScrollViewer $scrollViewer = New-Object Windows.Controls.ScrollViewer $scrollViewer.VerticalScrollBarVisibility = "Auto" $scrollViewer.HorizontalScrollBarVisibility = "Disabled" $scrollViewer.HorizontalAlignment = 'Stretch' $scrollViewer.VerticalAlignment = 'Stretch' $scrollViewer.Content = $stackPanelContainer [Windows.Controls.DockPanel]::SetDock($scrollViewer, [Windows.Controls.Dock]::Bottom) $dockPanelContainer.Children.Add($scrollViewer) | Out-Null } $panelcount++ # Now proceed with adding category labels and entries to $stackPanelContainer foreach ($category in ($organizedData[$panelKey].Keys | Sort-Object)) { $count++ $label = New-Object Windows.Controls.Label $categoryCleanName = $category -replace ".*__", "" $label.Content = $categoryCleanName $label.Focusable = $true $label.IsTabStop = $true [System.Windows.Automation.AutomationProperties]::SetName($label, $categoryCleanName) $label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "HeaderFontSize") $label.SetResourceReference([Windows.Controls.Control]::FontFamilyProperty, "HeaderFontFamily") $label.UseLayoutRounding = $true $stackPanelContainer.Children.Add($label) | Out-Null $sync[$category] = $label # Sort entries by type (checkboxes first, then buttons, then comboboxes, notes last) and then alphabetically by Content $entries = $organizedData[$panelKey][$category] | Sort-Object @{Expression = { switch ($_.Type) { 'Button' { 1 } 'Combobox' { 2 } 'Note' { 3 } default { 0 } } }}, Content foreach ($entryInfo in $entries) { $count++ # Create the UI elements based on the entry type switch ($entryInfo.Type) { "Toggle" { $dockPanel = New-Object Windows.Controls.DockPanel [System.Windows.Automation.AutomationProperties]::SetName($dockPanel, $entryInfo.Content) $checkBox = New-Object Windows.Controls.CheckBox $checkBox.Name = $entryInfo.Name $checkBox.HorizontalAlignment = "Right" $checkBox.UseLayoutRounding = $true [System.Windows.Automation.AutomationProperties]::SetName($checkBox, $entryInfo.Content) $dockPanel.Children.Add($checkBox) | Out-Null $checkBox.Style = $ColorfulToggleSwitchStyle $label = New-Object Windows.Controls.Label $label.Content = $entryInfo.Content $label.ToolTip = $entryInfo.Description $label.HorizontalAlignment = "Left" $label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") $label.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") $label.UseLayoutRounding = $true $dockPanel.Children.Add($label) | Out-Null $stackPanelContainer.Children.Add($dockPanel) | Out-Null $sync[$entryInfo.Name] = $checkBox $sync[$entryInfo.Name].IsChecked = (Get-WinUtilToggleStatus $entryInfo.Name) $sync[$entryInfo.Name].Add_Checked({ [System.Object]$Sender = $args[0] Invoke-WPFSelectedCheckboxesUpdate -type "Add" -checkboxName $Sender.name # Skip applying tweaks while an import is restoring toggle states if (-not $sync.ImportInProgress) { Invoke-WinUtilTweaks $Sender.name } }) $sync[$entryInfo.Name].Add_Unchecked({ [System.Object]$Sender = $args[0] Invoke-WPFSelectedCheckboxesUpdate -type "Remove" -checkboxName $Sender.name # Skip undoing tweaks while an import is restoring toggle states if (-not $sync.ImportInProgress) { Invoke-WinUtiltweaks $Sender.name -undo $true } }) } "ToggleButton" { $toggleButton = New-Object Windows.Controls.Primitives.ToggleButton $toggleButton.Name = $entryInfo.Name $toggleButton.Content = $entryInfo.Content[1] $toggleButton.ToolTip = $entryInfo.Description $toggleButton.HorizontalAlignment = "Left" $toggleButton.Style = $ToggleButtonStyle [System.Windows.Automation.AutomationProperties]::SetName($toggleButton, $entryInfo.Content[0]) $toggleButton.Tag = @{ contentOn = if ($entryInfo.Content.Count -ge 1) { $entryInfo.Content[0] } else { "" } contentOff = if ($entryInfo.Content.Count -ge 2) { $entryInfo.Content[1] } else { $contentOn } } $stackPanelContainer.Children.Add($toggleButton) | Out-Null $sync[$entryInfo.Name] = $toggleButton $sync[$entryInfo.Name].Add_Checked({ $this.Content = $this.Tag.contentOn }) $sync[$entryInfo.Name].Add_Unchecked({ $this.Content = $this.Tag.contentOff }) if ($null -eq $sync.Buttons) { $sync.Buttons = [System.Collections.Generic.List[PSObject]]::new() } if ($sync.Buttons -notcontains $toggleButton.Name) { $toggleButton.Add_Click({ [System.Object]$Sender = $args[0] Invoke-WPFButton $Sender.name }) $sync.Buttons.Add($toggleButton.Name) | Out-Null } } "Combobox" { $horizontalStackPanel = New-Object Windows.Controls.StackPanel $horizontalStackPanel.Orientation = "Horizontal" $horizontalStackPanel.Margin = "0,5,0,0" [System.Windows.Automation.AutomationProperties]::SetName($horizontalStackPanel, $entryInfo.Content) $label = New-Object Windows.Controls.Label $label.Content = $entryInfo.Content $label.HorizontalAlignment = "Left" $label.ToolTip = $entryInfo.Description $label.VerticalAlignment = "Center" $label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") $label.UseLayoutRounding = $true $horizontalStackPanel.Children.Add($label) | Out-Null $comboBox = New-Object Windows.Controls.ComboBox $comboBox.Name = $entryInfo.Name $comboBox.SetResourceReference([Windows.Controls.Control]::HeightProperty, "ButtonHeight") $comboBox.SetResourceReference([Windows.Controls.Control]::WidthProperty, "ButtonWidth") $comboBox.HorizontalAlignment = "Left" $comboBox.VerticalAlignment = "Center" $comboBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "ButtonMargin") $comboBox.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") $comboBox.UseLayoutRounding = $true $comboBox.Tag = [pscustomobject]@{ Registry = $entryInfo.Registry State = $null } [System.Windows.Automation.AutomationProperties]::SetName($comboBox, $entryInfo.Content) $comboItems = if ($entryInfo.ComboItems -is [string]) { if ($entryInfo.ComboItems.Contains("|")) { $entryInfo.ComboItems -split "\|" } else { $entryInfo.ComboItems -split " " } } else { @($entryInfo.ComboItems) } foreach ($comboitem in $comboItems) { $comboBoxItem = New-Object Windows.Controls.ComboBoxItem $comboBoxItem.Content = $comboitem if ($entryInfo.ComboDescriptions) { $comboDescription = $entryInfo.ComboDescriptions.PSObject.Properties[$comboitem].Value if ($comboDescription) { $comboBoxItem.ToolTip = $comboDescription } } $comboBoxItem.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") $comboBoxItem.UseLayoutRounding = $true $comboBox.Items.Add($comboBoxItem) | Out-Null } $horizontalStackPanel.Children.Add($comboBox) | Out-Null $stackPanelContainer.Children.Add($horizontalStackPanel) | Out-Null if ($entryInfo.Registry -and @($entryInfo.Registry)[0].Values) { try { $comboBox.Tag.State = Get-WinUtilRegistryComboState -Registry $entryInfo.Registry $comboBox.SelectedIndex = @($comboBox.Items.Content).IndexOf([string]$comboBox.Tag.State) } catch { $unknownStateItem = New-Object Windows.Controls.ComboBoxItem $unknownStateItem.Content = "Custom / Unknown - select a state" $unknownStateItem.IsEnabled = $false $unknownStateItem.ToolTip = "$($_.Exception.Message) Select one of the supported states to replace these values." $comboBox.Items.Add($unknownStateItem) | Out-Null $comboBox.SelectedItem = $unknownStateItem $comboBox.ToolTip = $unknownStateItem.ToolTip } } else { $comboBox.SelectedIndex = 0 } # Set initial text if ($comboBox.Items.Count -gt 0) { $comboBox.Text = $comboBox.SelectedItem.Content } $sync[$entryInfo.Name] = $comboBox # Add SelectionChanged event handler to update the text property $comboBox.Add_SelectionChanged({ $selectedItem = $this.SelectedItem if ($selectedItem) { $this.Text = $selectedItem.Content $registry = $this.Tag.Registry if ($registry -and $selectedItem.IsEnabled -and $selectedItem.Content -ne $this.Tag.State) { try { Set-WinUtilRegistryComboState -Registry $registry -State $selectedItem.Content $this.Tag.State = $selectedItem.Content $this.ToolTip = $null $unknownStateItem = @($this.Items) | Where-Object Content -EQ "Custom / Unknown - select a state" | Select-Object -First 1 if ($unknownStateItem) { $this.Items.Remove($unknownStateItem) } } catch { $applyError = $_.Exception.Message if ([string]::IsNullOrWhiteSpace($applyError)) { $applyError = "Unable to apply registry state '$($selectedItem.Content)'." } $previousState = if ($this.Tag.State) { $this.Tag.State } else { "Custom / Unknown - select a state" } $this.SelectedItem = @($this.Items) | Where-Object Content -EQ $previousState | Select-Object -First 1 [System.Windows.MessageBox]::Show( $applyError, "WinUtil", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning ) | Out-Null } } } }) if ($entryInfo.Registry -and @($entryInfo.Registry)[0].Values -and $entryInfo.Link) { $textBlock = New-Object Windows.Controls.TextBlock $textBlock.Name = $comboBox.Name + "Link" $textBlock.Text = "(?)" $textBlock.ToolTip = $entryInfo.Link $textBlock.Style = $HoverTextBlockStyle $textBlock.UseLayoutRounding = $true $textBlock.VerticalAlignment = "Center" $textBlock.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") $textBlock.Tag = $comboBox $textBlock.Add_MouseUp({ [System.Object]$Sender = $args[0] Start-Process $Sender.ToolTip -ErrorAction Stop }) $horizontalStackPanel.Children.Add($textBlock) | Out-Null $sync[$textBlock.Name] = $textBlock } } "Button" { $button = New-Object Windows.Controls.Button $button.Name = $entryInfo.Name $button.Content = $entryInfo.Content $button.HorizontalAlignment = "Left" $button.SetResourceReference([Windows.Controls.Control]::MarginProperty, "ButtonMargin") $button.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") if ($entryInfo.ButtonWidth) { $baseWidth = [int]$entryInfo.ButtonWidth $button.Width = [math]::Max($baseWidth, 350) } [System.Windows.Automation.AutomationProperties]::SetName($button, $entryInfo.Content) $stackPanelContainer.Children.Add($button) | Out-Null $sync[$entryInfo.Name] = $button if ($null -eq $sync.Buttons) { $sync.Buttons = [System.Collections.Generic.List[PSObject]]::new() } if ($sync.Buttons -notcontains $button.Name) { $button.Add_Click({ [System.Object]$Sender = $args[0] Invoke-WPFButton $Sender.name }) $sync.Buttons.Add($button.Name) | Out-Null } } "RadioButton" { # Check if a container for this GroupName already exists if (-not $radioButtonGroups.ContainsKey($entryInfo.GroupName)) { # Create a StackPanel for this group $groupStackPanel = New-Object Windows.Controls.StackPanel $groupStackPanel.Orientation = "Vertical" [System.Windows.Automation.AutomationProperties]::SetName($groupStackPanel, $entryInfo.GroupName) $radioButtonGroups[$entryInfo.GroupName] = $groupStackPanel # Add the group container to the ItemsControl $stackPanelContainer.Children.Add($groupStackPanel) | Out-Null } else { # Retrieve the existing group container $groupStackPanel = $radioButtonGroups[$entryInfo.GroupName] } # Create the RadioButton $radioButton = New-Object Windows.Controls.RadioButton $radioButton.Name = $entryInfo.Name $radioButton.GroupName = $entryInfo.GroupName $radioButton.Content = $entryInfo.Content $radioButton.HorizontalAlignment = "Left" $radioButton.SetResourceReference([Windows.Controls.Control]::MarginProperty, "CheckBoxMargin") $radioButton.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") $radioButton.ToolTip = $entryInfo.Description $radioButton.UseLayoutRounding = $true [System.Windows.Automation.AutomationProperties]::SetName($radioButton, $entryInfo.Content) if ($entryInfo.Checked -eq $true) { $radioButton.IsChecked = $true } # Add the RadioButton to the group container $groupStackPanel.Children.Add($radioButton) | Out-Null $sync[$entryInfo.Name] = $radioButton } "Note" { $textBlock = New-Object Windows.Controls.TextBlock $textBlock.TextWrapping = "Wrap" $textBlock.Margin = "5,5,5,5" $textBlock.UseLayoutRounding = $true $bulletBadge = [Windows.Documents.InlineUIContainer]::new((New-WinUtilFossBadge -Size 18 -Round)) $bulletBadge.BaselineAlignment = [Windows.BaselineAlignment]::Center $textRun = New-Object Windows.Documents.Run $textRun.Text = " $($entryInfo.Content)" $textRun.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") $textRun.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(19, 143, 83)) $textBlock.Inlines.Add($bulletBadge) $textBlock.Inlines.Add($textRun) $stackPanelContainer.Children.Add($textBlock) | Out-Null } default { $horizontalStackPanel = New-Object Windows.Controls.StackPanel $horizontalStackPanel.Orientation = "Horizontal" [System.Windows.Automation.AutomationProperties]::SetName($horizontalStackPanel, $entryInfo.Content) $checkBox = New-Object Windows.Controls.CheckBox $checkBox.Name = $entryInfo.Name $checkBox.Content = $entryInfo.Content $checkBox.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") $checkBox.ToolTip = $entryInfo.Description $checkBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "CheckBoxMargin") $checkBox.UseLayoutRounding = $true [System.Windows.Automation.AutomationProperties]::SetName($checkBox, $entryInfo.Content) if ($entryInfo.Checked -eq $true) { $checkBox.IsChecked = $entryInfo.Checked } $horizontalStackPanel.Children.Add($checkBox) | Out-Null if ($entryInfo.Link) { $textBlock = New-Object Windows.Controls.TextBlock $textBlock.Name = $checkBox.Name + "Link" $textBlock.Text = "(?)" $textBlock.ToolTip = $entryInfo.Link $textBlock.Style = $HoverTextBlockStyle $textBlock.UseLayoutRounding = $true $textBlock.VerticalAlignment = "Center" $textBlock.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") $textBlock.Tag = $checkBox $textBlock.Add_MouseUp({ [System.Object]$Sender = $args[0] Start-Process $Sender.ToolTip -ErrorAction Stop }) $updateLinkMargin = { [System.Object]$Sender = $args[0] $linkedCheckBox = $Sender.Tag $MarginTopBase = if ($linkedCheckBox) { $linkedCheckBox.Margin.Top } else { 0 } $Sender.Margin = New-Object Windows.Thickness( [math]::Round($Sender.FontSize * 0.5), ($MarginTopBase - [math]::Round($Sender.FontSize / 2)), 0, 0 ) } $textBlock.Add_Loaded($updateLinkMargin) $fontSizeDescriptor = [System.ComponentModel.DependencyPropertyDescriptor]::FromProperty( [Windows.Controls.Control]::FontSizeProperty, [Windows.Controls.TextBlock] ) $fontSizeDescriptor.AddValueChanged($textBlock, $updateLinkMargin) $horizontalStackPanel.Children.Add($textBlock) | Out-Null $sync[$textBlock.Name] = $textBlock } $stackPanelContainer.Children.Add($horizontalStackPanel) | Out-Null $sync[$entryInfo.Name] = $checkBox $sync[$entryInfo.Name].Add_Checked({ [System.Object]$Sender = $args[0] Invoke-WPFSelectedCheckboxesUpdate -type "Add" -checkboxName $Sender.name }) $sync[$entryInfo.Name].Add_Unchecked({ [System.Object]$Sender = $args[0] Invoke-WPFSelectedCheckboxesUpdate -type "Remove" -checkboxName $Sender.name }) } } } } } }