Render install app entries incrementally

This commit is contained in:
Chris Titus
2026-07-01 22:16:05 -05:00
parent eeb113a353
commit 30fde39f67
6 changed files with 118 additions and 15 deletions
@@ -13,11 +13,13 @@ function Initialize-InstallAppEntry {
$appKey
)
$app = $sync.configs.applicationsHashtable.$appKey
# Create the outer Border for the application type
$border = New-Object Windows.Controls.Border
$border.Style = $sync.Form.Resources.AppEntryBorderStyle
$border.Tag = $appKey
$border.ToolTip = $Apps.$appKey.description
$border.ToolTip = $app.description
$border.Add_MouseLeftButtonUp({
$childCheckbox = ($this.Child | Where-Object {$_.Template.TargetType -eq [System.Windows.Controls.Checkbox]})[0]
$childCheckBox.isChecked = -not $childCheckbox.IsChecked
@@ -61,10 +63,10 @@ function Initialize-InstallAppEntry {
# Create the TextBlock for the application name
$appName = New-Object Windows.Controls.TextBlock
$appName.Style = $sync.Form.Resources.AppEntryNameStyle
$appName.Text = $Apps.$appKey.content
$appName.Text = $app.content
# Add FOSS label after the name if FOSS
if ($Apps.$appKey.foss -eq $true) {
if ($app.foss -eq $true) {
$fossRun = [System.Windows.Documents.Run]::new(" $([char]0x25CF)")
$fossRun.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(110, 255, 114))
$fossRun.FontSize = 11.5
@@ -74,10 +76,13 @@ function Initialize-InstallAppEntry {
$checkBox.Content = $appName
# Add accessibility properties to make the elements screen reader friendly
$checkBox.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $Apps.$appKey.content)
$border.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $Apps.$appKey.content)
$checkBox.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $app.content)
$border.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $app.content)
$border.Child = $checkBox
if ($sync.selectedApps -contains $appKey) {
$checkBox.IsChecked = $true
}
# Add the border to the corresponding Category
$TargetElement.Children.Add($border) | Out-Null
return $checkbox
@@ -16,7 +16,7 @@ function Initialize-InstallCategoryAppList {
$Apps
)
# Pre-group apps by category
# Pre-group apps by category before creating WPF controls.
$appsByCategory = @{}
foreach ($appKey in $Apps.Keys) {
$category = $Apps.$appKey.Category
@@ -25,6 +25,8 @@ function Initialize-InstallCategoryAppList {
}
$appsByCategory[$category] += $appKey
}
$sync.InstallAppRenderQueue = [System.Collections.Queue]::new()
foreach ($category in $($appsByCategory.Keys | Sort-Object)) {
# Create a container for category label + apps
$categoryContainer = New-Object Windows.Controls.StackPanel
@@ -89,9 +91,12 @@ function Initialize-InstallCategoryAppList {
# Add the entire category container to the target element
$null = $TargetElement.Items.Add($categoryContainer)
# Add apps to the wrap panel
$appsByCategory[$category] | Sort-Object | ForEach-Object {
$sync.$_ = $(Initialize-InstallAppEntry -TargetElement $wrapPanel -AppKey $_)
}
$sync.InstallAppRenderQueue.Enqueue([pscustomobject]@{
Category = $category
TargetElement = $wrapPanel
AppKeys = @($appsByCategory[$category] | Sort-Object)
})
}
Start-WinUtilInstallAppRendering
}
@@ -0,0 +1,51 @@
function Start-WinUtilInstallAppRendering {
if ($null -eq $sync.InstallAppRenderQueue) {
return
}
$sync.InstallAppEntriesRendered = $false
$renderCategory = {
param($CategoryBatch)
foreach ($appKey in $CategoryBatch.AppKeys) {
$sync.$appKey = Initialize-InstallAppEntry -TargetElement $CategoryBatch.TargetElement -AppKey $appKey
}
if ($sync.currentTab -eq "Install" -and $sync.SearchBar -and -not [string]::IsNullOrWhiteSpace($sync.SearchBar.Text)) {
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text
}
}
if ($sync.Form -and $sync.Form.Dispatcher -and ("System.Windows.Threading.DispatcherTimer" -as [type])) {
$timer = New-Object System.Windows.Threading.DispatcherTimer
$timer.Interval = [TimeSpan]::FromMilliseconds(1)
$timer.Add_Tick({
$timer.Stop()
if ($sync.InstallAppRenderQueue.Count -gt 0) {
$categoryBatch = $sync.InstallAppRenderQueue.Dequeue()
& $renderCategory $categoryBatch
}
if ($sync.InstallAppRenderQueue.Count -gt 0) {
$timer.Start()
return
}
$sync.InstallAppEntriesRendered = $true
Write-WinUtilPerformanceCheckpoint -Name "Install app entries rendered"
})
$sync.InstallAppRenderTimer = $timer
$timer.Start()
return
}
while ($sync.InstallAppRenderQueue.Count -gt 0) {
$categoryBatch = $sync.InstallAppRenderQueue.Dequeue()
& $renderCategory $categoryBatch
}
$sync.InstallAppEntriesRendered = $true
Write-WinUtilPerformanceCheckpoint -Name "Install app entries rendered"
}
+39
View File
@@ -0,0 +1,39 @@
#===========================================================================
# Tests - Install tab rendering
#===========================================================================
BeforeAll {
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
}
Describe "Install app rendering startup contract" {
It "queues app entries after creating category containers" {
$categoryScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-InstallCategoryAppList.ps1") -Raw
$categoryScript | Should -Match '\$sync\.InstallAppRenderQueue = \[System\.Collections\.Queue\]::new\(\)'
$categoryScript | Should -Match 'Start-WinUtilInstallAppRendering'
$categoryScript | Should -Match 'Pre-group apps by category before creating WPF controls'
}
It "renders queued apps through a dispatcher timer when a form dispatcher exists" {
$renderScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Start-WinUtilInstallAppRendering.ps1") -Raw
$renderScript | Should -Match 'System\.Windows\.Threading\.DispatcherTimer'
$renderScript | Should -Match 'Initialize-InstallAppEntry'
$renderScript | Should -Match 'Install app entries rendered'
}
It "keeps app-entry metadata lookup independent from the old caller scope" {
$entryScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-InstallAppEntry.ps1") -Raw
$entryScript | Should -Match '\$app = \$sync\.configs\.applicationsHashtable\.\$appKey'
$entryScript | Should -Not -Match '\$Apps\.\$appKey'
}
It "restores delayed app checkbox state from selected apps" {
$entryScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-InstallAppEntry.ps1") -Raw
$entryScript | Should -Match '\$sync\.selectedApps -contains \$appKey'
$entryScript | Should -Match '\$checkBox\.IsChecked = \$true'
}
}
+3
View File
@@ -305,6 +305,9 @@ Describe "XAML and sync wiring" {
"InstallAppAreaScrollViewer",
"InstallAppAreaOverlay",
"InstallAppAreaOverlayText",
"InstallAppRenderQueue",
"InstallAppRenderTimer",
"InstallAppEntriesRendered",
"ProgressBar",
"progressBarTextBlock",
"Win11ISOImageInfo",
+5 -5
View File
@@ -20,11 +20,11 @@ WinUtil currently does too much work before the first GUI paint. Work from the t
## P2 - Install Tab Rendering
- [ ] Reduce up-front creation of all install app entries.
- [ ] Keep category/app grouping as pure data before touching WPF controls.
- [ ] Create WPF controls only on the UI thread.
- [ ] Investigate replacing eager app-entry creation with item templates or incremental category rendering.
- [ ] Verify search, selected-app counts, right-click popup, install, uninstall, and import/export selection state still work.
- [x] Reduce up-front creation of all install app entries.
- [x] Keep category/app grouping as pure data before touching WPF controls.
- [x] Create WPF controls only on the UI thread.
- [x] Investigate replacing eager app-entry creation with item templates or incremental category rendering.
- [x] Verify search, selected-app counts, right-click popup, install, uninstall, and import/export selection state still work.
## P3 - Toggle And Registry Startup Cost