我痴迷于凭借纯命令行模仿8位时代程序功能的想法。所以我想制作一系列菜单选项,可以选择标称(因为没有箭头键的键盘映射)左右键,并用Enter确认。为了清楚明白,我写了一个有效的例子。 请注意ANSI转义码我使用A LOT,不确定ESC控制字符是否会显示在浏览器中。
@ECHO OFF
ECHO Chose and option with [36mZ[0m for left, [36mX[0m for right, [36mC[0m for confirm
:PICKMENU
SETLOCAL
SET menumsg="[7mExit [0m Next"
SET menumsgDef="[7mExit [0m Next"
SET menumsgNxt="[0mExit [7m Next"
:subpick
CHOICE /N /C zxc /m:[3;1H%menumsg%
IF %errorlevel% EQU 0 ECHO Just in case of errors & GOTO PICKMENU
IF %errorlevel% EQU 1 SET menumsg=%menumsgDef% & GOTO subpick
IF %errorlevel% EQU 2 SET menumsg=%menumsgNxt% & GOTO subpick
IF %errorlevel% EQU 3 GOTO confirm
:confirm
IF %menumsg% EQU %menumsgNxt% (ECHO [0mNext option is chosen & GOTO PICKMENU) ELSE (ECHO ^Exit is chosen! & TIMEOUT 2 >NUL & EXIT)
ENDLOCAL
PAUSE
当我尝试将此菜单包装在某种函数中时,我的问题就出现了,我可以在每次需要菜单或对话框时传递参数而不是制作hodgie代码:
@ECHO OFF
REM ----So I start with setting variables with idea of argument string tokenisation on the mind----
SET $teststring=OptionOne OptionTwo OptionThree
SET /A $currentitem=1
REM ----I would use function that I found here on Stackoverflow to count amount of options in my string.----
REM ----This is magic for me, without this solution I would be using FOR loop and delims----
CALL :COUNTOPTIONS %$teststring%
:COUNTOPTIONS
SETLOCAL EnableDelayedExpansion
SET /A Count=0
:repeatme
IF NOT "%1"=="" (
SET /A Count+=1
SHIFT
GOTO :repeatme
)
ENDLOCAL & SET $optionsamount=%Count%
ECHO The number of words is %$optionsamount%
PAUSE
:PICKCURRENT
REM ----So I got maximum number of options, I guess it would be useful since by design user could stumble upon the
REM ----edge of menu by occasionaly pressing "left" or "right" controls too much. Thats how I limit the variable...
IF %$currentitem% LSS 1 SET /A $currentitem=1
IF %$currentitem% GTR %$optionsamount% SET /A $currentitem=%$optionsamount%
ECHO %$optionsamount%
REM ----This is where I completely lost it. I am trying to echo "options" string as separated items to the target location...-----
FOR /F "tokens=*" %%A IN ("%$teststring%") DO ECHO [1;1H%%A
REM ----...and echo "currently selected" item into the same place...
FOR /F "tokens=%$currentitem%" %%B IN ("%$itemstring%") DO SET $selector=[1;1H[7m%%B[0m
REM ----...and make it interactive with choice----
CHOICE /N /C zxc /m:%$selector%
IF %errorlevel% EQU 0 ECHO oops & GOTO PICKITEM
IF %errorlevel% EQU 1 SET /A "$currentitem=$currentitem-1" & GOTO PICKCURRENT
IF %errorlevel% EQU 2 SET /A "$currentitem=$currentitem+1" & GOTO PICKCURRENT
IF %errorlevel% EQU 3 GOTO confirm
PAUSE
(我把脚本结构变得有利于说明我在做什么)。
显然,我的问题是缺乏理解循环和功能,但是阅读ss64上的低谷参考和强烈的谷歌搜索到目前为止并不具有启发性。所以我希望指出教育材料以及对我的代码的评论。我想强调的是,问题陈述并不意味着使用其他库或资源包。
答案 0 :(得分:1)
要求提供图书馆和教程的建议超出了S.O.的范围,你的代码是tl; dr非常接近睡觉时间。也许我明天再说一遍。我看到的最明显的问题是你的函数位于主运行时,你永远不会goto
过去。它们应该在最终goto :EOF
或exit /B
之后,因此它们不会以批处理脚本的正常从上到下的顺序执行。例如:
@echo off & setlocal
call :add 5 9 sum
echo The sum is %sum%
exit /b
rem // Functions go here, below exit /b
:add <num1> <num2> <return_var>
set /a %~3=%~1 + %~2
goto :EOF
:subtract <num1> <num2> <return_var>
set /a %~3=%~1 - %~2
goto :EOF
但我想至少现在我可能会分享一个不需要ansi翻译的更完整的菜单脚本。
<# : Batch portion
@echo off & setlocal enabledelayedexpansion
set "menu[0]=Format C:"
set "menu[1]=Send spam to boss"
set "menu[2]=Truncate database *"
set "menu[3]=Randomize user password"
set "menu[4]=Download Dilbert"
set "menu[5]=Hack local AD"
for /L %%I in (6,1,15) do set "menu[%%I]=loop-generated demo item %%I"
set "default=0"
powershell -noprofile "iex (${%~f0} | out-string)"
echo You chose !menu[%ERRORLEVEL%]!.
goto :EOF
: end batch / begin PowerShell hybrid chimera #>
$menutitle = "CHOOSE YOUR WEAPON"
$menuprompt = "Use the arrow keys. Hit Enter to select."
$menufgc = "yellow"
$menubgc = "darkblue"
[int]$selection = $env:default
$h = $Host.UI.RawUI.WindowSize.Height
$w = $Host.UI.RawUI.WindowSize.Width
# assume the dialog must be at least as wide as the menu prompt
$len = [math]::max($menuprompt.length, $menutitle.length)
# get all environment vars matching menu[int]
$menu = gci env: | ?{ $_.Name -match "^menu\[(\d+)\]$" } | sort @{
# sort on array index as int
Expression={[int][RegEx]::Match($_.Name, '\d+').Value}
} | %{
$val = $_.Value.trim()
# truncate long values
if ($val.length -gt ($w - 8)) { $val = $val.Substring(0,($w - 11)) + "..." }
$val
# as long as we're looping through all vals anyway, check whether the
# dialog needs to be widened
$len = [math]::max($val.Length, $len)
}
# dialog must accomodate string length + box borders + idx label
$dialogwidth = $len + 8
# center horizontally
$xpos = [math]::floor(($w - $dialogwidth) / 2)
# center at top 1/3 of the console
$ypos = [math]::floor(($h - ($menu.Length + 4)) / 3)
# Is the console window scrolled?
$offY = [console]::WindowTop
# top left corner coords...
$x = [math]::max(($xpos - 1), 0); $y = [math]::max(($offY + $ypos - 1), 0)
$coords = New-Object Management.Automation.Host.Coordinates $x, $y
# ... to the bottom right corner coords
$rect = New-Object Management.Automation.Host.Rectangle `
$coords.X, $coords.Y, ($w - $xpos + 1), ($offY + $ypos + $menu.length + 4 + 1)
# The original console contents will be restored later.
$buffer = $Host.UI.RawUI.GetBufferContents($rect)
function destroy { $Host.UI.RawUI.SetBufferContents($coords,$buffer) }
$box = @{
"nw" = [char]0x2554 # northwest corner
"ns" = [char]0x2550 # horizontal line
"ne" = [char]0x2557 # northeast corner
"ew" = [char]0x2551 # vertical line
"sw" = [char]0x255A # southwest corner
"se" = [char]0x255D # southeast corner
"lsel" = [char]0x2192 # right arrow
"rsel" = [char]0x2190 # left arrow
}
function WriteTo-Pos ([string]$str, [int]$x = 0, [int]$y = 0,
[string]$bgc = $menubgc, [string]$fgc = $menufgc) {
$saveY = [console]::CursorTop
[console]::setcursorposition($x,$offY+$y)
Write-Host $str -b $bgc -f $fgc -nonewline
[console]::setcursorposition(0,$saveY)
}
# Wait for keypress of a recognized key, return virtual key code
function getKey {
# PgUp/PgDn + arrows + enter + 0-9
$valid = 33..34 + 37..40 + 13 + 48..(47 + [math]::min($menu.length, 10))
# 10=a, 11=b, etc.
if ($menu.length -gt 10) { $valid += 65..(54 + $menu.length) }
while (-not ($valid -contains $keycode)) {
$keycode = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode
}
$keycode
}
# for centering the title and footer prompt
function center([string]$what, [string]$fill = " ") {
$lpad = $fill * [math]::max([math]::floor(($dialogwidth - 4 - $what.length) / 2), 0)
$rpad = $fill * [math]::max(($dialogwidth - 4 - $what.length - $lpad.length), 0)
"$lpad $what $rpad"
}
function menu {
$y = $ypos
WriteTo-Pos ($box.nw + (center $menutitle $box.ns) + $box.ne) $xpos ($y++)
WriteTo-Pos ($box.ew + (" " * ($dialogwidth - 2)) + $box.ew) $xpos ($y++)
# while $item can equal $menu[$i++] without error...
for ($i=0; $item = $menu[$i]; $i++) {
$rtpad = " " * [math]::max(($dialogwidth - 8 - $item.length), 0)
if ($i -eq $selection) {
WriteTo-Pos ($box.ew + " " + $box.lsel + " $item " + $box.rsel + $rtpad `
+ $box.ew) $xpos ($y++) $menufgc $menubgc
} else {
# if $i is 2 digits, switch to the alphabet for labeling
$idx = $i; if ($i -gt 9) { [char]$idx = $i + 55 }
WriteTo-Pos ($box.ew + " $idx`: $item $rtpad" + $box.ew) $xpos ($y++)
}
}
WriteTo-Pos ($box.sw + ([string]$box.ns * ($dialogwidth - 2) + $box.se)) $xpos ($y++)
WriteTo-Pos (" " + (center $menuprompt) + " ") $xpos ($y++)
1
}
while (menu) {
[int]$key = getKey
switch ($key) {
33 { $selection = 0; break } # PgUp/PgDn
34 { $selection = $menu.length - 1; break }
37 {} # left or up
38 { if ($selection) { $selection-- }; break }
39 {} # right or down
40 { if ($selection -lt ($menu.length - 1)) { $selection++ }; break }
# letter, number, or enter
default {
# if alpha key, align with VirtualKeyCodes of number keys
if ($key -gt 64) { $key -= 7 }
if ($key -gt 13) {$selection = $key - 48}
# restore the original console buffer contents
destroy
exit($selection)
}
}
}
答案 1 :(得分:0)
嗯,我能在几个不眠之日自己找到解决方案:
@ECHO OFF
:SELECTLOOP
SETLOCAL EnableDelayedExpansion
SET /A current=1
:subpick
ECHO ------
SET /A count=0
FOR /F "delims=" %%M IN ("ONE TWO THREE FOUR FIVE") DO FOR %%N IN (%%M) DO (
SET /A count+=1
SET str!count!=%%N
IF !count! NEQ !current! (ECHO %%N) ELSE (ECHO ^>%%N^<))
CHOICE /N /C zxc /m:"------"
IF %errorlevel% EQU 0 ECHO error message & GOTO SELECTLOOP
IF %errorlevel% EQU 1 set /A current-=1 & GOTO subcheck
IF %errorlevel% EQU 2 set /A current+=1 & GOTO subcheck
IF %errorlevel% EQU 3 (ECHO !current! IS CONFIRMED) & (TIMEOUT 3 >NUL)
:subcheck
CLS
IF !current! LSS 1 SET /A current=1
IF !current! GTR !count! SET /A current=!count!
GOTO subpick
PAUSE