Files
Win11Debloat/Scripts/GUI/MainWindow-AppSelection.ps1
T

635 lines
26 KiB
PowerShell

# MainWindow-AppSelection.ps1
# App-selection panel functions: tri-state helpers, sorting, search/highlight, app loading, preset management, and removal scope.
function Add-TriStateClickBehavior {
param([System.Windows.Controls.CheckBox]$CheckBox)
if (-not $CheckBox -or -not $CheckBox.IsThreeState) { return }
if (-not $CheckBox.PSObject.Properties['WasIndeterminateBeforeClick']) {
Add-Member -InputObject $CheckBox -MemberType NoteProperty -Name 'WasIndeterminateBeforeClick' -Value $false
}
$CheckBox.Add_PreviewMouseLeftButtonDown({
$this.WasIndeterminateBeforeClick = ($this.IsChecked -eq [System.Nullable[bool]]$null)
})
}
function ConvertTo-NormalizedCheckboxState {
param([System.Windows.Controls.CheckBox]$CheckBox)
if ($CheckBox.PSObject.Properties['WasIndeterminateBeforeClick'] -and $CheckBox.WasIndeterminateBeforeClick) {
# WPF toggles null -> false before Click handlers fire; restore desired mixed -> checked behavior.
$CheckBox.WasIndeterminateBeforeClick = $false
$CheckBox.IsChecked = $true
return $true
}
return ($CheckBox.IsChecked -eq $true)
}
function Set-TriStatePresetCheckBoxState {
param(
[System.Windows.Controls.CheckBox]$CheckBox,
[int]$Total,
[int]$Selected
)
if (-not $CheckBox) { return }
if ($Total -eq 0) {
$CheckBox.IsEnabled = $false
$CheckBox.IsChecked = $false
return
}
$CheckBox.IsEnabled = $true
if ($Selected -eq 0) {
$CheckBox.IsChecked = $false
}
elseif ($Selected -eq $Total) {
$CheckBox.IsChecked = $true
}
else {
$CheckBox.IsChecked = [System.Nullable[bool]]$null
}
}
function Update-SortArrows {
param(
[System.Windows.Controls.TextBlock]$SortArrowName,
[System.Windows.Controls.TextBlock]$SortArrowDescription,
[System.Windows.Controls.TextBlock]$SortArrowAppId
)
$ease = New-Object System.Windows.Media.Animation.CubicEase
$ease.EasingMode = 'EaseOut'
$arrows = @{
'Name' = $SortArrowName
'Description' = $SortArrowDescription
'AppId' = $SortArrowAppId
}
foreach ($col in $arrows.Keys) {
$tb = $arrows[$col]
# Active column: full opacity, rotate to indicate direction (0 = up/asc, 180 = down/desc)
# Inactive columns: dim, reset to 0
if ($col -eq $script:SortColumn) {
$targetAngle = if ($script:SortAscending) { 0 } else { 180 }
$tb.Opacity = 1.0
}
else {
$targetAngle = 0
$tb.Opacity = 0.3
}
$anim = New-Object System.Windows.Media.Animation.DoubleAnimation
$anim.To = $targetAngle
$anim.Duration = [System.Windows.Duration]::new([System.TimeSpan]::FromMilliseconds(200))
$anim.EasingFunction = $ease
$tb.RenderTransform.BeginAnimation([System.Windows.Media.RotateTransform]::AngleProperty, $anim)
}
}
function Update-AppsPanelRebuildSearchIndex {
param(
[System.Windows.Controls.Panel]$AppsPanel,
$ActiveMatch = $null
)
$newMatches = @()
$newActiveIndex = -1
$i = 0
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox] -and $child.Background -ne [System.Windows.Media.Brushes]::Transparent) {
$newMatches += $child
if ($null -ne $ActiveMatch -and [System.Object]::ReferenceEquals($child, $ActiveMatch)) {
$newActiveIndex = $i
}
$i++
}
}
$script:AppSearchMatches = $newMatches
$script:AppSearchMatchIndex = if ($newActiveIndex -ge 0) { $newActiveIndex } elseif ($newMatches.Count -gt 0) { 0 } else { -1 }
}
function Update-AppsPanelSort {
param(
[System.Windows.Controls.Panel]$AppsPanel,
[System.Windows.Controls.TextBlock]$SortArrowName,
[System.Windows.Controls.TextBlock]$SortArrowDescription,
[System.Windows.Controls.TextBlock]$SortArrowAppId
)
$children = @($AppsPanel.Children)
$key = switch ($script:SortColumn) {
'Name' { { $_.AppName } }
'Description' { { $_.AppDescription } }
'AppId' { { $_.AppIdDisplay } }
}
$sorted = $children | Sort-Object $key -Descending:(-not $script:SortAscending)
$AppsPanel.Children.Clear()
foreach ($checkbox in $sorted) {
$AppsPanel.Children.Add($checkbox) | Out-Null
}
Update-SortArrows -SortArrowName $SortArrowName -SortArrowDescription $SortArrowDescription -SortArrowAppId $SortArrowAppId
# Rebuild search match list in new sorted order so keyboard navigation stays correct
if ($script:AppSearchMatches.Count -gt 0) {
$activeMatch = if ($script:AppSearchMatchIndex -ge 0 -and $script:AppSearchMatchIndex -lt $script:AppSearchMatches.Count) {
$script:AppSearchMatches[$script:AppSearchMatchIndex]
}
else { $null }
Update-AppsPanelRebuildSearchIndex -AppsPanel $AppsPanel -ActiveMatch $activeMatch
}
}
<#
.SYNOPSIS
Updates the "N apps selected" status text and shows or hides the app-removal scope section.
.DESCRIPTION
The scope section only makes sense once at least one app is selected, so it's hidden
entirely at zero and its description text is refreshed via Update-AppRemovalScopeDescription
whenever the selection count changes.
#>
function Update-AppSelectionStatus {
param(
[System.Windows.Controls.Panel]$AppsPanel,
[System.Windows.Controls.TextBlock]$AppSelectionStatus,
[System.Windows.Controls.ComboBox]$AppRemovalScopeCombo,
[System.Windows.Controls.Border]$AppRemovalScopeSection,
[System.Windows.Controls.TextBlock]$AppRemovalScopeDescription,
[System.Windows.Controls.ComboBox]$UserSelectionCombo
)
$selectedCount = 0
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
$selectedCount++
}
}
$AppSelectionStatus.Text = Get-Translation -Key 'AppSelectionStatusSelected' -Count $selectedCount -FormatArgs @($selectedCount)
if ($AppRemovalScopeCombo -and $AppRemovalScopeSection -and $AppRemovalScopeDescription) {
if ($selectedCount -gt 0) {
$AppRemovalScopeSection.Visibility = 'Visible'
if ($UserSelectionCombo.SelectedIndex -ne 2) {
$AppRemovalScopeCombo.IsEnabled = $true
}
Update-AppRemovalScopeDescription -AppRemovalScopeCombo $AppRemovalScopeCombo -AppRemovalScopeDescription $AppRemovalScopeDescription
}
else {
$AppRemovalScopeSection.Visibility = 'Collapsed'
}
}
}
<#
.SYNOPSIS
Updates the app-removal scope description to match the selected ComboBoxItem.
#>
function Update-AppRemovalScopeDescription {
param(
[System.Windows.Controls.ComboBox]$AppRemovalScopeCombo,
[System.Windows.Controls.TextBlock]$AppRemovalScopeDescription
)
$selectedItem = $AppRemovalScopeCombo.SelectedItem
if ($selectedItem) {
# Content is the display text and will change once translated; Name is stable.
switch ($selectedItem.Name) {
"AppRemovalScopeAllUsers" {
$AppRemovalScopeDescription.Text = Get-Translation -Key 'AppRemovalScopeDescriptionAllUsers'
}
"AppRemovalScopeCurrentUser" {
$AppRemovalScopeDescription.Text = Get-Translation -Key 'AppRemovalScopeDescriptionCurrentUser'
}
"AppRemovalScopeTargetUser" {
$AppRemovalScopeDescription.Text = Get-Translation -Key 'AppRemovalScopeDescriptionTargetUser'
}
}
}
}
<#
.SYNOPSIS
Tests whether the app-removal scope combo is currently set to "Target user only".
#>
function Test-AppRemovalScopeTargetsOtherUser {
param(
[System.Windows.Controls.ComboBox]$AppRemovalScopeCombo
)
return ($AppRemovalScopeCombo -and $AppRemovalScopeCombo.SelectedItem -and $AppRemovalScopeCombo.SelectedItem.Name -eq 'AppRemovalScopeTargetUser')
}
<#
.SYNOPSIS
Resolves the -AppRemovalTarget value for the selected app-removal scope.
#>
function Get-AppRemovalScopeTarget {
param(
[System.Windows.Controls.ComboBox]$AppRemovalScopeCombo,
[System.Windows.Controls.TextBox]$OtherUsernameTextBox
)
$selectedItem = $AppRemovalScopeCombo.SelectedItem
if (-not $selectedItem) { return $null }
if (Test-AppRemovalScopeTargetsOtherUser -AppRemovalScopeCombo $AppRemovalScopeCombo) {
return $OtherUsernameTextBox.Text.Trim()
}
switch ($selectedItem.Name) {
"AppRemovalScopeAllUsers" { return 'AllUsers' }
"AppRemovalScopeCurrentUser" { return 'CurrentUser' }
default {
Write-Warning "Unrecognized app-removal scope item '$($selectedItem.Name)'. Skipping app removal."
return $null
}
}
}
function Invoke-AppPreset {
param(
[System.Windows.Controls.Panel]$AppsPanel,
[scriptblock]$MatchFilter,
[bool]$Check,
[switch]$Exclusive
)
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox]) {
if ($Exclusive) {
$child.IsChecked = (& $MatchFilter $child)
}
elseif (& $MatchFilter $child) {
$child.IsChecked = $Check
}
}
}
Update-AppPresetStates -AppsPanel $AppsPanel
}
function Update-AppPresetStates {
param([System.Windows.Controls.Panel]$AppsPanel)
$script:UpdatingPresets = $true
try {
# Helper: count matching and checked apps, set checkbox state
function Set-PresetState($CheckBox, [scriptblock]$MatchFilter) {
$total = 0; $checked = 0
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox]) {
if (& $MatchFilter $child) {
$total++
if ($child.IsChecked) { $checked++ }
}
}
}
Set-TriStatePresetCheckBoxState -CheckBox $CheckBox -Total $total -Selected $checked
}
# Find preset checkboxes via window
$window = $script:MainWindow
$presetDefaultApps = $window.FindName('PresetDefaultApps')
$presetLastUsed = $window.FindName('PresetLastUsed')
Set-PresetState $presetDefaultApps { param($c) $c.SelectedByDefault -eq $true }
foreach ($jsonCb in $script:JsonPresetCheckboxes) {
$localIds = $jsonCb.PresetAppIds
Set-PresetState $jsonCb { param($c) (@($c.AppIds) | Where-Object { $localIds -contains $_ }).Count -gt 0 }.GetNewClosure()
}
# Last used preset: only update if it's visible (has saved apps)
if ($presetLastUsed.Visibility -ne 'Collapsed' -and $script:SavedAppIds) {
Set-PresetState $presetLastUsed { param($c) (@($c.AppIds) | Where-Object { $script:SavedAppIds -contains $_ }).Count -gt 0 }
}
}
finally {
$script:UpdatingPresets = $false
}
}
function Scroll-ToItemIfNotVisible {
param (
[System.Windows.Controls.ScrollViewer]$ScrollViewer,
[System.Windows.UIElement]$Item,
[System.Windows.UIElement]$Container
)
if (-not $ScrollViewer -or -not $Item -or -not $Container) { return }
try {
$itemPosition = $Item.TransformToAncestor($Container).Transform([System.Windows.Point]::new(0, 0)).Y
$viewportHeight = $ScrollViewer.ViewportHeight
$itemHeight = $Item.ActualHeight
$currentOffset = $ScrollViewer.VerticalOffset
# Check if the item is currently visible in the viewport
$itemTop = $itemPosition - $currentOffset
$itemBottom = $itemTop + $itemHeight
$isVisible = ($itemTop -ge 0) -and ($itemBottom -le $viewportHeight)
# Only scroll if the item is not visible
if (-not $isVisible) {
# Center the item in the viewport
$targetOffset = $itemPosition - ($viewportHeight / 2) + ($itemHeight / 2)
$ScrollViewer.ScrollToVerticalOffset([Math]::Max(0, $targetOffset))
}
}
catch {
# Fallback to simple bring into view
$Item.BringIntoView()
}
}
function Find-ParentScrollViewer {
param ([System.Windows.UIElement]$Element)
$parent = [System.Windows.Media.VisualTreeHelper]::GetParent($Element)
while ($null -ne $parent) {
if ($parent -is [System.Windows.Controls.ScrollViewer]) {
return $parent
}
$parent = [System.Windows.Media.VisualTreeHelper]::GetParent($parent)
}
return $null
}
<#
.SYNOPSIS
Loads application details and adds their interactive checkboxes to the main window.
.PARAMETER Window
The main application window and resource owner.
.PARAMETER AppsPanel
The panel populated with application checkboxes.
.PARAMETER OnlyInstalledAppsBox
The filter control that determines whether only installed apps are loaded.
.PARAMETER LoadingAppsIndicator
The loading indicator shown while application details are prepared.
.PARAMETER ImportConfigBtn
The optional import control re-enabled after loading completes.
.PARAMETER ListOfApps
An optional pre-fetched list of installed WinGet applications.
#>
function Add-AppsToMainWindow {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.Panel]$AppsPanel,
[System.Windows.Controls.CheckBox]$OnlyInstalledAppsBox,
[System.Windows.Controls.Border]$LoadingAppsIndicator,
[System.Windows.Controls.MenuItem]$ImportConfigBtn,
[object[]]$ListOfApps
)
$script:MainWindowLastSelectedCheckbox = $null
$onlyInstalled = [bool]$OnlyInstalledAppsBox.IsChecked
# Use preloaded data if available; otherwise load in a background job to keep the UI responsive.
if (-not $onlyInstalled -and $script:PreloadedAppData) {
$rawAppData = $script:PreloadedAppData
$script:PreloadedAppData = $null
}
else {
$rawAppData = Invoke-AppDetailsFromJsonAsync -OnlyInstalled:$onlyInstalled -InstalledList $ListOfApps
}
$appsToAdd = @($rawAppData | Where-Object { $_ -and ($_.AppId -or $_.FriendlyName) } | Sort-Object -Property FriendlyName)
$LoadingAppsIndicator.Visibility = 'Collapsed'
if ($appsToAdd.Count -eq 0) {
$OnlyInstalledAppsBox.IsHitTestVisible = $true
$Window.FindName('DeploymentApplyBtn').IsEnabled = $true
if ($ImportConfigBtn) {
$ImportConfigBtn.IsEnabled = $true
}
return
}
$brushSafe = $Window.Resources['AppRecommendationSafeColor']
$brushDefault = $Window.Resources['AppRecommendationOptionalColor']
$brushUnsafe = $Window.Resources['AppRecommendationUnsafeColor']
# Create WPF controls; pump the Dispatcher every batch so the spinner keeps animating.
$batchSize = 20
for ($i = 0; $i -lt $appsToAdd.Count; $i++) {
$app = $appsToAdd[$i]
$checkbox = New-Object System.Windows.Controls.CheckBox
$automationName = if ($app.FriendlyName) { $app.FriendlyName } elseif ($app.AppIdDisplay) { $app.AppIdDisplay } else { $null }
if ($automationName) { $checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $automationName) }
$checkbox.Tag = $app.AppIdDisplay
$checkbox.IsChecked = $app.IsChecked
$checkbox.Style = $Window.Resources['AppsPanelCheckBoxStyle']
# Build table row: Recommendation dot | Name | Description | App ID
$row = New-Object System.Windows.Controls.Grid
$row.Style = $Window.Resources['AppTableRowStyle']
$c0 = New-Object System.Windows.Controls.ColumnDefinition; $c0.Width = $Window.Resources['AppTableDotColWidth']
$c1 = New-Object System.Windows.Controls.ColumnDefinition; $c1.Width = $Window.Resources['AppTableNameColWidth']
$c2 = New-Object System.Windows.Controls.ColumnDefinition; $c2.Width = $Window.Resources['AppTableDescColWidth']
$c3 = New-Object System.Windows.Controls.ColumnDefinition; $c3.Width = $Window.Resources['AppTableIdColWidth']
$row.ColumnDefinitions.Add($c0); $row.ColumnDefinitions.Add($c1)
$row.ColumnDefinitions.Add($c2); $row.ColumnDefinitions.Add($c3)
$dot = New-Object System.Windows.Shapes.Ellipse
$dot.Style = $Window.Resources['AppRecommendationDotStyle']
$dot.Fill = switch ($app.Recommendation) { 'safe' { $brushSafe } 'unsafe' { $brushUnsafe } default { $brushDefault } }
$recommendationKey = switch ($app.Recommendation) {
'safe' { 'AppLegendRecommendedTooltip' }
'unsafe' { 'AppLegendNotRecommendedTooltip' }
default { 'AppLegendOptionalTooltip' }
}
$dot.SetResourceReference([System.Windows.FrameworkElement]::ToolTipProperty, "Language_$recommendationKey")
[System.Windows.Controls.Grid]::SetColumn($dot, 0)
$tbName = New-Object System.Windows.Controls.TextBlock
$tbName.Text = $app.FriendlyName
$tbName.Style = $Window.Resources['AppNameTextStyle']
[System.Windows.Controls.Grid]::SetColumn($tbName, 1)
$tbDesc = New-Object System.Windows.Controls.TextBlock
$tbDesc.Text = $app.Description
$tbDesc.Style = $Window.Resources['AppDescTextStyle']
$tbDesc.ToolTip = $app.Description
[System.Windows.Controls.Grid]::SetColumn($tbDesc, 2)
$tbId = New-Object System.Windows.Controls.TextBlock
$tbId.Text = $app.AppIdDisplay
$tbId.Style = $Window.Resources["AppIdTextStyle"]
$tbId.ToolTip = $app.AppIdDisplay
[System.Windows.Controls.Grid]::SetColumn($tbId, 3)
$row.Children.Add($dot) | Out-Null
$row.Children.Add($tbName) | Out-Null
$row.Children.Add($tbDesc) | Out-Null
$row.Children.Add($tbId) | Out-Null
$checkbox.Content = $row
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppName' -Value $app.FriendlyName
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppDescription' -Value $app.Description
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'SelectedByDefault' -Value $app.SelectedByDefault
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIds' -Value @($app.AppId)
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIdDisplay' -Value $app.AppIdDisplay
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppNameText' -Value $tbName
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppDescriptionText' -Value $tbDesc
$checkbox.Add_Checked({
$w = $script:MainWindow
Update-AppSelectionStatus -AppsPanel $w.FindName('AppSelectionPanel') `
-AppSelectionStatus $w.FindName('AppSelectionStatus') `
-AppRemovalScopeCombo $w.FindName('AppRemovalScopeCombo') `
-AppRemovalScopeSection $w.FindName('AppRemovalScopeSection') `
-AppRemovalScopeDescription $w.FindName('AppRemovalScopeDescription') `
-UserSelectionCombo $w.FindName('UserSelectionCombo')
})
$checkbox.Add_Unchecked({
$w = $script:MainWindow
Update-AppSelectionStatus -AppsPanel $w.FindName('AppSelectionPanel') `
-AppSelectionStatus $w.FindName('AppSelectionStatus') `
-AppRemovalScopeCombo $w.FindName('AppRemovalScopeCombo') `
-AppRemovalScopeSection $w.FindName('AppRemovalScopeSection') `
-AppRemovalScopeDescription $w.FindName('AppRemovalScopeDescription') `
-UserSelectionCombo $w.FindName('UserSelectionCombo')
})
Attach-ShiftClickBehavior -checkbox $checkbox -appsPanel $AppsPanel `
-lastSelectedCheckboxRef ([ref]$script:MainWindowLastSelectedCheckbox) `
-updateStatusCallback {
$w = $script:MainWindow
Update-AppSelectionStatus -AppsPanel $w.FindName('AppSelectionPanel') `
-AppSelectionStatus $w.FindName('AppSelectionStatus') `
-AppRemovalScopeCombo $w.FindName('AppRemovalScopeCombo') `
-AppRemovalScopeSection $w.FindName('AppRemovalScopeSection') `
-AppRemovalScopeDescription $w.FindName('AppRemovalScopeDescription') `
-UserSelectionCombo $w.FindName('UserSelectionCombo')
}
$AppsPanel.Children.Add($checkbox) | Out-Null
if (($i + 1) % $batchSize -eq 0) { Invoke-DoEvents }
}
$sortArrowName = $Window.FindName('SortArrowName')
$sortArrowDescription = $Window.FindName('SortArrowDescription')
$sortArrowAppId = $Window.FindName('SortArrowAppId')
Update-AppsPanelSort -AppsPanel $AppsPanel -SortArrowName $sortArrowName -SortArrowDescription $sortArrowDescription -SortArrowAppId $sortArrowAppId
# If Default Mode was clicked while apps were still loading, apply defaults now
if ($script:PendingDefaultMode) {
$script:PendingDefaultMode = $false
Invoke-AppPreset -AppsPanel $AppsPanel -MatchFilter { param($c) $c.SelectedByDefault -eq $true } -Exclusive
}
$appSelectionStatusText = $Window.FindName('AppSelectionStatus')
$appRemovalScopeCombo = $Window.FindName('AppRemovalScopeCombo')
$appRemovalScopeSection = $Window.FindName('AppRemovalScopeSection')
$appRemovalScopeDescription = $Window.FindName('AppRemovalScopeDescription')
$userSelectionCombo = $Window.FindName('UserSelectionCombo')
Update-AppSelectionStatus -AppsPanel $AppsPanel -AppSelectionStatus $appSelectionStatusText `
-AppRemovalScopeCombo $appRemovalScopeCombo -AppRemovalScopeSection $appRemovalScopeSection `
-AppRemovalScopeDescription $appRemovalScopeDescription -UserSelectionCombo $userSelectionCombo
# Re-enable controls now that the full, correctly-checked app list is ready
$OnlyInstalledAppsBox.IsHitTestVisible = $true
$Window.FindName('DeploymentApplyBtn').IsEnabled = $true
if ($ImportConfigBtn) {
$ImportConfigBtn.IsEnabled = $true
}
}
<#
.SYNOPSIS
Starts asynchronous loading of application checkboxes for the main window.
.PARAMETER Window
The main application window.
.PARAMETER AppsPanel
The panel that receives application checkboxes.
.PARAMETER OnlyInstalledAppsBox
The installed-applications filter control.
.PARAMETER LoadingAppsIndicator
The loading indicator shown until loading completes.
.PARAMETER ImportConfigBtn
The optional import control disabled while loading is in progress.
#>
function Initialize-MainWindowApps {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.Panel]$AppsPanel,
[System.Windows.Controls.CheckBox]$OnlyInstalledAppsBox,
[System.Windows.Controls.Border]$LoadingAppsIndicator,
[System.Windows.Controls.MenuItem]$ImportConfigBtn
)
# Prevent concurrent loads
if ($script:IsLoadingApps) { return }
$script:IsLoadingApps = $true
$settingsButton = $Window.FindName('SettingsBtn')
if ($settingsButton) { $settingsButton.IsEnabled = $false }
if ($ImportConfigBtn) {
$ImportConfigBtn.IsEnabled = $false
}
# Show loading indicator and clear existing apps
$LoadingAppsIndicator.Visibility = 'Visible'
$AppsPanel.Children.Clear()
# Disable controls while apps are loading so they can't be interacted with mid-load
$Window.FindName('DeploymentApplyBtn').IsEnabled = $false
$OnlyInstalledAppsBox.IsHitTestVisible = $false
# Update navigation buttons to disable Next/Previous
Update-NavigationButtons -Window $Window -TabControl $Window.FindName('MainTabControl')
# Force a render so the loading indicator is visible, then schedule the
# actual loading at Background priority so this call returns immediately.
# This is critical when called from Add_Loaded: the window must finish
# its initialization before we start a nested message pump via Invoke-DoEvents.
$Window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action] {})
$Window.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action] {
try {
$listOfApps = $null
if ($OnlyInstalledAppsBox.IsChecked -and ($script:WingetInstalled -eq $true)) {
Write-Host "Retrieving installed apps via winget..."
$listOfApps = Get-WingetInstalledApps -TimeOut 20 -NonBlocking
if ($null -eq $listOfApps) {
Write-Warning "WinGet returned no data (command timed out or failed)"
Show-MessageBox -Message (Get-Translation -Key 'AppSelectionWinGetLoadFailedMessage') -Title (Get-Translation -Key 'ErrorTitle') -Button 'OK' -Icon 'Error' | Out-Null
$OnlyInstalledAppsBox.IsChecked = $false
}
}
Add-AppsToMainWindow -Window $Window -AppsPanel $AppsPanel -OnlyInstalledAppsBox $OnlyInstalledAppsBox `
-LoadingAppsIndicator $LoadingAppsIndicator -ImportConfigBtn $ImportConfigBtn -ListOfApps $listOfApps
}
catch {
Write-Warning "Failed to load apps list: $($_.Exception.Message)"
$LoadingAppsIndicator.Visibility = 'Collapsed'
$OnlyInstalledAppsBox.IsHitTestVisible = $true
$Window.FindName('DeploymentApplyBtn').IsEnabled = $true
if ($ImportConfigBtn) { $ImportConfigBtn.IsEnabled = $true }
}
finally {
$script:IsLoadingApps = $false
$settingsButton = $Window.FindName('SettingsBtn')
if ($settingsButton) { $settingsButton.IsEnabled = $true }
}
}) | Out-Null
}