PowerShell中如何为已选选项着色且不使用Clear-Host?
Select-Multiple 函数优化:高亮选中项且不清除屏幕
需求说明
我需要实现一个Select-Multiple函数,核心功能和要求如下:
- 接收选项列表(例如
@("First Option", "Second Option"))、标题、描述、提问文本等参数 - 生成带字母标识的交互菜单,示例格式:
a All b First Option c Second Option d Exit Choose your option: > ... - 持续显示提问文本,直到满足任一终止条件:
- 用户选择
All或Exit选项 - 用户选中所有除
All和Exit外的可选选项
- 用户选择
- 最终返回用户选中的选项列表
- 核心要求:不使用
Clear-Host命令,实现已选中选项的高亮显示(例如选中b后,b First Option变为绿色)
初始版本代码
function Select-Multiple { Param( [Parameter(Mandatory=$false)] [string] $title, [Parameter(Mandatory=$false)] [string] $description, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $options, [Parameter(Mandatory=$true)] [string] $question ) if ($title) { Write-Host -ForegroundColor Yellow $title Write-Host -ForegroundColor Yellow ("-"*$title.Length) } if ($description) { Write-Host $description Write-Host } $chosen = @() $values = @() $offset = 0 $all = "All" $values += @($all) Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline Write-Host $all $offset++ $options.GetEnumerator() | ForEach-Object { Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline $values += @($_) Write-Host $_ $offset++ } $exit = "Exit" $values += @($exit) Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline Write-Host $exit $answer = -1 while($chosen.Count -ne $options.Count) { Write-Host "$question " -NoNewline $selection = (Read-Host).ToLowerInvariant() if (($selection.Length -ne 1) -or (([int][char]($selection)) -lt 97 -or ([int][char]($selection)) -gt (97+$offset))) { Write-Host -ForegroundColor Red "Illegal answer. " -NoNewline } else { $answer = ([int][char]($selection))-97 $value = $($values)[$answer] if ($value -eq $exit) { return $chosen } if ($value -eq $all) { return $options } else { if ($chosen.Contains($value)) { Write-Host -ForegroundColor Red "The value $value was already chosen." } else { $chosen += ($value) } } } if ($answer -eq -1) { Write-Host -ForegroundColor Red "Please answer one letter, from a to $([char]($offset+97))" } $answer = -1; } return $chosen }
优化后的实现
function Select-Multiple { Param( [Parameter(Mandatory=$false)] [string] $title, [Parameter(Mandatory=$false)] [string] $description, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $options, [Parameter(Mandatory=$true)] [string] $question ) # 计算菜单总行数,用于光标回退定位 $baseMenuLines = 1 # All选项行 $baseMenuLines += $options.Count # 用户自定义选项行数 $baseMenuLines += 1 # Exit选项行 if ($title) { $baseMenuLines += 2 } # 标题行+分隔线行 if ($description) { $baseMenuLines += 2 } # 描述行+空行 $chosen = @() $renderedOnce = $false # 定义菜单渲染函数,负责绘制带高亮的菜单 function Render-Menu { if ($script:renderedOnce) { # 使用ANSI转义序列将光标向上移动到菜单起始位置,覆盖重绘 Write-Host "`e[$($baseMenuLines)A" -NoNewline } else { $script:renderedOnce = $true } # 渲染标题 if ($title) { Write-Host -ForegroundColor Yellow $title Write-Host -ForegroundColor Yellow ("-"*$title.Length) } # 渲染描述 if ($description) { Write-Host $description Write-Host } # 渲染All选项 $all = "All" Write-Host -ForegroundColor Yellow "a " -NoNewline Write-Host $all # 渲染用户自定义选项,高亮已选中项 $offset = 1 $options.GetEnumerator() | ForEach-Object { $char = [char]($offset + 97) Write-Host -ForegroundColor Yellow "$char " -NoNewline if ($chosen.Contains($_)) { Write-Host -ForegroundColor Green $_ } else { Write-Host $_ } $offset++ } # 渲染Exit选项 $exit = "Exit" $exitChar = [char]($offset + 97) Write-Host -ForegroundColor Yellow "$exitChar " -NoNewline Write-Host $exit } # 首次渲染菜单 Render-Menu while($chosen.Count -ne $options.Count) { # 显示提问文本 Write-Host "$question " -NoNewline $selection = (Read-Host).ToLowerInvariant() # 清除当前行的错误提示内容 Write-Host "`e[K" -NoNewline $validInput = $true $maxCharCode = 97 + $options.Count + 1 # All + 选项 + Exit 的总数量 if (($selection.Length -ne 1) -or (([int][char]$selection) -lt 97 -or ([int][char]$selection) -gt $maxCharCode)) { Write-Host -ForegroundColor Red "非法输入,请输入a到$([char]$maxCharCode)之间的单个字母" $validInput = $false } else { $answer = ([int][char]$selection) - 97 $value = @("All") + $options + @("Exit") | Select-Object -Index $answer switch ($value) { "Exit" { return $chosen } "All" { return $options } default { if ($chosen.Contains($value)) { Write-Host -ForegroundColor Red "选项 '$value' 已被选中" } else { $chosen += $value # 重绘菜单以高亮新选中的选项 Render-Menu } } } } # 输入无效时,回退到提问行位置,等待下一次输入 if (-not $validInput) { Write-Host "`e[1A`e[K" -NoNewline } } return $chosen }
关键优化点说明
- ANSI转义序列控制光标:
- 使用
`e[$nA将光标向上移动指定行数,回到菜单起始位置,实现菜单的覆盖重绘,无需调用Clear-Host - 使用
`e[K清除当前行的所有内容,避免错误提示文本残留
- 使用
- 提取菜单渲染逻辑:将菜单绘制逻辑封装为
Render-Menu函数,每次选中新选项后调用该函数,动态高亮已选中的选项(绿色显示) - 输入验证优化:调整字母范围计算逻辑,确保输入验证匹配菜单的实际选项数量
- 界面整洁处理:输入无效时,自动回退到提问行并清除错误提示,保持交互界面的整洁
内容的提问来源于stack exchange,提问作者Dreeze
相关产品推荐
相关产品推荐

