尉氏港区2017大好消息:第六章 菜单开发及应用(1)

来源:百度文库 编辑:偶看新闻 时间:2024/04/28 07:24:36
6.1菜单简介
6.1.1菜单概念

在第三章中我们曾经使用curses窗口创建了一个简单的菜单程序,从程序中可以看到菜单实际上是一系列选项的组合,我们一次可以选中一项或者多项,也可以使用方向键进行移动。一旦用户做出了选择,应用程序将做出相应的反应,或者是弹出一个消息框,或者是弹出子菜单等等。但事实上curses包中已经为我们提供了菜单开发库,通过菜单开发库我们可以非常方便,快捷的开发各种菜单。菜单库中菜单的通常形式如图6.1。菜单的每一项通常包含两个部分,左边的为菜单项的名字,用来标识一个菜单项,比如Taurus为当前选中的菜单项的名字,右边的则为该菜单项的描述信息,通常用来补充说明菜单项名称,比如The Bull。图6.1
6.1.2编译和链接菜单程序
为了能够使用菜单函数,我们必须在程序中包含菜单的头文件。
#include 
为了能够在程序编译的时候链接到菜单库,因此我们还必须在编译命令中通过 –l选项指定菜单库,同时必须将curses库链接进去,编译命令如下所示:
gcc [flags ] files –lmenu –lcurses     [gcc编译器,比如Linux]
或者
cc [flags ] files –lmenu –lcurses      [cc编译器,比如ScoUnix和Solaris]
有些编译器对于-lmenu和-lcurses的顺序没有什么特别的限制,但是你最好将-lmenu在-lcurses选项之前使用,因为像SCO Unix这样的系统必须将 –lmenu放在-lcurses之前。这样你在一个平台上写的Makefile可以直接在另一个平台Make。
6.1.3菜单相关数据结构
对于系统中的每一个菜单,系统中都维护了一个MENU数据结构,同时对于每一个菜单项系统中也维护了一个ITEM结构,这两个数据结构都定义在中。Sco Unix和Solaris中MENU的定义如下:
typedef struct MENU {
int height;      /* Number of chars high */
int width;      /* Number of chars wide */
int rows;      /* Number of items high */
int cols;      /* Number of items wide */
int frows;      /* Number of formated items high */
int fcols;      /* Number of formated items wide */
int namelen;      /* 菜单项名称的最长长度 */
int desclen;      /* 菜单项描述信息的最长长度 */
int marklen;      /* 标记符号的长度 */
int itemlen;      /* 菜单项的长度*/
char *pattern;      /* 菜单模式缓冲区 */
int pindex;      /* 模式缓冲区索引 */
WINDOW *win;       /* 包含整个菜单的窗口,即通常的菜单窗口*/
WINDOW *sub;       /* 菜单子窗口,即菜单的显示部分*/
WINDOW *userwin;     /* 用户窗口 */
WINDOW *usersub;     /* 用户子窗口 */
ITEM **items;         /* 菜单的关联菜单项*/
int  nitems;      /* 菜单中的菜单项总数 */
ITEM *curitem;     /* 菜单中当前菜单项 */
int toprow;      /* 菜单的顶行索引 */
int pad;      /* 菜单的填充字符 */
chtype fore; /* 菜单项选中时候的属性信息 */
chtype back; /* 菜单项未选中时候的属性信息 */
chtype grey; /* 菜单项处于非激活状态的属性信息 */
PTF_void menuinit;    /*菜单初始化和结束时候的关联操作函数 */
PTF_void menuterm;
PTF_void iteminit;    /*菜单项初始化和结束时候的关联操作函数*/
PTF_void itemterm;
char *userptr;    /*菜单项用户指针*/
char *mark;
OPTIONS opt;
int      status;      /*菜单状态 */
} MENU;
其中菜单项定义ITEM结构如下:
typedef struct ITEM {
TEXT name;            /* 菜单项名称*/
TEXT description;   /* 菜单项描述信息*/
int index;         /* 当前菜单项在菜单中的索引号 */
struct MENU *imenu;    /* 菜单项所属菜单指针 */
int  value;              /*菜单项是否选中*/
char *userptr;          /*菜单项用户指针*/
OPTIONS  opt;               /*菜单项选项 */
int  status;                /*菜单项状态*/
short  y;            /* 菜单项在菜单中的y和x的位置 */
short  x;
struct ITEM  *left;            /*left,right,up,down分别是菜单的相邻*/
struct ITEM  *right;           /*菜单项*/
struct ITEM  *up;
struct ITEM  *down;
} ITEM;
ITEM结构中的name,description为TEXT结构,它的定义如下:
typedef struct {
char *str;
int length;
} TEXT
Liunux的最新版本ncurses中定义与之没有什么明显的区别。
6.2程序中使用菜单
6.2.1菜单处理过程
为了使用菜单,你首先必须创建菜单项,然后将菜单项与菜单关联起来,接着我们就可以在系统中登记菜单。所谓登记就是将菜单输出到它的关联窗口上。每一个菜单都有两个窗口与之关联:菜单主窗口和菜单子窗口。在菜单主窗口中我们主要输出一些标题、边框等等,菜单子窗口中则显示菜单项。这一切结束以后你就可以通过菜单驱动menu_driver()接受用户输入,返回用户处理结果。一般的情况下,一个菜单应用程序会遵循下面的几个步骤:
■ 创建菜单项
在创建菜单之前我们必须为菜单创建必须的菜单项,而且菜单创建的时候菜单项必须已经存在。
■ 创建菜单
创建菜单,同时将刚才创建的菜单项与它进行关联。
■ 在系统中登记菜单
一旦菜单创建之后我们可以在系统中进行登记从而才可以显示菜单。
■ 刷新屏幕,显示菜单
■ 通过menu_driver()接受终端用户的各种请求并进行相应的处理
■ 清除菜单
■ 释放菜单所占用空间
■ 释放菜单项所占用空间
下面我们给出一个简单的菜单程序6-1,它演示了菜单的创建和销毁过程,在阅读程序的时候,可能一时不能完全接受,我们会在后面进行详细的探讨。程序中菜单中还不能接受用户请求,我们会在后面逐渐完善它。
程序6-1 简单菜单示例程序
程序名称 menu.c
编译命令 cc –o menu  menu.c –lcurses
程序使用的菜单库函数:
new_item(),new_menu(),post_menu(),unpost_menu(),free_item(),
free_menu()
#include 
#include 
char* colors[13]=
{
“Black”,”Charcoal”,”Light Gray”,”Brown”,”Camel”,
“Navy”,”Light Blue”,”Hunter Green”,”Gold”,
“Burgundy”,”Rust”,”White”,(char*)0
};
ITEM *items[13];
main()
{
MENU * m;
ITEM ** i=items;
char ** c=colors;
initscr();
nonl();
raw();
noecho();
wclear(stdscr);
while(*c)
*i++=new_item( *c++,””);
*i=(ITEM *)0;
m=new_menu(i=items);
post_menu(m);
refresh();
sleep(5);
unpost_menu(m);
refresh();
free_menu(m);
while(*i)
free_item(*i++);
endwin();
exit(0);
}

