Render install app entries incrementally
This commit is contained in:
@@ -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"
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,9 @@ Describe "XAML and sync wiring" {
|
||||
"InstallAppAreaScrollViewer",
|
||||
"InstallAppAreaOverlay",
|
||||
"InstallAppAreaOverlayText",
|
||||
"InstallAppRenderQueue",
|
||||
"InstallAppRenderTimer",
|
||||
"InstallAppEntriesRendered",
|
||||
"ProgressBar",
|
||||
"progressBarTextBlock",
|
||||
"Win11ISOImageInfo",
|
||||
|
||||
+5
-5
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user