如何使用 Batch 在可滚动菜单中制作一系列拨动开关?

Posted

技术标签:

【中文标题】如何使用 Batch 在可滚动菜单中制作一系列拨动开关?【英文标题】:How to make a series of toggle switches in a scrollable menu with Batch? 【发布时间】:2021-01-31 13:09:54 【问题描述】:

我花了好几个小时来做​​这件事。

@echo off
set list = 0 0 1 1
:loop
cls
echo Program Select
echo --------------
set "el=0"
for %%a in (%list%) do ( 
    set /a "el+=1"
    if %%a equ 0 echo "[ ] Program %el%"
    if %%a equ 1 echo "[X] Program %el%"
)
echo ----------------------------------------------------
echo W = Up  /  S = Down  /  L  = Toggle  /  H  = Confirm
choice /C WSLH /N >nul
if %ERRORLEVEL% equ 1 set key=UP
if %ERRORLEVEL% equ 2 set key=DN
if %ERRORLEVEL% equ 3 set key=SL
if %ERRORLEVEL% equ 4 set key=CN
echo %key%
pause >nul
goto loop

现在 key 变量工作正常,我还没有实现滚动,因为我似乎无法让它甚至呈现文本。

目标是得到这样的输出

Program Select
--------------
[ ] Program 1
[ ] Program 2
[X] Program 3
[X] Program 4
----------------------------------------------------
W = Up  /  S = Down  /  L  = Toggle  /  H  = Confirm

但相反,我只获得了程序选择和控件。我错过了什么?

【问题讨论】:

它是因为set list = 0 0 1 1 正在创建一个名为%list % 的变量,看到那个尾随空格了吗?将您的行更改为 SET "list= 0 0 1 1",它将创建一个名为 %list% 的变量,并且外观将按预期处理它 您的问题中不清楚的地方 - 您是要突出显示当前的选择选项(但尚未确认)、当前选择和确认的选项(单个)还是所有确认的选择(多个)?如果处理多项选择,您是否打算将组构建为列表或数组以迭代所选组 - 同样如果处理组,您将如何防止组中相同选择的多个实例? 【参考方案1】:

这是您的代码的更“充实”的版本:

@echo off
SETLOCAL enabledelayedexpansion


:: Program names used

set "program[1]=Program one"
set "program[2]=Program two"
set "program[3]=Program three"
set "program[4]=Program four"
set /a maxprogs=4

:: symbols used for statuses

set "symbols= X"
set "symbolsc=-+"

:restart
:: Set original list status. 0=not selected, 1=selected;

set "list=0 0 1 1"
set /a cursor=1

:loop
cls
echo Program Select
echo --------------
set /a el=0
for %%a in (%list%) do ( 
    set /a el+=1
    if !el!==%cursor% (set "ds=!symbolsc:~%%a,1!") else (set "ds=!symbols:~%%a,1!")
    call set "progname=%%program[!el!]%%"
    echo [!ds!] !progname!
)
echo ----------------------------------------------------
choice /C WSLHQ /N /M "W = Up  /  S = Down  /  L  = Toggle  /  H  = Confirm / Q = Quit "
set /a key=%errorlevel%
if %key%==5 goto :eof
if %key%==4 goto confirm
if %key%==3 goto toggle
if %key%==2 if %cursor%==%maxprogs% (set /a cursor=1) else set /a cursor+=1
if %key%==1 if %cursor%==1 (set /a cursor=%maxprogs%) else set /a cursor-=1

goto loop