程序运行结果如图6.2:图6.2
6.2.2程序解析
在上面的这个菜单中我们不能进行任何输入,因为我们并没有对各种键盘事件进行响应,它仅是一个菜单的显示,而且没有装饰边框,非常难看。菜单在显示5秒中之后自动的清除,同时程序退出。下面我们简要的对这个程序进行介绍,在后面的部分,我们会对菜单的有关函数进行详细的讲解。
首先是头文件。每一个程序如果想使用菜单,就必须在文件中包含头文件。
接下来我们定义了一个包含十三个颜色元素的字符串数组,用来表示菜单项的名称,需要注意的一点是这个颜色数组的最后一个元素必须为null,为什么会这样,我们后面会解释。接着我们进入主程序,一开始首先进行了一些curses库的初始化操作,然后我们进入了循环while之中。在while循环中,我们使用new_item()为菜单创建菜单项。
一旦菜单项创建,现在就可以创建菜单了,我们使用函数new_menu()创建菜单,菜单项指针作为参数传递给该函数,这样菜单项就与菜单进行了关联。一切进行完毕之后使用post_menu()将菜单登记到菜单窗口上,默认的菜单窗口是标准屏幕。屏幕进行刷新后,菜单就显示出来。sleep()命令使得菜单保持5秒钟。5秒后程序将清除菜单。为了清除,必须首先通知菜单窗口取消刚才登记的菜单,即调用函数unpost_menu(),只有菜单从菜单窗口中取消登记之后才可以进行清除。清除之后调用refresh()进行屏幕刷新,菜单就从标准屏幕上消失了。尽管如此,菜单在系统中所占用的资源仍然存在,因此我们还必须释放菜单所占用的空间。在释放菜单之前,我们必须使用函数free_menu()断开菜单项与菜单的关联关系同时释放菜单空间。最后一个while循环中使用free_item()来依次释放每一个菜单项的空间。这样,我们所分配的空间才算回收完毕,否则会发生内存泄漏。
最后,函数endwin()和exit()中断curses和程序的运行。
上面的部分我们对创建菜单以及释放菜单的整个过程有所了解。下面我们会更详细的讲解菜单操作的各个方面。这一章中我们在每个函数讲解结束后会用程序来演示这些函数的具体用法。
6.3操作菜单项
菜单项是菜单的组成部分,而且创建菜单之前必须创建菜单项,因此在使用菜单之前必须先了解菜单项的操作。
6.3.1创建和释放菜单项
在程序6.1中我们已经用到了创建和释放菜单项的函数。为了创建菜单项,使用函数new_item(),其语法如表6.1所示。
表6.1 菜单项创建函数概述
头文件
curses.h   menu.h
概述
ITEM * new_item(name,description)
char *name;
char *description;
参数name是菜单项的名称,用来标识一个菜单项,但不是唯一标识。不同的菜单项的名称可以重复,同时name字符串必须为可打印字符串,否则会返回E_BAD_ARGUMENT错误码。当菜单登记的时候,菜单显示字符串name,以表示当前的菜单项。另外菜单项名称name还会使用到模式匹配操作中,关于模式匹配的详细介绍请参考6.5.7。
有的时候我们需要额外的备注信息与某个菜单项关联,那么参数description可以起到这样的作用。它是一个与菜单项关联的描述性的字符串。它的显示与否由O_SHOWDESC选项决定。你可以通过set_menu_opts()以及其余的一些相关函数设置或者关闭它。如果O_SHOWDESC选项设置,则显示描述信息。但对菜单项来说描述信息并不是必须的,它可以为NULL,这就意味着没有任何描述信息与该菜单项关联。
如果这一切顺利的话,函数new_item()会返回一个指向新的菜单项的指针。通过这个指针,你可以更改,记录以及检查菜单项的属性信息。不过如果没有足够的内存来分配给菜单项或者菜单名称name为NULL的话,new_item()返回NULL,同时会设置errno,错误码如表6.2所示。
表6.2 new_item()出错信息
返回errno           错误码                            错误信息
-1               E_SYSTEM_ERROR                内存分配错误或者
-2               E_BAD_ARGUMENT               菜单项名称为NULL或者菜单项名称为不可打
印字符
通常情况下我们可以使用一个数组来保存new_item()返回的菜单项指针。下面的例子演示了这种用法。
ITEM * planets[10];
planets[0] = new_item(“mercury”,”the first planet”);
planets[1] = new_item(“venus” , “the second planet”);
planets[2] = new_item(“earth”,”the third planet”);
planets[3] = new_item(“mars”,”the forth planet”);
planets[4] = new_item(“jupiter”,”the fifth planet”);
planets[5] = new_item(“saturn” ,”the sixth planet”);
planets[6] = new_item(“uranus”,”the seventh planet”);
planets[7] = new_item(“neptune”,”the eighth planet”);
planets[8] = new_item(“pluto”,”the ninth planet”);
planets[9] = (ITEM*)0;
从程序6-1以及上面的数组中可以看出关联到菜单的菜单项数组的最后一个总是为(char*)0或者(ITEM*)0,事实上这也是必须的。菜单在关联菜单项的时候通过判断当前的菜单项为NULL从而来确定已经到达最后一个菜单项,因此即使NULL菜单项后面仍然有非NULL菜单项,菜单也不进行关联。这一点可以从下面的例子证实。
我们将程序6-1中的colors[13]定义更改为如下:
char* colors[13]=
{
“Black”,”Charcoal”,”Light Gray”,”Brown”,”Camel”,
“Navy”,”Light Blue”,”Hunter Green”,”Gold”,
“Burgundy”, (char*)0, ”White”, ”Rust”
};

