批处理脚本:带有突出显示的选择的导航菜单

时间:2016-07-04 01:51:43

标签: windows user-interface batch-file cmd scripting

我痴迷于凭借纯命令行模仿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上的低谷参考和强烈的谷歌搜索到目前为止并不具有启发性。所以我希望指出教育材料以及对我的代码的评论。我想强调的是,问题陈述并不意味着使用其他库或资源包。

2 个答案:

答案 0 :(得分:1)

要求提供图书馆和教程的建议超出了S.O.的范围,你的代码是tl; dr非常接近睡觉时间。也许我明天再说一遍。我看到的最明显的问题是你的函数位于主运行时,你永远不会goto过去。它们应该在最终goto :EOFexit /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