摘要 该文在AIX V3.2.5环境下,利用C语言的指针机制,开发出了非图形终端(HT-382)上通用下拉弹出式菜单工具。
一、问题的提出
随着计算机在社会生产、生活各部门的广泛应用和高档微机、小型机的不断出台,高性能UNIX/XENIX多用户、多任务操作系统正在逐步取代原有单用户的DOS系统。这就迫切需要在新环境下开发出大批功能齐全的应用软件工具,以取代DOS环境下的各种应用软件,从而满足用户新的要求。
以往的菜单工具虽各有其特点,但均是在DOS环境下开发的,都借助于Windows或TurboC强大的图形处理库函数,有良好的开发环境,而且一般的做法是,为每一个菜单编制一特定的程序,即:用户菜单与程序具有一一对应的关系,一旦菜单做出修改,对程序也必须做相应的改动,从而导致编程工作量大,重复劳动多,灵活性差等不足。能否在UNIX操作系统下的中西文终端上,脱离TurboC强大的函数库,开发出一个独立于用户菜单,对不同内容与结构的若干个用户菜单实现管理的应用程序呢?
针对这个问题,笔者经过探索和实践,以 AIX V3.2.5 为背景,利用C语言的指针机制,在非图形终端(HT-382)上开发出一通用的下拉弹出式菜单工具软件。该程序和具体用户菜单的联接是通过相应于菜单的描述文件(*.TXT)实现。*.TXT文件不仅描述了用户菜单的内容与结构,而且对其屏幕显示属性也做了相应描述。
二、问题分析
与具体的用户菜单无关是该通用软件的主要特征。通用性要求:
使程序相对于菜单而独立,必须通过某种方式将一个菜单的信息传递给程序,而不同的菜单其内容与功能选项,以及各功能选项所包含的子菜单嵌套层数(以下简称结构)都是不同的,这就要求程序必须能"动态"地处理用户菜单的内容与结构。
1.动态处理菜单
程序要想"动态"地处理不同的用户菜单,则必须在程序中实现变量的"动态定义",以便将数目不定的各功能选项进行所需处理。C语言中变量定义的实质,是为了在编译时能为其分配相应的存储单元。同时,C语言又提供了指针机制,允许使用指针对内存单元进行操作。而且,C语言经编译后,取得并使用四个逻辑上不同、用于不同对象的内存区域,分别是:栈、堆、全局变量区、程序代码区,其中"堆"是一个自由内存区域,C语言可通过内存分配函数(malloc()、calloc()),动态地从中获得所需空间。由指针指向被分配的内存块,使之可作为变量空间使用。当该内存空间无用时,可利用内存释放函数free()释放指针所指向的内存块。
而数据结构中链表的特点是:逻辑上相邻的元素在物理上不一定相邻,数据元素之间的逻辑关系是由结点中的指针指示的,换句话说,指针为数据元素之间的逻辑关系的映像。它是一种动态结构,链表所占用的空间不需预先分配,而是由系统应需求即时生成。
2.用双向循环链表实现菜单功能
下拉弹出式菜单系统系在顶端水平显示包含各主功能选项的菜单条行,用户可以利用$-→键或←键移动并回车选择所需的主功能选项,而被选取的主功能选项将会显示其相关的子功能选项在下拉的弹出式菜单框中。同级菜单的功能选项形成一闭合循环,即:当用户利用↑(或↓)键移动高亮度反白亮条至同级功能选项的顶(或底)部时,再往上(或下)移动,则高亮度反白条将自动返回底(或顶)部。子菜单可在该级任一功能选项上按"q"键返回其父菜单。为了实现这一功能,采用双向式循环链表作为该软件的基本数据结构。将一个窗口下的菜单(仅
有一个)作为一相对独立的双向循环链表,即:同一级的菜单以各功能选项信息的结构体形式为结点,构造成双向循环链表。各菜单之间(即:相对独立的双向循环链表之间)又有父子关系,采用C语言的指针机制使各链表有机地连结成网。基于此,菜单便可"大"可"小",可"深"可"浅"("大":指菜单项目多,反之为"小";"深":指子菜单嵌套层数多)。
3.创建与控制屏幕窗口
与许多操作系统只在汇编语言级向用户提供系统调用的接口不同,UNIX不仅在汇编语言级,而且在程序设计语言C中提供了这种接口。
这给编程者提供了很大方便。笔者充分利用此特点,借助AIX操作系统(V3.2.5)提供的CURSES库完成屏幕窗口的创建与操纵。CURSES库是一组屏幕控制子程序,它允许用 C 程序调用这些子程序去控制终端的输入与输出。由此提供给用户友好的界面。由于CURSES库是一个较为复杂的问题,因此本文不作详细论述,有关这方面的内容,请读者参考相应的书籍。
通过以上分析,笔者想到了一种应用内存分配(指针)和链表相结合来实现变量的动态定义,利用C语言的递归调用最终实现程序通用性的方法。现将该方法与程序提供出来供大家参考。
三、具体方法
1.先根据相应的text文件内容创建双向循环链表网;
2.再用自定义函数menu()实现对该链表网的应用。
详见程序清单和text文件示例。
(1)下拉弹出式菜单程序清单(以W开头的有关窗口函数是CURSES提供的)
# include <curses.h>
# include <string.h>
# include <stdio.h>
# define LEN sizeof(struct link)
struct link
{char str[50],subp[20],prname[20],subtitl[30],mbegin,men
d; int tlin
,tcol,subhigh,subwide,sublin,subcol,lin,col; struct link
*next,*befo,*subm,*pare
m,*paret;WINDOW *pwin,*subwin;
};
struct link *mhead; char tree[40];
main() /*主函数*/
{
struct link *creat(); void menu();char fname[10],tname[
30];
int wi,hi,li,co,tlin,tcol;FILE *fp1;
if ((fp1=fopen("main.txt","r"))==NULL) { exit(0); }
fscanf(fp1,"%s %d %d %s %d %d %d %d %s\n",fname,&tlin,&
tcol,tname,&hi,&wi,&li,
&co,tree);
free(fp1); initscr(); cbreak();
mhead=creat(fname,NULL,NULL);/*创建双向循环链表网*/
menu(mhead,NULL,tlin,tcol,tname,hi,wi,li,co);/*操纵双向
循环链表网*/endwin(); exit(0);
}
/*操纵菜单函数*/
void menu(head,parewin,tline,tcolu,titl,high,wide,line,
colu)
struct link *head; int high,wide,line,colu,tline,tcolu;
char titl[30]; WINDOW *parewin;
{
WINDOW *win,*pw; struct link *p,*stout,*head1;int keyco
de;
win=newwin(high,wide,line,colu); /*创建窗口*/
keypad(win,TRUE); keycode=0; box(win,'*','*');
mvwaddstr(win,tline,tcolu,titl); wrefresh(win);
stout=head1=head; wrefresh(win);
for (p=head1;p->mend!='T';) /*菜单显示*/
{ p->pwin=parewin; p=p->next; }
p->pwin=parewin;
do
{wrefresh(win); p=head1;
for (;p->mend!='T';)
{mvwaddstr(win,p->lin,p->col,p->str); p=p->next; }
mvwaddstr(win,p->lin,p->col,p->str);wrefresh(win);wstand
out(win);
mvwaddstr(win,stout>lin,stout>col,stout>str);
wrefresh(win);keycode=wgetch(win);
switch(keycode) /*接收键值*/
{case KEY-UP:stout=stout>befo;wstandend(win);break;
case KEY-DOWN:stout=stout>next;wstandend(win);break;
case 1861:stout=stout>next;wstandend(win);break;
case 1860:stout=stout>befo;wstandend(win);break;
case 10:
{if (strcmp(stout>prname,"exit")==0) {wstandend(win); e
ndwin(0);exit
(0);}
else
if (strcmp(stout>prname,"null.exe")!=0) {wstandend(wi
n);system(stout>prname);wrefresh(curscr);break;}
if (stout>subm!=NULL) /*递归调用*/
{wstandend(win);
menu(stout>subm,win,stout>tlin,stout>tcol,stout>subt
itl,stout>subhigh,stout>subwide,stout>sublin,stout>subcol);b
reak;
}
else {wstandend(win); break; }
}
case 113 : /*"q"键返回父菜单*/
if (stout>pwin!=NULL)
{wstandend(win); wclear(win); wrefresh(win); free(win)
;win=stout>pwin; touchwin(win); wrefresh(win); keypad(win,TR
UE); head1=stout>parem;wstan dend(win); stout=stout>paret; b
reak;
}
else {wstandend(win); break; }
}
} while (1);
return;
}
/*创建链表函数*/
struct link *creat(submenu,paremenu,pareitem)
char submenu[10]; struct link *paremenu; struct link *pa
reitem;
{
int n; struct link menu1; struct link *head; struct lin
k *p1,*p2;
FILE *fp; char subname[60];
strcpy(subname,NULL); strcpy(subname,tree); strcat(subn
ame,submenu);
if ((fp=fopen(subname,"r"))==NULL) { exit(0); }
head=NULL; n=1;
for (;!feof(fp);)
{fscanf(fp,"%s %d %d %s %d %d %s %d %d %d %d %s\n",menu
1.str,&menu1.lin,&menu1.col,menu1.subp,&menu1.tlin,&menu1.tc
ol,menu1.subtitl,&menu1.subhigh,&menu1.subwide,&menu1.sublin
,&menu1.subcol,menu1.prname);
if (n==1)
{p1=p2=(struct link *)malloc(LEN); head=p1; head>mbegi
n='T'; head>mend='F';
strcpy(head>str,menu1.str); strcpy(head>subp,menu1.su
bp);
strcpy(head>prname,menu1.prname); strcpy(head>subtitl
,menu1.subtitl);
head>lin=menu1.lin; head>col=menu1.col; head>tlin=men
u1.tlin;
head>tcol=menu1.tcol; head>subhigh=menu1.subhigh;
head>
;subwide=menu1.subwide; head>sublin=menu1.sublin;
head>subcol=menu1.subcol; head>pwin=NULL; head>subwin
=NULL;
head>subm=NULL; head>parem=paremenu; head>paret=parei
tem;
if (strcmp(p1->subp,"null.txt")) head>subm=creat(head
>subp,head,p1);
}
else
{p1=(struct link *)malloc(LEN); p2->next=p1; p1->befo=
p2;
p1->mbegin='F'; p1->mend='F'; strcpy(p1->str,menu1.st
r);
strcpy(p1->subp,menu1.subp); strcpy(p1->prname,menu1.
prname);
strcpy(p1->subtitl,menu1.subtitl); p1->lin=menu1.lin;
p1->col=menu1.col; p1->tlin=menu1.tlin; p1->tcol=menu
1.tcol;
p1->subhigh=menu1.subhigh; p1->subwide=menu1.subwide;
p1->sublin=menu1.sublin; p1->subcol=menu1.subcol;
p1->pwin=NULL; p1->subwin=NULL; p1->subm=NULL;
p1->parem=paremenu; p1->paret=pareitem;
if (strcmp(p1->subp,"null.txt")) p1->subm=creat(p1->s
ubp,head,p1);
}
p2=p1; n=n+1;
}
p2->next=head; p2->mbegin='F'; p2->mend='T'; head>befo=
p2; return(head);
}
(2)菜单(text)描述文件举例
main.txt——菜单主描述文件,主要包含以下信息:
主菜单描述文件名、行坐标、列坐标、主标题、主菜单窗口的行数、列数、主菜单窗口起始行坐标、列坐标、菜单描述文件所在目录。
例如:zjsj.txt 0 34 钻井设计菜单 5 78 0 1 menu/
zjsj.txt——菜单具体信息描述文件,主要包含以下信息:
功能选项、行坐标、列坐标、菜单描述文件名、题行坐标、题纵坐标、标题、子菜单窗口的行数、列数、子菜单窗口起始行坐标、列坐标、功能选项对应的可执行文件名。
例如:地质设计 2 6 file.txt 0 5 设计审批及地质设计 6 28 5
1 program.exe
...
打印退出 2 66 null.txt 0 0 打印设计书 8 40 8 18 exit
注:(1)各描述项之间以一个空格分隔;
(2)如没有的字符项应用 null(如:null.txt null.exe null)
代替,数据项用"0"代替
(3)main.txt 文件名可以取其它名字,但必须与主函数中的文件名一致。
四、结束语
该软件具有以下特点。
1.通用性强:该菜单工具适用于任何用户,即使不懂得编程,也能很快地构造出所需的菜单,具编程经验的用户更是易如反掌。
2.易于维护:当用户需对菜单进行修改时,只要对相应的text文件中的有关数据进行维护即可,而程序无需任何改动。
3.使用灵活方便。
笔者已将该工具软件成功应用于RISC/6000AIX V3.2.5 操作系统下的部级课题《钻井数据库分系统及应用》,并且推广到相同环境下的所有应用软件系统中,大大缩短了系统的开发周期,并且具有很强的通用性和可维护性,收到了显著的效果。