程序运行结果则如下图6.3,从图中可以看出(char*)0之后的white和rust菜单项确实没有关联到菜单中。图6.3
函数new_item()并不对菜单项名称或者描述字符串进行拷贝,它只是保存指向它们的指针。因此一旦你调用new_item()创建了一个菜单项,你不可能再更改这个菜单项名称除非你通过free_item() 释放后再重新创建。
表6.3菜单项释放函数概述
头文件
curses.h   menu.h
概述
int free_item(item);
ITEM *item;
返回
成功
失败
E_OK
返回错误码
函数free_item()释放一个菜单项,但由于它仅保存菜单项名称和描述信息的指针,因此它并不释放名称和描述信息所占的空间。为了释放一个菜单项,你必须首先确信已经通过new_item()创建了它们,而且它们没有跟任何菜单关联。如果这些条件都不能满足的话,函数调用会返回如表6.4所示的错误码。
表6.4 free_item()出错信息
返回值           错误码                            错误信息
-1               E_SYSTEM_ERROR                系统错误
-2               E_BAD_ARGUMENT               不存在的菜单项,即没有通过new_item()创建或
者已经通过free_item()进行释放
-3               E_CONNECTED                    菜单项正与菜单关联,不能释放
6.3.2获取菜单项的名称和描述
在创建菜单项的时候我们将菜单项的名称以及描述信息作为参数传递进去,反之如果我们需要获取给定菜单项的名称和描述的话可以使用item_name()和item_description()。它们的参数都是菜单项的指针。函数语法如表6.5所示。
表6.5 获取菜单项名称和描述信息函数概述
头文件
curses.h   menu.h
概述
char * item_name(item)
ITEM *item;
char *item_description(item)
ITEM *item;
如果给定的菜单项的指针为NULL或者是获取一个不存在的菜单项的信息,两个函数都返回NULL 。如果菜单项的描述信息为NULL,则item_description()返回NULL。
6.3.3操作当前菜单项
当前菜单项就是你在屏幕上光标定位处的菜单项,除非它不可见,否则一般它将会高度加亮,同时光标停留在菜单项上。为了能够让你的应用程序设置或者确定当前的菜单项,你可以使用如表6.6所示的函数:
表6.6 获取当前菜单项函数概述
头文件
curses.h   menu.h
概述
概述
int set_current_item(menu,item)
MENU *menu;
ITEM *item;
int  item_index(item);
ITEM *item;
ITEM *current_item(menu)
MENU *menu;
函数set_current_item()允许你将菜单项item的指针传递给它,来设置当前项。在设置之前,函数将检查item是否已经与菜单关联,如果没有关联则返回E_NOT_CONNECTED错误码。如果存在关联则检查菜单的调用状态,如果菜单是在初始化或者中断结束函数中调用则返回E_BAD_STATE错误。set_curren_item()的错误码如表6.7所示。
表6.7 set_current_item()出错信息
返回值           错误码                            错误信息
-1               E_SYSTEM_ERROR                系统错误
-2               E_BAD_ARGUMENT               菜单项不存在或者菜单项没有与菜单关联
-5               E_BAD_STATE                    不能在初始化以及中断函数中调用该函数
-11              E_NOT_CONNECTED              菜单项与菜单没有关联
current_item()返回当前的菜单项,如果菜单为NULL或者菜单没有关联菜单项,则函数返回(ITEM*)0。
在很多情况下菜单能够自动的响应一些浏览请求,比如你按下’↓’键后,菜单系统将自动的将当前菜单项向下移动一个位置,因此你的应用程序不必再调用set_current_item()来手工设置当前菜单项。除非你想在程序中实现一些特殊的菜单项浏览请求,比如,你可能需要从当前的菜单项直接跳到非相邻的菜单项。这种请求系统默认定义下是实现不了的,这时候你可以使用set_current_item()来进行设置。
下面的代码演示了set_current_item()函数的用法。它将菜单menu的第一个菜单项设置为当前菜单项:
int set_first_item(menu)
MENU *menu
{
/*menu_items()用来获取与菜单关联的菜单项*/
ITEM ** item = menu_items(menu);
return set_current_item(menu,item[0]);
}
一旦通过new_menu()创建了一个菜单或者与菜单关联的菜单项发生了改变,菜单的第一个菜单项总是被设置成当前菜单项。大多数情况下,当前菜单项不一定是第一个菜单项,这时我们可以通过current_item()获取当前的菜单项。
下面的代码演示了current_item()的用法,它将检查菜单的第一个菜单项是否是当前菜单项。
int first_item(menu)
MENU *menu;
{
/*获取菜单的当前菜单项*/
ITEM *item=current_item(menu);
return item_index(item)==0;
}
如果current_item()的参数为NULL指针或者没有任何菜单项与菜单关联,那么函数将返回(ITEM*)0。
在上面的代码片断中我们使用了item_index(),这个函数返回当前选中菜单项的索引号,索引从0到N-1,N的值是当前与菜单关联的菜单项的总数。如果当前菜单项的索引为零则表示当前菜单项为第一个菜单项。如果菜单项指针为NULL或者没有任何菜单项与菜单关联,那么函数item_index()返回-1。
6.3.4菜单项选项属性
菜单项选项实际上是一些可以打开或者关闭的属性,它们通常都是布尔类型的值。当前的curses版本只提供了一个选项: O_SELECTABLE。通过设置这个选项,用户可以允许或者禁止选择某一菜单项。默认情况下,每一个菜单项的O_SELECTABLE选项都是设置的,即每个菜单项都是可选的。函数set_item_opts()可以用来设置和关闭这个选项属性,而item_opts()让你返回给定菜单项的选项属性。函数语法如表6.8所示。
表6.8菜单项选项属性函数概述
头文件
curses.h   menu.h
概述
int set_item_opts(item,opts)
ITEM *item;
OPTIONS opts;
OPTIONS item_opts(item)
ITEM *item;
函数set_item_opts()如果执行成功,返回E_OK,否则,返回如表6.9所示的错误码。
表6.9 set_item_opts()出错信息
返回值           错误码                            错误信息
-1               E_SYSTEM_ERROR                系统错误
-2               E_BAD_ARGUMENT               错误的菜单选项
对于item_opts()如果执行成功将返回OPTIONS类型的选项属性,OPTIONS实际上也是int类型的。
如果set_item_opts()的参数item是一个NULL选项指针,菜单项将opts设置成所有菜单项该属性的默认值,同样如果item_opts()的参数item是一个NULL指针,那将返回当前菜单项选项的默认值。
一旦你关闭O_SELECTABLE,菜单项则不能被选中。不可选择的菜单项显示的时候呈现灰色状态,具体的在”设置菜单显示属性”一节中描述。如果你需要将菜单项item0设置成不可选,而将菜单项item1设置成可选,则代码如下:
ITEM *item0 ,  *item1;
set_item_opts(item0,item_opts(item0)&~O_SELECTABLE);
set_item_opts(item1,item_opts(item1)|O_SELECTABLE);
除了上面的两个函数可以用来设置菜单项的选项属性之外,curses中还提供了函数item_opts_on()和item_opts_off()来设置这些属性。相比于上set_item_opts()函数而言,这两个函数更加简洁明了,也更易用。但它们实际上也是内部调用了set_item_opts()函数。函数语法如表6.10所示。
表6.10 菜单项选项属性函数概述
头文件
curses.h   menu.h
概述
int item_opts_on(item,opts)
ITEM *item;
OPTIONS opts;
int item_opts_off(item,opts)
ITEM *item;
OPTIONS opts;
返回
成功
失败
E_OK
返回错误码E_SYSTEM_ERROR
例如,下面的例子可以完成跟上面的代码一样的功能。
ITEM *item0, *item1;
item_opts_off(item0,O_SELECTABLE);
item_opts_on (item1,O_SELECTABLE);
如果需要改变当前菜单项的默认值为不可选择,则你将NULL菜单项传递给这两个函数:
set_item_opts((ITEM*)0,item_opts((ITEM*)0) &~O_SELECTABLE);
item_opts_off((ITEM*)0,O_SELECTABLE);
下面给出的一个完整的程序6-2来演示菜单项选项属性的用法,在使用中,有两个菜单项是不可选的。这个例子同时演示了set_current_item()和current_item()函数的用法,通过‘a’键我们总是可以将菜单的第二项设置为当前菜单项。
程序6-2 菜单项选项属性使用示例
程序名称 menu_attr.c
编译命令 cc –o menu_attr  menu_attr.c –lcurses
程序使用的菜单库函数:
new_item(),new_menu(),post_menu(),unpost_menu(),free_item(),
free_menu(),item_opts_off(),menu_driver(),set_current_item(),
menu_items(),pos_menu_cursor()
#include 
#include 
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD     4
char *choices[] = {
"Black",
"Charcoal",
"Light Gray",
"Brown","Camel",
"Navy",
"Light Blue",
"Hunter Green",
"Gold",
"Burgundy",
"Rust",
"White",
(char*)0
};
int main()
{
ITEM **my_items;
int c;
MENU *my_menu;
int n_choices, i;
ITEM *cur_item;
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
n_choices = ARRAY_SIZE(choices);
my_items = (ITEM **)calloc(n_choices + 1, sizeof(ITEM *));
for(i = 0; i < n_choices; ++i)
my_items[i] = new_item(choices[i], choices[i]);
my_items[n_choices] = (ITEM *)NULL;
item_opts_off(my_items[3], O_SELECTABLE);
item_opts_off(my_items[6], O_SELECTABLE);
my_menu = new_menu((ITEM **)my_items);
mvprintw(LINES - 3, 0, "回车键查看选中菜单项");
mvprintw(LINES - 2, 0, "方向键进行菜单浏览 ,’a’键将菜单第二项设置为当前项 (F1 退出)");
post_menu(my_menu);
refresh();
while((c = getch()) != KEY_F(1))
{   switch(c)
{   case KEY_DOWN:
menu_driver(my_menu, REQ_DOWN_ITEM);
break;
case KEY_UP:
menu_driver(my_menu, REQ_UP_ITEM);
break;
case 10: /* Enter */
move(20, 0);
clrtoeol();
mvprintw(20, 0, "Item selected is : %s",
item_name(current_item(my_menu)));
pos_menu_cursor(my_menu);
break;
case 'a':
my_items=menu_items(my_menu);
set_current_item(my_menu,my_items[1]);
}
}
unpost_menu(my_menu);
for(i = 0; i < n_choices; ++i)
free_item(my_items[i]);
free_menu(my_menu);
endwin();
}
程序中我们使用了menu_driver()函数,它是整个菜单与用户进行交互处理的核心处理函数,本书简称为菜单驱动。关于菜单驱动的细节我们在后面部分会进行讲解。
6.3.5单选菜单与多选菜单
首先了解一下单选菜单和多选菜单的概念。
单选菜单       在单选菜单中我们一次只能选择一个菜单项
多选菜单       在多选菜单中我们一次可以选择一个或者多个菜单项
默认情况下,每一个菜单都是单选菜单。在菜单中我们通过O_ONEVALUE选项来设定菜单是单选还是多选。如果该选项打开表示为单选,否则为多选,因此为了能够创建一个多选菜单,你首先必须关闭O_ONEVALUE选项。这通常可以通过函数set_menu_opts()或者menu_opts_off()实现。
不管单选菜单还是多选菜单总会有当前菜单项。单选菜单中我们不需要借助任何额外手段就可以确定当前菜单项,但是在多选菜单中,我们必须进行一些小小的变通。为了能够确定当前选中的所有的菜单项,我们必须对每一个菜单项使用item_value()函数进行检查,如果该菜单项没有选中,则返回FALSE,否则返回TRUE。
大部分的菜单函数既对单选菜单有效同时也对多选菜单有效,只有函数set_item_value()和item_value()例外,它只能用于多选菜单。通过set_item_value()可以设置一个菜单项的选中状态,而且item_value()函数可以返回当前菜单项的选中状态,如果选中返回TRUE,否则返回FALSE。
表6.11 菜单选中函数概述
头文件
curses.h   menu.h
概述
int set_item_value(item,value)
ITEM *item;
int value;
int item_value(item)
ITEM *item;
对于set_item_value()函数如果菜单已经登记到窗口中,则菜单将重新显示。如果给定的菜单项是不可选择的(O_SELECTABLE选项被关闭)或者菜单项关联到一个单选菜单上,则set_item_value()函数返回错误,错误码如表6.12所示。
表6.12 set_item_value()出错信息
返回值           错误码                            错误信息
-1               E_SYSTEM_ERROR                系统错误
-12              E_REQUEST_DENIED              菜单项不可选或者菜单是单选
下面的代码演示set_item_value()和item_value()的用法,你在程序中可以直接使用它们。函数process_menu()用来确定你选择的菜单项,并且适当的进行处理。
void process_menu(m)
MENU *m;
{
//获取与当前菜单关联的菜单项数组
item **i=menu_items(m);
//不停的进行循环查找,如果还有菜单项存在则判断它是否选中
while(*i)
{
if(item_value(*i))
{
//如果该菜单项选中的话,将该菜单项取消选中,这儿你可以更改
//为你自己的代码。
set_item_value(*i,false);
}
++i;
}
}
下面的是一个完整的应用程序,它演示了多选菜单的用法,其中用到了上面的代码。程序中我们可以通过空格键来选定或者取消选定一个菜单项,一旦按下回车键后我们可以打印出所有的选中的菜单项的名称。
程序6-3 多选菜单使用示例程序
程序名称 menu_mvalue.c
编译命令 cc –o menu_mvalue menu_mvalue.c –lmenu –lcurses
程序使用的菜单库函数:
new_item(),new_menu(),post_menu(),unpost_menu(),free_item(),
free_menu(),menu_driver(),set_current_item(),current_item(),
menu_items(),set_item_value(),item_value()
#include 
#include 
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD     4
char *choices[] = {
"Black",
"Charcoal",
"Light Gray",
"Brown",
"Camel",
"Navy",
"Light Blue",
"Burgundy",
"Rust",
"White",
};
int main()
{   ITEM **my_items;
int c;
MENU *my_menu;
int n_choices, i;
ITEM *cur_item;
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
n_choices = ARRAY_SIZE(choices);
my_items = (ITEM **)calloc(n_choices + 1, sizeof(ITEM *));
for(i = 0; i < n_choices; ++i)
my_items[i] = new_item(choices[i], choices[i]);
my_items[n_choices] = (ITEM *)NULL;
my_menu = new_menu((ITEM **)my_items);
/* 将菜单设置成多选菜单 */
menu_opts_off(my_menu, O_ONEVALUE);
mvprintw(LINES - 6, 0, "请使用空格键选择或取消选择菜单项.");
mvprintw(LINES - 5, 0, "回车键查看选中菜单项(F1退出)");
post_menu(my_menu);
refresh();
while((c = getch()) != KEY_F(1))
{   switch(c)
{   case KEY_DOWN:
menu_driver(my_menu, REQ_DOWN_ITEM);
break;
case KEY_UP:
menu_driver(my_menu, REQ_UP_ITEM);
break;
case ' ':
/*获取当前菜单*/
cur_item=current_item(my_menu);
/*如果当前菜单已经选中则取消选中*/
if(item_value(cur_item)==TRUE)
set_item_value(cur_item,FALSE);
/*否则如果没有选中,则将它设为选中*/
else
set_item_value(cur_item,TRUE);
case 10:    /* Enter */
/*打印所有选中的菜单项名称*/
{   char temp[200];
ITEM **items;
items = menu_items(my_menu);
temp[0] = '/0';
for(i = 0; i < item_count(my_menu); ++i)
if(item_value(items[i]) == TRUE)
{    strcat(temp, item_name(items[i]));
strcat(temp, " ");
}
move(20, 0);
clrtoeol();
mvprintw(20, 0, temp);
refresh();
}
break;
}
}
unpost_menu(my_menu);
for(i = 0; i < n_choices; ++i)
free_item(my_items[i]);
free_menu(my_menu);
endwin();
}

