目前在Unix系统下用shell编写的菜单程序大都还是采用多级菜单的模式,这种模式的弊端在于菜单的层次多,界面本身不直观,而且在编程过程中,将菜单的显示格式和内容以及所调用的子程序包括在菜单主程序中,使得程序只能满足某个方面的需求,菜单程序本身不具备通用性。本程序设计采用了一种新的设计思路,将下拉菜单界面作为二维表格来处理,把下拉菜单的内容以及所调用的子程序名称分别存放在这两个二维表中,通过对表的读取,实现了控制光标移动、选择菜单内容以及调用子程序的目的。采用这种方式编写出来的程序易于维护,通用性强。在程序本身不做任何改动的情况下,可以在同一操作平台中进行任意移植,因而具有广泛的应用价值。这种思维模式并不局限在Unix系统下的shell编程,而且对于像C这样的过程化语言也具有一定的借鉴意义。
设计思路
在下拉菜单制作过程中,整个下拉菜单界面所包含的菜单名称以及所调用的子程序名之间的相互关系构成了二维表,其中子菜单名称和子程序名称作为表的元素,通过选择光标在表中上下左右移动,将表中元素读出来,再进行处理运算,从而达到控制菜单的选择以及子程序调用等目的。 <BR><BR>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR bgColor=#99ccff>
<TD colSpan=5><FONT class=a14>表1 菜单项</FONT></TD></TR>
<TR bgColor=#efefef>
<TD>菜单1</TD>
<TD>菜单2</TD>
<TD>菜单3</TD>
<TD>……</TD>
<TD>菜单n</TD></TR>
<TR bgColor=#efefef>
<TD>菜单11</TD>
<TD>菜单12</TD>
<TD>菜单13</TD>
<TD>……</TD>
<TD>菜单1n</TD></TR>
<TR bgColor=#efefef>
<TD>菜单21</TD>
<TD>菜单22</TD>
<TD>菜单23</TD>
<TD> </TD>
<TD>菜单2n</TD></TR>
<TR bgColor=#efefef>
<TD>菜单31</TD>
<TD>菜单32</TD>
<TD>菜单33</TD>
<TD> </TD>
<TD>菜单3n</TD></TR>
<TR bgColor=#efefef>
<TD>……</TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD></TR>
<TR bgColor=#efefef>
<TD>菜单m1</TD>
<TD>菜单m2</TD>
<TD>菜单m3</TD>
<TD> </TD>
<TD>菜单mn</TD></TR></TBODY></TABLE><BR><BR>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR bgColor=#99ccff>
<TD colSpan=5><FONT class=a14>表2 对应各菜单项的子程序</FONT></TD></TR>
<TR bgColor=#efefef>
<TD>子程序11</TD>
<TD>子程序12</TD>
<TD>子程序13</TD>
<TD>……</TD>
<TD>子程序1n</TD></TR>
<TR bgColor=#efefef>
<TD>子程序21</TD>
<TD>子程序22</TD>
<TD>子程序23</TD>
<TD> </TD>
<TD>子程序2n</TD></TR>
<TR bgColor=#efefef>
<TD>子程序31</TD>
<TD>子程序32</TD>
<TD>子程序33</TD>
<TD> </TD>
<TD>子程序3n</TD></TR>
<TR bgColor=#efefef>
<TD>……</TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD></TR>
<TR bgColor=#efefef>
<TD>子程序m1</TD>
<TD>子程序m2</TD>
<TD>子程序m3</TD>
<TD> </TD>
<TD>子程序mn</TD></TR></TBODY></TABLE>
从上面的两个表中不难看出除表1中的第一行为标题行(菜单栏),表1与表2有相同结构,两个表之间的元素存在着一一对应的关系,即每个菜单名称下对应着所调用的程序名(备注: 由于每个菜单标题栏下的子菜单的内容是不一样的,因而每个子菜单下的菜单数目也各不相同,表中一些元素可以是空值,它表示在此没有菜单选择项)。
文中介绍方法的技术难点在于选择光标位置与实际光标位置的关系。所谓选择光标位置是指在上下左右键的控制下,光标在菜单界面的位置,也就是光标在表中的行和列的位置。而实际光标位置是指光标在计算机屏幕上的实际位置。如何通过选择光标位置计算出实际光标位置是本程序的一个难点。本程序的处理办法是将选择光标的行列位置分别作为计算函数的参数,通过函数计算出实际光标的位置。
实现步骤
先将菜单的内容按照一定的格式显示在计算机屏幕上。显示格式要依据表的结构与内容而定,而不能固定不变。如果事先固定下来,会使显示格式与内容之间产生矛盾,难以达到相互之间的统一,程序就不具备通用性。
选择光标在菜单栏左右移动确定选择项目的同时将菜单栏下所包含的子菜单内容显示出来。菜单栏最右端的菜单选择项一般情况下表示“退出”,当选择光标处于这个位置时,回车后退出整个菜单的选择。
在菜单栏中回车或按↓键进入菜单栏下一级子菜单,按照所显示的子菜单内容,选择光标上下移动确定所选定的子菜单内容,回车执行所调用的子程序,←、→两个键退出子菜单的选择。
需要说明的是由于在Unix系统中,光标在上下左右移动时, Unix系统的read命令无法捕获←、↑、→、↓键的控制字符,无法对光标进行有效的控制,为了获取移动光标的控制字符,这里需要用C语言编写一个函数,其主要功能是在光标进行上下左右移动时,能够准确地返回←、↑、→、↓控制键的ASCII值,函数名为getchar。
程序分析
由于光标移动过程中涉及光标的行列位置等重复运算,运用函数可减少程序自身的长度,使程序变得短小、精悍。这里涉及以下一些函数:
1. 画框函数
前面提到显示格式依据表的结构而定,对菜单的边框长度的设置不能固定不变,它要依据菜单标题栏的长度以及标题栏的标题个数而定。这个函数的功能就是依据菜单界面宽度画边框,参数$1表示边框的横线与竖线。
menu_x()
{
_R=$1
col_x=1
while [ col_x -le ${S_LENGTH} ]
do
if [ $_R ]; then echo $_R“\c”
else echo “\c”
fi
col_x=‘expr $col_x + 2'
done
}
2. 计算实际光标在屏幕上的行列位置函数
选择光标在标题栏左右移动的过程中,需要计算光标在屏幕上的实际位置,通过这个函数可以准确地计算出这个实际位置。其运算过程是将选择光标在表中的行列位置作为函数的参数,依据这两个参数计算出光标在屏幕上的准确位置,并将选择光标按照计算出的位置在屏幕上准确显示。其中变量SCREEN- CUR表示表1的元素内容,也就是菜单界面的菜单名称,变量SCREEN-R和SCREEN-C分别表示实际光标在屏幕上位置。执行的结果是将选择光标的内容按实际光标的位置显示在屏幕上。
menu_c()
{ _C=$1 # 选择光标在菜单界面的列位置
_R=$2 # 选择光标在菜单界面的行位置
SCREEN_CUR=‘awk -F“|” “NR==$_R { print }”menu|cut -d“|” -f$_C'
if [ $_C -gt 1 ]; then F_C=‘expr $_C - 1'
SCREEN_LENG=‘head -1 menu| cut -d“|” -f0-$F_C|sed -e ‘s/|//g' \ | awk ‘{ print length($0)}''
else
SCREEN_LENG=0
fi
SCREEN_R=‘expr $_R + 2'
SCREEN_C=‘expr $C_COL + $SCREEN_LENG + 2'
SCREEN_CUR_X=“\033[${SCREEN_R};${SCREEN_C}H${SCREEN_CUR}”
}
3. 计算选择光标在移动过程中位置的函数
选择光标在上下左右的移动过程中,其在菜单界面的位置也随之发生变化,需要通过运算,以确定选择光标在菜单界面的准确位置。其中参数$1表示上下左右键所返回的ASCII值,当参数$1等于2或3时,表示选择光标在上移或左移; 等于1或4时表示选择光标在向下移动或向右移动。参数$2表示选择光标移动过程中在表1中的位置,参数$3表示选择光标移动过程中所限定的区间范围。
menu_x_y()
{ _Z=$1
_S=$2
_L=$3
case $_Z in
2|3) if [ $_S -gt 1 ]
then _S=‘expr $_S - 1'
else _S=$_L
fi ;;
1|4) if [ $_S -lt $_L ]
then _S=‘expr $_S + 1'
else _S=1
fi;;
esac
return $_S
}
4. 计算菜单界面每个菜单栏下的菜单数目函数
通常情况下每个菜单标题下所包含的内容是不一样的,因而每个菜单栏下菜单的数目也是不相同的,需要对每个菜单栏下的菜单数目进行计算,参数$1表示选择光标在菜单栏下的列位置。
menu_row_number()
{ _H=$1
S_NUMBER=‘cut -d“|” -f$_H menu|sed -e ‘s/ //g'-e ‘/^$/d'|\ awk ‘END { print NR}''
}
5. 执行子程序函数
子程序名存在prg文件中,表2中的元素就是子程序名。调用子程序的过程实际就是根据选择光标在菜单界面的行列位置将相应位置的元素读出来,然后依据表2所提供的程序名判断是否真实存在,如果存在则执行。
menu_prg()
{ _C=$1 # 选择光标在菜单界面的列位置
_R=$2 # 选择光标在菜单界面的行位置
prg_name=‘awk -F“|” “NR==$_R { print }” prg|cut -d“|” -f$_C'
if [ -s $prg_name ]
then
eval $prg_name
# 执行所调用的子程序
else
echo “\007”
fi
}
下面是主程序:
# 设置菜单界面前景与背景颜色
COLOR1=“\033[32;44;1m” # 菜单界面的前景色
COLOR2=“\033[33;45;1m” # 菜单界面的背景色
COLOR3=“\033[37;40;1m” # 选择光标的颜色
# 对程序中所用的一些变量进行初始化设置
CUR_R=1 #选择光标在菜单界面的行位置
CUR_C=1 #选择光标在菜单界面的列位置
S_LENGTH=‘head -1 menu|sed -e ‘s/|//g' | \ awk ‘{ print length($0)}''
# 确定菜单界面的宽度
S_MENU=‘head -1 menu| \ awk -F“|” ‘{ print NF}''
# 确定菜单标题栏的字段数
C_COL=‘expr \( 80 - $S_LENGTH - 4 \) / 2 ' # 确定菜单界面的起始位置
echo ${COLOR1}; clear # 按格式显示菜单界面
row=2 # 显示行 [2-23]
while [ row -le 23 ]
do
case $row in
2) echo “\033[${row};${C_COL}H┏\c”; menu_x “━”; echo “┓” ;;
3) echo “\033[${row};${C_COL}H┃\c”;
head -1 menu |sed -e ‘s/|//g' |awk ‘{ print $0 “┃” }';;
23) echo “\033[${row};${C_COL}H┗\c”; menu_x “━”; echo “┛\c” ;;
*) echo “\033[${row};${C_COL}H┃\c”; menu_x “ ”; echo “┃” ;;
esac
row=‘expr $row + 1'
done
while true
do
menu_c $CUR_C $CUR_R # 计算选择光标的位置
echo “${COLOR2}${SCREEN_CUR_X}\c”
stty -echo
getchar # 等待选择
ANS_X=$? # 返回ASCII值
stty echo
echo “${COLOR1}${SCREEN_CUR_X}\c”
case $ANS_X in
3|4) menu_x_y $ANS_X $CUR_C $S_MENU
#选择光标在菜单标题栏中左右移动
CUR_C=$?;;
1|10) if [ $CUR_C = $S_MENU ] #按回车键或↓键进入子菜单
then setcolor -n ; clear; break
fi
menu_row_number $CUR_C
# 在菜单标题栏下将所包含子菜单内容显示在屏幕上
row=2
while [ row -le ${S_NUMBER} ]
do
menu_c $CUR_C $row
echo “${COLOR3}${SCREEN_CUR_X}\c”
row=‘expr $row + 1 '
done
while true do
menu_c $CUR_C $CUR_R
echo “${COLOR2}${SCREEN_CUR_X}\c”
stty -echo
getchar
ANS_Y=$?
stty echo
echo “${COLOR3}${SCREEN_CUR_X}\c”
case $ANS_Y in
1|2) menu_x_y $ANS_Y $CUR_R $S_NUMBER #上下移动选择光标
CUR_R=$?;;
3|4) menu_x_y $ANS_Y $CUR_C $S_MENU #左右移动选择光标退出子菜单选择
CUR_C=$?
CUR_R=1
break;;
10) menu_prg $CUR_C $CUR_R;; #回车后执行子程序
*) echo “\007”;;
esac
done
;;
*) echo “\007\c”;;
esac
done
小结
本文所论述的是如何在Unix系统下利用shell制作通用的下拉菜单。这种通用性集中体现在实现了菜单下的菜单名称以及所调用的子程序名称与菜单主程序的分离,菜单界面下子菜单名称以及所调用的子程序名称分别存放在两个文本文件中,主程序通过对这两个文件的读取实现了菜单程序的正确显示与选择功能。只要对这两个文本文件进行编辑,不需要对主程序进行任何改动,即可完成Unix系统下拉菜单的制作,使得菜单制作非常快捷、灵活。同时可以很方便地进行移植,因而有较强的通用性。而且采用这种方式制作出来的下拉菜单界面比较直观、明了,操作起来更加简单、方便。
备注:在编辑menu和prg文件时,子菜单名称和子程序名称是一一对应的关系,所以子菜单与子程序在文件中位置要摆放正确,不能乱放。由于在本程序中awk语句的所有分隔符都是“|”,而不是空格,因而文本文件中的分隔符也是“|”,而不能用空格,这一点在编辑这两个文件时要特别注意。