:confirm
echo Confirmed!
set "runany="
set /a el=0
for %%a in (%list%) do ( 
    set /a el+=1
    if %%a==1 (
     set /a runany+=1
     call set "progname=%%program[!el!]%%"
     echo Run !progname!
    )
)
if not defined runany echo None selected :(

timeout /t 5 /nobreak
goto restart

:toggle
set "newlist="
set /a el=0
for %%a in (%list%) do ( 
    set /a el+=1
    if !el!==%cursor% (
     if %%a==0 (set "newlist=!newlist! 1") else (set "newlist=!newlist! 0")
    ) else set "newlist=!newlist! %%a"
)
set "list=%newlist%"
goto loop

评论:

SETLOCAL enabledelayedexpansion 允许!var! 在循环中访问var 的更改值(%var% 在循环开始执行之前访问原始 值)。

我使用了maxprogs,以便直观地扩展列表 - 只需跟随弹跳球...

由于光标是静态的,我使用symbols 表示非选中/选中状态,symbolsc 表示“光标”处于状态时,所以+ 是 cursor-is-here-it's -selected 和 - 是 cursor-is-here-it's-not-selected

list 用法与您的版本相似 - cursor 用于当前光标行。

在 display-selections-and-prognames 部分,注意使用!el! 来访问循环内el修改

这里比较棘手的是call set "progname=%%program[!el!]%%" 语句。这使用解析技巧来获取程序名称el 的值。例如,假设el 的当前值为2,这将通过将%% 解释为转义-% 并替换current,在子shell 中执行命令set "progname=%program[2]%" > el 的值。子shell继承其调用者的环境,因此目标变量是根据计算值分配的。

我已经修改了choice 命令以使用图例进行提示,并添加 Q 表示退出作为很好的衡量标准。我自己会使用 UDTRQ 来表示 Up/Down/Toggle/Run/Quit,但有些事情告诉我你可能不一定使用英语。

我将errorlevel 设置为key 以避免在维护其值时必须特别小心,然后测试key 中仅有的5 个感兴趣的值;其他人只需发出哔哔声(来自choice 并再次出现。

退出很明显; cursor-move 只是增加或减少 cursor 并检查边界条件以进行回滚。

另外两个例程只是对显示列表例程的修改; run 显示程序名称,因为我不知道您是要串行还是并行运行程序。

toggle 例程使用相同的技术重建list,同时切换cursor'th 元素。

【讨论】:

我喜欢你的方法,但我更喜欢它是set "ds=!symbols:~%%a,1!" 然后if !el!==%cursor% (set "aa=*[!ds!]*") else (set "aa= [!ds!] ") 然后echo !aa! !progname! 但这只是外观的偏好问题。否则我正在考虑编写非常相似的代码,干得好 magoo! 值得注意的是,这里的代码比 T3RR0Rs 更快,因为他将大部分代码保存在内存中,因此我预计他会更快。我猜这是因为他正在将文件写入磁盘并读取它们以费心为代码着色......如果不是这样的话,它会更快。 已通过将 findstr 函数 C_out 转换为定义到菜单宏中的宏来解决我的答案中选项显示的效率问题【参考方案2】:

我可以理解格式化菜单显示的愿望 - 但是我不推荐您考虑的方法。让用户滚动到他们想要的选项,然后确认它不会使脚本对用户更容易。

用户通过一个按键从列表中选择一个选项要简单得多。您可以在选择后对确认操作至关重要的位置轻松添加确认。

我开发了一个菜单模板(适用于 Windows 10),如果您愿意,可以轻松编写脚本和菜单选项的操作。使用该模板的实用 GUI 脚本示例为here

以更简单的方式完成的更符合您想要的输出的东西:


更新 - 向后兼容版本:

增强功能:

菜单选项数组总共支持 36 个选项 颜色模式选择 - 现在可以在运行时禁用颜色。 旧版操作系统的 Findstr 颜色输出已转换为宏以加快执行速度。
::: Multi-Use Menu :: Author - T3RRY ::  Version 4.2 :: October 2020 ::
::: %Menu% macro - accepts up to 36 options for selection as quoted Args in list form.
::: parameters to call functions with via a given menu can be supplied using Substring modification as exampled.
::: - Multiple selection of menu items achieved using loops.
::: Tests if a menu option is a label and calls if true.
::: Builds a list of selected options [ that can be deselected ] when used in a loop.
@Echo off & Goto :Main
:::::::::::::::::::::::::: [* Balances Environment stack on script completion *]
:End [Endlocal ^& Set "_End=Y" ^& Exit /B 0]
 Color 07 & %CLOSE%
:::::::::::::::::::::::::::::::::::::::::::::: Findstr based Colorprint function
::: No longer used within menu macro
::: Unsupported Chars: "<" ">" ":" "\" "/" "?" "&"
:C_out [BG:a-f|0-9][FG:a-f|0-9] ["Quoted Strings" "to print"]
 Set "Str_=%*" &  Set "_Str=" & For %%G in (!Str_!)Do Set "_Str=!_Str! %%~G"
 Set "C_Out=%~1"
 Set "_Str=!_Str:%1 =!" & For /F "Delims=" %%G in ("!_Str!")Do Set "_Str=%%~G"
 For %%G in (!_Str!) Do Set ".Str=%%G"
  If /I "!.Str!" == "Exit"  (Set "C_Out=04") Else If /I "!.Str!" == "Next" (Set "C_Out=02") Else If /I "!.Str!" == "Continue" (Set "C_Out=02") Else If /I "!.Str!" == "Back" (Set "C_Out=05") Else If /I "!.Str!" == "Return" (Set "C_Out=05")
    <nul set /p ".=%DEL%" > " !_Str!"
    findstr /v /a:!C_Out! /R "^$" " !_Str!" nul
    del " !_Str!" > nul 2>&1
    Echo/
Exit /B 0
::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Key variable and Macro definition
:main
::::::::::::::::::::: [ For readablities sake - %Menu% macro is built from the following ]:
rem ::: Order of definition must be preserved.
rem [* prepare default findstr color for non windows 10 users *]
 For /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do (set "DEL=%%a")
rem [* default color args for Picked / Not Picked options. Overriden to Echo with Ascii Escape codes if Windows 10 *]
rem [* Color /? for the full combination of colors - BG + FG colors must differ. [BG:a-f|0-9][FG:a-f|0-9] *]
 Set "ColText=For %%l in (1 2)Do if %%l==2 (Set "_Str="&(Set "C_Out=!Oline:~0,2!" & Set "_Str=!Oline:~3!")&(For %%s in (!_Str!)Do Set ".Str=%%s")&(If /I "!.Str!" == "Exit"  (Set "C_Out=04") Else If /I "!.Str!" == "Next" (Set "C_Out=02") Else If /I "!.Str!" == "Continue" (Set "C_Out=02") Else If /I "!.Str!" == "Back" (Set "C_Out=05") Else If /I "!.Str!" == "Return" (Set "C_Out=05"))&( <nul set /p ".=%DEL%" > " !_Str!" )&( findstr /v /a:!C_Out! /R "^$" " !_Str!" nul )&( del " !_Str!" > nul 2>&1 )& Echo/)Else Set Oline="
 Set "_End="

:# Windows Version control. Assigns flag true if system is windows 10 build GTR 10586
:# https://en.wikipedia.org/wiki/ANSI_escape_code#DOS,_OS/2,_and_Windows
:# Version 1511 build number = 10.0.10586
 Set "Win10="
 For /f "tokens=3 delims=." %%v in ('Ver')Do if %%v GTR 10586 Set "Win10=True"

:# If Win10 true ; Test if virtual terminal codes enabled ; enable if false
:# removes win10 flag definition if version does not support Virtual Terminal sequences
:# Reg values: https://devblogs.microsoft.com/commandline/understanding-windows-console-host-settings/
 If defined Win10 (
  Reg Query HKCU\Console | %SystemRoot%\System32\findstr.exe /LIC:"VirtualTerminalLevel    REG_DWORD    0x1" > nul || (
    Reg Add HKCU\Console /f /v VirtualTerminalLevel /t REG_DWORD /d 1
  ) > Nul && (
    Echo(CMD restart required to enable Virtual terminal sequences.
    Pause
    EXIT
  ) || Set "Win10="
 )

  If defined Win10 For /f %%e in ('Echo(prompt $E^|Cmd') Do Set "\E=%%e"

  If Defined Win10 (
   Set "_nP=Echo/%\E%[90m"& Set "_P=Echo/%\E%[33m"
   Echo/Menu Color mode: [L]egacy [W]indows [D]isabled & For /F "Delims=" %%C in (' Choice /N /C:LWD ')Do (
    If "%%C" =="L" (
     Set "_nP=For %%l in (1 2)Do if %%l==2 (Set "_Str="&(Set "C_Out=!Oline:~0,2!" & Set "_Str=!Oline:~3!")&(For %%s in (!_Str!)Do Set ".Str=%%s")&(If /I "!.Str!" == "Exit"  (Set "C_Out=04") Else If /I "!.Str!" == "Next" (Set "C_Out=02") Else If /I "!.Str!" == "Continue" (Set "C_Out=02") Else If /I "!.Str!" == "Back" (Set "C_Out=05") Else If /I "!.Str!" == "Return" (Set "C_Out=05"))&( <nul set /p ".=%DEL%" > " !_Str!" )&( findstr /v /a:!C_Out! /R "^$" " !_Str!" nul )&( del " !_Str!" > nul 2>&1 )& Echo/)Else Set Oline=08"
     Set "_P=For %%l in (1 2)Do if %%l==2 (Set "_Str="&(Set "C_Out=!Oline:~0,2!" & Set "_Str=!Oline:~3!")&(For %%s in (!_Str!)Do Set ".Str=%%s")&(If /I "!.Str!" == "Exit"  (Set "C_Out=04") Else If /I "!.Str!" == "Next" (Set "C_Out=02") Else If /I "!.Str!" == "Continue" (Set "C_Out=02") Else If /I "!.Str!" == "Back" (Set "C_Out=05") Else If /I "!.Str!" == "Return" (Set "C_Out=05"))&( <nul set /p ".=%DEL%" > " !_Str!" )&( findstr /v /a:!C_Out! /R "^$" " !_Str!" nul )&( del " !_Str!" > nul 2>&1 )& Echo/)Else Set Oline=06"
    )
    If "%%C" =="D" (Set "_nP=Echo/"& Set "_P=Echo/")
   )
  ) Else (
    Set "_nP=For %%l in (1 2)Do if %%l==2 (Set "_Str="&(Set "C_Out=!Oline:~0,2!" & Set "_Str=!Oline:~3!")&(For %%s in (!_Str!)Do Set ".Str=%%s")&(If /I "!.Str!" == "Exit"  (Set "C_Out=04") Else If /I "!.Str!" == "Next" (Set "C_Out=02") Else If /I "!.Str!" == "Continue" (Set "C_Out=02") Else If /I "!.Str!" == "Back" (Set "C_Out=05") Else If /I "!.Str!" == "Return" (Set "C_Out=05"))&( <nul set /p ".=%DEL%" > " !_Str!" )&( findstr /v /a:!C_Out! /R "^$" " !_Str!" nul )&( del " !_Str!" > nul 2>&1 )& Echo/)Else Set Oline=08"
    Set "_P=For %%l in (1 2)Do if %%l==2 (Set "_Str="&(Set "C_Out=!Oline:~0,2!" & Set "_Str=!Oline:~3!")&(For %%s in (!_Str!)Do Set ".Str=%%s")&(If /I "!.Str!" == "Exit"  (Set "C_Out=04") Else If /I "!.Str!" == "Next" (Set "C_Out=02") Else If /I "!.Str!" == "Continue" (Set "C_Out=02") Else If /I "!.Str!" == "Back" (Set "C_Out=05") Else If /I "!.Str!" == "Return" (Set "C_Out=05"))&( <nul set /p ".=%DEL%" > " !_Str!" )&( findstr /v /a:!C_Out! /R "^$" " !_Str!" nul )&( del " !_Str!" > nul 2>&1 )& Echo/)Else Set Oline=06"
    Echo/Menu Color mode: [L]egacy [D]isabled & For /F "Delims=" %%C in (' Choice /N /C:LD ')Do If "%%C" =="D" (Set "_nP=Echo/"& Set "_P=Echo/")
   )
  )

rem [* Menu supports 36 choices using _O array index with substring modification on _Cho var to index choice selection of Array Elements *]
 Set "_Cho=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 Set "DisplayArray=(Echo/!#Sel!| Findstr /LIC:"%%~G" > Nul 2> Nul && ( %_P% [!_Cho:~%%z,1!] X %%~G ) ) || ( %_nP% [!_Cho:~%%z,1!] - %%~G )"
 Set "#Sel=_Nil"
 Set "ClearArray=(For /F "Tokens=1,2 Delims==" %%i in (' Set Opt[ 2^> Nul ')Do "Set %%i=")"
 Set "ResetVars=(Set "#L=F" &  Set "OptL=" & Set "_O=0")"
 Set "CLOSE=POPD & Endlocal & Set "_End=Y" & Exit /B 0"
 Set "BuildArray=((If !_O! GTR 35 (Call :C_Out 04 "Maximum options [!_O!] Exceeded" & (Timeout /T 5 /NOBREAK) & %CLOSE%))&Set "OptL=!OptL!!_Cho:~%%z,1!"&Set "Opt[!_Cho:~%%z,1!]=%%~G")"
 Set "MakeChoice=(For /F "Delims=" %%C in ('Choice /N /C:!OptL!')Do findstr.exe /BLIC:":!Opt[%%C]!" "%~F0" > nul 2> nul && Call :!Opt[%%C]! "Param" 2> Nul || ((Echo/"!#Sel!"| Findstr /LIC:"!Opt[%%C]!" > Nul 2> Nul && (For /F "Delims=" %%r in ("!Opt[%%C]!")Do If Not "!#Sel!" == "" (Set "#Sel=!#Sel:"%%r"=!")Else (Set "#Sel=_Nil"))) || (Set "#Sel=!#Sel! "!Opt[%%C]!"")))"
 Set "Return=For /L %%s in (0 1 4)Do (If not "!#Sel!" == "" (If "!#Sel:~0,1!" == " " (If "!#L!" == "F" (Set "#Sel=!#Sel:~1!"))Else (Set "#L=T"))Else (Set "#Sel=_Nil"))&if not "!#Sel!" == "_Nil" if not "!#Sel!" == "" (Set "#Sel=!#Sel:_Nil=!")"
 Set "Menu=(If defined _End Goto :End) &For %%n in (1 2)Do if %%n==2 (%ClearArray% & %ResetVars% &(For %%G in (!Options!)Do For %%z in (!_O!)Do %BuildArray% & %DisplayArray% &Set /A "_O+=1")& %MakeChoice% & %Return% )Else Set Options="
 For %%M in ( ClearArray ResetVars BuildArray DisplayArray MakeChoice Return )Do Set "%%M="
 IF NOT EXIST "%TEMP%\colorize" md "%TEMP%\colorize"
 PUSHD "%TEMP%\colorize" || (Echo/"%TEMP%\colorize" Could not be found & %CLOSE%)
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 Setlocal EnableExtensions EnableDelayedExpansion & REM [* required to be activated AFTER definition of Macro's. *]
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Commence script main body | Demonstration of usages
:Loop
rem [* doublequoting of options recommended - Required for strings containing standard delimiters. *]
 If not defined _End cls
 If not defined _End %ColText%05 Make a selection.
rem [* Param Substring modification examples passing parameters to labels called when selected - Optional feature. *]
 Set /A "PRM=!Random! %%100 + 100" & Rem [* Example param only *]
 %Menu% "Exit" "Next" "Option 1" "Option 2" "Option 3" "Option 4"
 Echo/"!#Sel!" | Findstr.exe /LIC:"Exit" > Nul 2> Nul && (Goto :End)
Goto :Loop
 
:Next
rem [* Selection of a single option occurs by using the macro without a loop or resetting the #Sel variable
rem - between %menu% use and next iteration of a loop *]
rem [* Process #Sel items using the !#Sel! variable - then ** SET "#Sel-_Nil" prior to next usage of Menu macro** *]
 Set "Menu1Opts=!#Sel!"
 Set "#Sel="
 Cls
 Call :C_Out 03 "Selected =" !Menu1opts!
 %Menu% "Exit" " + Continue" ".. Back"
 Echo/!#Sel! | Findstr.exe /LIC:".. Back" > Nul 2> Nul && (Set "#Sel=!Menu1opts!"& Exit /B 0)
 Echo/!#Sel! | Findstr.exe /LIC:"Exit" > Nul 2> Nul && (%CLOSE%)
 Set "#Sel="
 Echo/!Menu1opts! | Findstr.exe /LIC:"_Nil" > Nul 2> Nul && (Call :C_Out 04 "Selection Required."&(Pause & Exit /B 0)) || Call :C_Out 02 "Confirmed=" !Menu1opts!
 Call :C_Out 02 "Example complete."
 Pause
rem [* to exit at end of script or by user selection *]
 %CLOSE%
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: End of example

输出:

【讨论】:

这绝对很棒,但不幸的是我想保持与旧版本的 Windows 的兼容性(我相信颜色不适用),这也不适用于我的应用程序正在发展。我知道还有其他更好的语言可以解决这个问题,但我决定使用 Batch 有几个原因,我不会在这里讨论。 很公平 - 最好将这些细节添加到问题的主体中。有一种逐行着色文本的方法,它与使用 findstr 的旧 Windows 版本兼容,不需要太多修改上述内容即可使用。至于“这不适用于我正在开发的应用程序”,除了向后兼容之外,我看不出这种方法如何不兼容。请详细说明原因? @GabrielKeess 回答已更新以支持 Windows 10 和更早版本,而不会影响 Windows 10 系统的性能。 示例用法还扩展为演示如何在将初始选择转移到新变量后重用菜单进行单项选择。 我预计你的速度会更快,因为你将大部分内容保存在内存中。我猜这是因为您正在将文件写入磁盘并读取它们以费心为代码着色......如果不是这种情况,它可能会快得多。

以上是关于如何使用 Batch 在可滚动菜单中制作一系列拨动开关?的主要内容,如果未能解决你的问题,请参考以下文章

如何制作 TopMost 拨动开关

制作可滚动表格

如何制作 Bootstrap 下拉菜单的垂直滚动条

如何在可滚动部分的视口中更改项目的样式属性?

Python,Tkinter:如何在可滚动画布上获取坐标

如何让文本在可滚动的 UILabel 中居中?