程序运行界面如表6.4所示。图6.4
6.3.6 检查菜单项是否可见
每一个菜单总是与两个窗口关联的,一个是菜单主窗口,一个是菜单子窗口。通常菜单项是在子窗口中显示(关于菜单窗口的详细介绍在后面部分会有详细介绍)。有的时候菜单子窗口不能一次显示所有的菜单项,这时可能有部分菜单项被隐藏。如果一个菜单项出现在与它关联的菜单子窗口上,那么它就是可见的,否则是不可见的。函数item_visible()可以让你的程序来判断当前的菜单项是否可见。不可见的菜单项我们可以通过滚动将它显示出来。
表6.13 菜单可见性函数概述
头文件
curses.h   menu.h
概述
int item_visible(item)
ITEM *item;
返回
可见
不可见
TRUE
FALSE
如果菜单项不为空,并且关联菜单已经登记,同时出现在关联菜单的子窗口上,这时候item_visible()返回TRUE,否则返回FALSE。
例如如果我们需要判断菜单的第一个菜单项目前是否可见,可以用下面的代码:
int at_top(m)
MENU *m;
{
ITEM **i=menu_items(m);
ITEM *firstitem = i[0];
return item_visible(firstitem);
}
6.3.7操作顶端菜单项
正如6.3.5中所说,有的时候菜单子窗口中不一定能够显示所有的菜单项,这种情况下就必须进行菜单滚动。滚动中,菜单子窗口中最顶端的菜单项会发生变化,有的时候我们希望能够获取最顶端的菜单项,菜单库中提供了top_row()和set_top_row()来操作顶端菜单项,top_row()获取当前菜单中最上面的菜单的索引,索引号从0开始。set_top_row()将指定的菜单项设定为顶端菜单。函数语法如表6.14所示。
表6.14 顶端菜单项操作函数概述
头文件
curses.h   menu.h
概述
int set_top_row(menu,row)
MENU *menu;
int row;
int top_row(menu)
MENU *menu;
由于curses中的菜单支持多列(关于多列菜单在6.4.4中详细描述),因此顶端菜单项可能包括二列以及二列以上。这时候set_top_row()将把指定的菜单项设置为该行中最左边的位置。同时参数row必须在TR到VR的范围内,TR是菜单的总的行数,VR是可见菜单项的行数。如果row的值太大的话,从row开始后面的所有的菜单项就不能填满菜单子窗口,如果这样的话,顶端菜单项保持不变。
如果我们使用new_menu()创建新菜单或者使用set_menu_items()更改了菜单的关联菜单项,顶端菜单项将默认设置成索引为0的菜单项。另外如果菜单的排列方式或者O_ROWMAJOR选项发生了改变,顶端菜单项也将默认设置成索引为0的菜单项。
如果set_top_row()设置成功,返回E_OK,否则返回如表6.15所示的错误码:
表6.15 set_top_row()出错信息
返回值           错误码                            错误信息
-1               E_SYSTEM_ERROR                系统错误
-12              E_BAD_ARGUMENT               菜单指针为NULL或者row超出了范围
-5               E_BAD_STATE                    不能在初始化和中断结束函数中调用
-11              E_NOT_CONNECTED              没有关联菜单项
下面给出一个set_top_row()和top_row()使用的例子,在示例中我们可以通过方向键浏览菜单,同时打印当前顶端菜单项的索引,按下‘s’则将索引为10的菜单项设置为顶端菜单项。
程序6-4 set_top_row()和top_row()使用示例程序
程序名称 menu_toprow.c
编译命令 cc –o menu_toprow menu_toprow.c –lmenu –lcurses
程序使用的菜单库函数:
new_item(),new_menu(),post_menu(),unpost_menu(),free_item(),
free_menu(),menu_driver(),menu_items(),set_top_row(),top_row()
#include 
#include 
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD     4
char *choices[] = {
"0   Cyan",
"1   Black",
"2   Charcoal",
"3   Light Gray",
"4   Brown",
"5   Camel",
"6   Navy",
"7   Light Blue",
"8   Burgundy",
"9   Rust",
"10  White",
"11  Pin",
"12  Pea green",
"13  Purple",
"14  Coffee",
"15  Scarlet",
"16  Orange color",
"17  Yellow",
(char *)NULL
};
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string);
int main()
{
ITEM **my_items;
int c;
MENU *my_menu;
WINDOW *my_menu_win;
int n_choices, i;
int top;
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
n_choices = ARRAY_SIZE(choices);
my_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));
for(i = 0; i < n_choices; ++i)
my_items[i] = new_item(choices[i], choices[i]);
my_menu = new_menu((ITEM **)my_items);
my_menu_win = newwin(10, 30, 4, 4);
keypad(my_menu_win, TRUE);
set_menu_win(my_menu, my_menu_win);
set_menu_sub(my_menu, derwin(my_menu_win, 7, 28, 3, 1));
set_menu_format(my_menu, 5, 1);
menu_opts_off(my_menu,O_SHOWDESC);
box(my_menu_win, 0, 0);
print_in_middle(my_menu_win, 1, 0, 30, "top_row()示例函数");
mvwaddch(my_menu_win, 2, 0, ACS_LTEE);
mvwhline(my_menu_win, 2, 1, ACS_HLINE, 28);
mvwaddch(my_menu_win, 2, 29, ACS_RTEE);
post_menu(my_menu);
wrefresh(my_menu_win);
mvprintw(LINES - 8, 4, "使用方向键浏览菜单");
mvprintw(LINES - 7, 4, "使用's'设置当前顶行索引(F1退出)");
refresh();
while((c = wgetch(my_menu_win)) != KEY_F(1))
{   switch(c)
{
case KEY_DOWN:
case 'j':
menu_driver(my_menu, REQ_DOWN_ITEM);
top=top_row(my_menu);
mvprintw(LINES-10,4,"当前顶行索引:%d",top);
refresh();
break;
case KEY_UP:
case 'k':
menu_driver(my_menu, REQ_UP_ITEM);
top=top_row(my_menu);
mvprintw(LINES-10,4,"当前顶行索引:%d",top);
refresh();
break;
case 's':    /*设置索引10的为顶层菜单*/
set_top_row(my_menu,10);
top=top_row(my_menu);
mvprintw(LINES-10,4,"当前顶行索引:%d",top);
refresh();
break;
}
wrefresh(my_menu_win);
}
/* Unpost and free all the memory taken up */
unpost_menu(my_menu);
free_menu(my_menu);
for(i = 0; i < n_choices; ++i)
free_item(my_items[i]);
endwin();
}
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string)
{   int length, x, y;
float temp;
if(win == NULL)
win = stdscr;
getyx(win, y, x);
if(startx != 0)
x = startx;
if(starty != 0)
y = starty;
if(width == 0)
width = 80;
length = strlen(string);
temp = (width - length)/ 2;
x = startx + (int)temp;
mvwprintw(win, y, x, "%s", string);
refresh();

}程序运行结果如图6.5所示。
图6.5
6.3.8统计菜单项总数
有的时候你可能需要进行一些处理,它们的结果决定于与菜单关联的菜单项的数目。函数item_count()就可以返回与菜单关联的菜单项的数目。函数语法如表6.16所示。
表6.16 菜单项统计函数概述
头文件
curses.h   menu.h
概述
int item_count(memu)
MENU *menu;
如果参数menu为NULL,函数item_count返回-1。下面的代码演示了如何使用这个函数。由于菜单的最后一个菜单项的索引号比总的选项数目小一。这个函数判断最后一个菜单项是否可见。
int at_bottom(m)
MENU *m;
{
ITEM ** i=menu_items(m);
ITEM * lastitem = i[item_count(m)-1];
return item_visible(lastitem);
}
6.3.9设置菜单项用户指针
有的时候菜单项除了描述信息以及名称之外还需要额外的附加信息,那么如何在菜单项中增加额外信息呢。菜单库中使用用户指针来实现这一点,从ITEM结构中可以看出。对于每一个创建的菜单项 ,菜单库都会自动的给它们分配一个用户指针userptr,指向给定的数据结构,这样通过用户指针菜单项就能够跟任何数据关联起来。默认情况下,用户指针的值为NULL,即没有任何的附加信息与之关联。如果需要,这个值必须由你自己来设置,你可以关联任何想关联的数据。函数语法如表6.17所示。
表6.17 菜单项用户指针函数概述
头文件
curses.h   menu.h
概述
int set_item_userptr(item,userptr)
ITEM  *item;
char  *userptr;
char  *item_user_ptr(item)
ITEM  *item;
使用菜单项的用户指针,任何自定义的数据结构都可以跟菜单项关联起来。需要注意的是设置这个指针的时候首先必须强制转化为(char*)类型,使用的时候需要再次强制转换为自定义的结构类型。下面的程序6-5演示了如何使用菜单项的用户指针关联到一个ITEM_ID的结构上。这个结构用来存储一些动植物信息。
代码6-5 菜单项用户指针使用示例程序
typedef struct
{
int id;
char *name;
char *type;
}ITEM_ID;
ITEM_ID ids[7]=
{
1,”apple” ,”fruit”,
2,”ant”  ,”insect”,
3,”cow” ,”mammal”,
4,”lizard”,”reptile”,
5,”patato”,”vegetable”,
6,”zebra”,”mammal”,
0,””,””,
};
ITEM *items[7];
for(i=0;ids[i];++i)
{
/*从每一个ids.name中创建item,描述信息为空*/
items[i]=new_item(ids[i].name,””);
/*设置用户指针*/
set_item_userptr(items[i],(char*)&ids[i]);
}
items[i]=(ITEM *)0;
从上面可以看出,ids数组的每个入口的指针都被强制转换为char*类型的。这是set_item_userptr() 所要求的,它只能处理字符指针类型的数据。接着如果需要使用这个用户指针数据,那么你还必须再将这个字符指针强制转换为自定义数据类型。代码示例如下:
char *get_type(i)
ITEM *i
{
ITEM_ID  *id = (ITEM_ID *)item_userptr(i);
return id->type;
}
上面的例子中,由item_userptr()返回的值又被通过(ITEM_ID *)item_userptr(i)强制转换为原来的类型(ITEM_ID *).最后,你可以调用get_type()输出item的类型,代码如下:
WINDOW *win;
waddstr(win,get_type(i));
如果顺利的话,函数返回E_OK,否则返回错误码E_SYSTEM_ERROR
如果函数set_item_userptr()的参数是一个NULL值,那么userptr参数将成为以后新创建的菜单项的用户指针的默认值。例如,下面的代码将新的用户指针设置成为字符”You are Here”:
set_item_userptr((ITEM *) 0,”You are Here”);