回 目 录
上 一 章 下 一 章

# 第十七章：菜单库

  菜单库（Menu）很好的扩展了cureses基础库。你可以通过这个库所提供的函数方便的创建菜单。同时你一定会定制它的显示效果，让它看起来更美观。那么，我们来看看这个库。

  > 菜单就是一个显示屏幕，里面包含着帮助用户选择的特定子菜单项。简而言之，菜单就是一个菜单项的集合，使你可以方便的从中选择相应的菜单命令。有些读者还不了解多选菜单的强大功能。菜单库提供编制多选菜单的功能。这个我们稍后讨论，现在我们了解一下菜单库的基础知识。

### 17.1 基础知识

  要创建菜单，首先要建立菜单项，之然后通过菜单把菜单项显示出来。接下来，就由功能强大的menu_driver()函数来处理用户的操作。对于处理包含菜单的程序来说，这个函数是一个非常强大的工具。

一个菜单程序的控制流程大致如下：

    1．	初始化curses
    2．	用函数new_item()创建菜单项。同时要为菜单项指定名称用来描述相应功能。
    3．	用函数new_menu()创建菜单，同时指定要添加的菜单项。
    4．	用函数menu_post()递送菜单并刷新屏幕
    5．	用一个循环处理用户的菜单请求。并用menu_driver()函数对菜单做必要的更新。
    6．	用menu_unpost()取消菜单递送。
    7．	用free_menu()释放分配给菜单的内存
    8．	用free_item()释放分配给菜单项的内存
    9．	结束curses

  现在我们看一个简单的菜单示例程序，这个程序可以用方向键来更新当前菜单项。

### 17.2 编译包含菜单库的程序

  要使用菜单库中的函数，首先要把menu.h这个文件包含进去。在编译和连接时就要同时添加-lmenu和-lncurses两个参数。

    	#include <menu.h>

    编译和连接: gcc <程序> -lmenu –lncurses

例18 菜单基础知识示例

    #include <curses.h>
    #include <menu.h>

    #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
    #define CTRLD 	4

    char *choices[] = {
                            "Choice 1",
                            "Choice 2",
                            "Choice 3",
                            "Choice 4",
                            "Exit",
                      };

    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);
    	mvprintw(LINES - 2, 0, "F1 to Exit");
    	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;
    		}
    	}

    	free_item(my_items[0]);
    	free_item(my_items[1]);
    	free_menu(my_menu);
    	endwin();
    }

  这个程序演示了用菜单库创建菜单的基本步骤。首先我们用new_item()建立菜单项，然后用new_menu()函数把它们添加到菜单。当递送菜单并刷新屏幕后，主循环就开始处理了。它读入取用户的输入并进行相应的操作。函数menu_driver()是处理菜单系统的主函数。这个函数的第二个参数用来说明每个菜单的相应操作。menu_driver()函数根据参数执行相应的任务。参数的值可以是菜单导览请求，一个ASCII码或与鼠标事件相关的一个特定的KEY_MOUSE值。

    menu_driver()函数可以接受以下导览请求：（就是第二个参数）

    	REQ_LEFT_ITEM			左移一个菜单项。
    	REQ_RIGHT_ITEM			右移一个菜单项。
    	REQ_UP_ITEM				上移一个菜单项。
    	REQ_DOWN_ITEM			下移一个菜单项。
    	REQ_SCR_ULINE			向上滚动一行。
    	REQ_SCR_DLINE			向下滚动一行。
    	REQ_SCR_DPAGE			下翻一页。
    	REQ_SCR_UPAGE			上翻一页
    	REQ_FIRST_ITEM			跳到首项。
    	REQ_LAST_ITEM			跳到最末一项。
    	REQ_NEXT_ITEM			跳到下一项。
    	REQ_PREV_ITEM			跳到上一项。
    	REQ_TOGGLE_ITEM			选择/取消选择一项。
    	REQ_CLEAR_PATTERN			清空菜单模式缓冲区。
    	REQ_BACK_PATTERN			删除菜单模式缓冲区的前面一个字符。
    	REQ_NEXT_MATCH			跳到下一个与模式匹配的项。
    	REQ_PREV_MATCH			跳到上一个与模式匹配的项。

  千万不要被这么多的操作请求吓倒，稍后我们会一个一个地讲解。在这个例子中，最有趣的是REQ_UP_ITEM和REQ_DOWN_ITEM。当这两个选项传给menu_driver()函数时，menu_driver()函数将会通过重新刷新屏幕上移或下移一个菜单项。

### 17.3 Menu Driver：菜单系统的灵魂

  如你在上面的例子中所看到的，menu_driver在更新菜单时有着举足轻重的作用。所以了解它的各个选项和它们的作用就很有必要了。前面已经解释了，menu_driver()的第二个参数可以是一个移动请求，一个可打印的字符或KEY_MOUSE键值。我们来剖析一下各个移动请求的区别。

REQ_LEFT_ITEM 和 REQ_RIGHT_ITEM

  一个菜单可以用多列的方式显示菜单项，这可以用函数menu_format()来实现。当显示一个多列菜单时，这两个移动请求可使menu drive在当前菜单项位置左移或右移一个菜单项。

REQ_UP_ITEM 和 REQ_DOWN_ITEM

  这两个移动请求在上面的例子中已经出现过。这两个移动请求传递给menu_driver时，menu_driver从当前菜单项上移或下移一个菜单项。

REQ_SCR_* 之类的移动请求

  REQ_SCR_ULINE, REQ_SCR_DLINE, REQ_SCR_DPAGE和 REQ_SCR_UPAGE是有关屏幕滚动的移动请求。如果所有的菜单项在当前菜单子窗口中显示不完，菜单就是可滚动的了。这些请求传给menu_driver时，分别向上/下滚动一行/页。

REQ_FIRST_ITEM, REQ_LAST_ITEM, REQ_NEXT_ITEM 和REQ_PREV_ITEM

  这些移动请求请求的功能从名字上就可以明显的看出来。分别是移动到第一个菜单项、第二个菜单项、下一个菜单项和前一个菜单项。

REQ_TOGGLE_ITEM

  当使用这个移动请求时，当前菜单项会被锁定。这个选项仅用于一个多层菜单。因此使用它时须关闭O_ONEVALUE。这个选项可以通过set_menu_opts()函数关闭或启用。

样式匹配请求

  每个菜单都有一个与之关联的匹配缓冲区。通过这个缓冲区，用户可以通过输入ASCII字符样式查找与之匹配的菜单项。任何传给menu_driver的ASCII字符都会被送入样式匹配缓冲区，同时它试着从菜单项列表中查找最近的匹配项。然后立刻从当前菜单项跳转到那个与匹配样式最近的菜单项上去。REQ_CLEAR_PATTERN请求用来清空匹配缓冲区。REQ_BACK_PATTERN清除模式缓冲区的前一个匹配样式。如果有多个匹配样式，这些匹配样式就可以通过REQ_NEXT_PATTERN和REQ_PREV_PATTERN切换，这两个选项分别从当前菜单项移至下/上一个匹配样式。

鼠标事件请求

  如果有鼠标事件请求，则根据鼠标的位置来产生相应的事件。在man帮助页是这样解释这些行为的：
如果第二个参数是KEY_MOUSE的键值，相应的鼠标事件就会转化为上面已经定义的请求。现在你只须在用户窗口点击鼠标（如：在菜单显示区或窗口）。如果你是在菜单显示区单击的，将生成REQ_SCR_ULINE选项，如果是双击，就会生成REQ_SCR_UPAGE，如果单击三次，则生成REQ_FIRST_ITEM。如果你是在菜单显示区域的下方单击的，将生成REQ_SCR_DLINE，若是双击，就会生成REQ_SCR_DPAGE，若是单击三次的话，则生成REQ_LAST_ITEM。如果你单击菜单显示区内的某个菜单项，菜单的提示光标就定位在那个菜单项上了。

后面的程序中将会对以上的移动请求作适当的讲解。

### 17.4 含菜单窗口

  每个已创建的菜单都对应着一个窗口和一个子窗口。菜单窗口显示菜单的标题或边框线。菜单子窗口显示当前可选的菜单项。但在上面的那个例子里我们并没有指定窗口或子窗口。当窗口未被指定时，stdscr将作为主窗口。然后菜单系统根据将要显示的菜单项规划计算子窗口的大小。这些菜单项就在规划好的子窗口中显示出来。接下来让我们就来通过玩转这些窗口：打印一个有边框线和标题的菜单。

例19． 一个菜单窗口用法的例子

    #include <menu.h>
    #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
    #define CTRLD 	4

    char *choices[] = {
                            "Choice 1",
                            "Choice 2",
                            "Choice 3",
                            "Choice 4",
                            "Exit",
                            (char *)NULL,
                      };
    void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);

    int main()
    {	ITEM **my_items;
    	int c;
    	MENU *my_menu;
        WINDOW *my_menu_win;
        int n_choices, i;

    	/* 初始化curses */
    	initscr();
    	start_color();
        cbreak();
        noecho();
    	keypad(stdscr, TRUE);
    	init_pair(1, COLOR_RED, COLOR_BLACK);

    	/* 创建菜单项 */
        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, 40, 4, 4);
        keypad(my_menu_win, TRUE);

    	/* 设置主窗口和子窗口 */
        set_menu_win(my_menu, my_menu_win);
        set_menu_sub(my_menu, derwin(my_menu_win, 6, 38, 3, 1));

    	/* 设置字符串的标记为 " * " */
        set_menu_mark(my_menu, " * ");

    	/* 在主窗口的边界打印边框线和标题 */
        box(my_menu_win, 0, 0);
    	print_in_middle(my_menu_win, 1, 0, 40, "My Menu", COLOR_PAIR(1));
    	mvwaddch(my_menu_win, 2, 0, ACS_LTEE);
    	mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38);
    	mvwaddch(my_menu_win, 2, 39, ACS_RTEE);
    	mvprintw(LINES - 2, 0, "F1 to exit");
    	refresh();

    	/* 递送菜单 */
    	post_menu(my_menu);
    	wrefresh(my_menu_win);

    	while((c = wgetch(my_menu_win)) != 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;
    			}
                wrefresh(my_menu_win);
    	}

    	/* 取消递送并释放占用的内存 */
        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, chtype color)
    {	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;
    	wattron(win, color);
    	mvwprintw(win, y, x, "%s", string);
    	wattroff(win, color);
    	refresh();
    }

  这个例子创建了这样一个菜单：有标题、边框，用一根线分隔开标题和菜单项。如你所见，使用函数set_menu_win()来把一个窗口附加到一个菜单。同样也把子窗口附加进来。这样就可以在子窗口中显示菜单和菜单项了。你也可以使用set_menu_mark()来设置标志串，让它显示在在所选菜单项的左边。

### 17.5 滚动菜单

  如果设置的子窗口面积不够显示所有的菜单项，菜单将变成可滚动的。在当前列的最后一个菜单项时，如果你把参数REQ_DOWN_ITEM传递过去，它将会变成REQ_SCR_DLINE，将菜单滚动一个菜单项。你也可以手动地用REQ_SCR_操作来滚动菜单。我们来看看怎么做。

例20．一个滚动菜单的例子

    #include <curses.h>
    #include <menu.h>

    #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
    #define CTRLD 	4

    char *choices[] = {
                            "Choice 1",
                            "Choice 2",
                            "Choice 3",
                            "Choice 4",
    				 "Choice 5",
    				 "Choice 6",
    				 "Choice 7",
    				 "Choice 8",
    				 "Choice 9",
    				 "Choice 10",
                            "Exit",
                            (char *)NULL,
                      };
    void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);

    int main()
    {	ITEM **my_items;
    	int c;
    	MENU *my_menu;
            WINDOW *my_menu_win;
            int n_choices, i;

    	/* 初始化curses */
    	initscr();
    	start_color();
        cbreak();
        noecho();
    	keypad(stdscr, TRUE);
    	init_pair(1, COLOR_RED, COLOR_BLACK);
    	init_pair(2, COLOR_CYAN, COLOR_BLACK);

    	/* 创建菜单项 */
        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, 40, 4, 4);
        keypad(my_menu_win, TRUE);

    	/* 设置主窗口和子窗口 */
        set_menu_win(my_menu, my_menu_win);
        set_menu_sub(my_menu, derwin(my_menu_win, 6, 38, 3, 1));
    	set_menu_format(my_menu, 5, 1);

    	/* 设置标志串为" * " */
        set_menu_mark(my_menu, " * ");

    	/* 在主窗口的边界打印边框线和标题 */
        box(my_menu_win, 0, 0);
    	print_in_middle(my_menu_win, 1, 0, 40, "My Menu", COLOR_PAIR(1));
    	mvwaddch(my_menu_win, 2, 0, ACS_LTEE);
    	mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38);
    	mvwaddch(my_menu_win, 2, 39, ACS_RTEE);

    	/* 传递菜单 */
    	post_menu(my_menu);
    	wrefresh(my_menu_win);

    	attron(COLOR_PAIR(2));
    	mvprintw(LINES - 2, 0, "Use PageUp and PageDown to scoll down or up a page of items");
    	mvprintw(LINES - 1, 0, "Arrow Keys to navigate (F1 to Exit)");
    	attroff(COLOR_PAIR(2));
    	refresh();

    	while((c = wgetch(my_menu_win)) != 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 KEY_NPAGE:
    				menu_driver(my_menu, REQ_SCR_DPAGE);
    				break;
    			case KEY_PPAGE:
    				menu_driver(my_menu, REQ_SCR_UPAGE);
    				break;
    		}
                    wrefresh(my_menu_win);
    	}
    	/* 取消传递并且释放占用的内存 */
        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, chtype color)
    {	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;
    	wattron(win, color);
    	mvwprintw(win, y, x, "%s", string);
    	wattroff(win, color);
    	refresh();
    }

  这个程序本身已经有了很好地注解。在这个例子中，菜单项的数量增加到了10个，大于我们设置的子窗口的大小——6项，这个信息可以通过函数set_menu_format()显式地传递给菜单系统。在这里我们指定了在一页中可以显示的行数和列数。如果小于子窗口的高度，我们可以任意地指定每页可以显示的行数和列数。如果用户按了键PAGE UP或PAGE DOWN，请求REQ_SCR_UPAGE和REQ_SCR_PATTERN就传递给了menu_driver()，菜单则会滚动。

### 17.6 多列菜单

  在上面的例子中你知道了函数set_menu_format()的用法。我并没有提及列变量（第三个参数）的用法。如果子窗口够宽的话，你可以选择显示每行显示多个菜单项。这可以在列变量里面设置。为简化问题起见，下面的这个例子没有对菜单项进行描述。

例21．一个多列菜单的例子

    #include <curses.h>>
    #include <menu.h>
    #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
    #define CTRLD 	4

    char *choices[] = {
                     "Choice 1", "Choice 2", "Choice 3", "Choice 4", "Choice 5",
    			"Choice 6", "Choice 7", "Choice 8", "Choice 9", "Choice 10",
    			"Choice 11", "Choice 12", "Choice 13", "Choice 14", "Choice 15",
    			"Choice 16", "Choice 17", "Choice 18", "Choice 19", "Choice 20",
                	"Exit",
                	(char *)NULL,
         		};

    int main()
    {	ITEM **my_items;
    	int c;
    	MENU *my_menu;
    	WINDOW *my_menu_win;
    	int n_choices, i;

    	/* 初始化curses */
    	initscr();
    	start_color();
    	cbreak();
    	noecho();
    	keypad(stdscr, TRUE);
    	init_pair(1, COLOR_RED, COLOR_BLACK);
    	init_pair(2, COLOR_CYAN, COLOR_BLACK);

    	/* 创建菜单项 */
    	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);

    	/* 设置菜单选项，不显示描述 */
    	menu_opts_off(my_menu, O_SHOWDESC);

    	/* 创建与菜单相关联的窗口 */
    	my_menu_win = newwin(10, 70, 4, 4);
    	keypad(my_menu_win, TRUE);

    	/* 设置主窗口和子窗口 */
    	set_menu_win(my_menu, my_menu_win);
    	set_menu_sub(my_menu, derwin(my_menu_win, 6, 68, 3, 1));
    	set_menu_format(my_menu, 5, 3);
    	set_menu_mark(my_menu, " * ");

    	/* 在主窗口的边界打印边框线和标题 */
    	box(my_menu_win, 0, 0);

    	attron(COLOR_PAIR(2));
    	mvprintw(LINES - 3, 0, "Use PageUp and PageDown to scroll");
    	mvprintw(LINES - 2, 0, "Use Arrow Keys to navigate (F1 to Exit)");
    	attroff(COLOR_PAIR(2));
    	refresh();

    	/* 传递菜单 */
    	post_menu(my_menu);
    	wrefresh(my_menu_win);

    	while((c = wgetch(my_menu_win)) != 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 KEY_LEFT:
    				menu_driver(my_menu, REQ_LEFT_ITEM);
    				break;
    			case KEY_RIGHT:
    				menu_driver(my_menu, REQ_RIGHT_ITEM);
    				break;
    			case KEY_NPAGE:
    				menu_driver(my_menu, REQ_SCR_DPAGE);
    				break;
    			case KEY_PPAGE:
    				menu_driver(my_menu, REQ_SCR_UPAGE);
    				break;
    		}
            wrefresh(my_menu_win);
    	}

    	/* 取消传递并释放占用的内存 */
    	unpost_menu(my_menu);
    	free_menu(my_menu);
    	for(i = 0; i < n_choices; ++i)
    		free_item(my_items[i]);
    	endwin();
    }

  我们仔细看一下函数set_menu_format()的调用。它指定行数为3，因此每行就显示3个菜单项。我们已经用函数menu_opts_off()关闭了描述显示。其他几个函数：set_menu_opts()，menu_opts_on()和menu_opts()也可以用来控制菜单选项。下面的菜单选项可以用来指定：

         O_ONEVALUE
                在菜单中只能选一个菜单项。

         O_SHOWDESC
                当菜单被传递时显示对菜单项的描述。

         O_ROWMAJOR
               以行为主序显示菜单。

         O_IGNORECASE
               在样式匹配时忽略大小写。

         O_SHOWMATCH
               样式匹配时光标移到菜单项上。

         O_NONCYCLIC
               上移或下移一个菜单项时，不能跳到菜单另一端的菜单项上去。

  默认情况下，这些选项都是打开的。你可以通过函数menu_opts_on()和menu_opts_off()用来开关某个属性。你也可以使用函数set_menu_opts()直接指定这些选项。这个函数的参数应该是上面那些常量的或（OR）值。函数menu_opts()可以用来查看当前菜单的选项。

### 17.7 多选菜单

  把选项O_ONEVALUE关闭之后会是什么情况呢？菜单变成了多项选择的了。这意味着你可以同时选择多个菜单项。这就把我们的注意力转向了REQ_TOGGLE_ITEM请求的用法，一起来看看吧：

例22．一个多选菜单的例子

    #include <curses.h>
    #include <menu.h>>

    #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
    #define CTRLD 	4

    char *choices[] = {
                            "Choice 1",
                            "Choice 2",
                            "Choice 3",
                            "Choice 4",
    				 "Choice 5",
    				 "Choice 6",
    				 "Choice 7",
                            "Exit",
                      };

    int main()
    {	ITEM **my_items;
    	int c;
    	MENU *my_menu;
    	int n_choices, i;
    	ITEM *cur_item;

    	/* 初始化curses */
    	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 - 3, 0, "Use <SPACE> to select or unselect an item.");
    	mvprintw(LINES - 2, 0, "<ENTER> to see presently selected items(F1 to Exit)");
    	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 ' ':
    				menu_driver(my_menu, REQ_TOGGLE_ITEM);
    				break;
    			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;
    		}
    	}

    	free_item(my_items[0]);
    	free_item(my_items[1]);
    	free_menu(my_menu);
    	endwin();
    }

  Woo！出现了好多新函数。我们一个一个来看吧。先看REQ_TOGGLE_ITEM。在一个多选菜单里，用户可以选择多个菜单项。请求REQ_TOGGLE_ITEM锁定当前选择的菜单项。在这种情况下，当按下空格键时，请求REQ_TOGGLE_ITEM就被传给menu_driver，从而锁定某个菜单项。

  如果用户按下了键，则显示当前已选的菜单项。首先，用函数menu_items()找到与菜单相关联的菜单项。然后循环核对这些菜单项，看某个菜单项是否已选。如果一个菜单项已选，函数item_value()返回TRUE。函数item_count()返回菜单的菜单项数。菜单项的名字可以通过函数item_name()找到。你也可以用函数item_description()来得知一个菜单项的相关描述。

### 17.8 菜单选项

  好了，现在你一定非常想在你的菜单里弄出点花样来，且使得菜单功能更加强大。我知道，你要让菜单变成彩色的！！！你希望创建一个好看一点的菜单，就像这里的文本模式DOS 游戏。函数set_menu_fore()和set_menu_back()可以用来改变选中和未选中菜单项的属性。这两个函数的名字可能会引起误会。它们并不会改变菜单的前景色与背景色哦！

  函数set_menu_grey()可以用来设置菜单中未选菜单项的属性。这把我们引向了仅为单个菜单项的进行设置的选项???O_SELECTABLE。我们可以用函数item_opts_off()来关闭这个选项，然后那个菜单项就不可选了。在菜单窗口中它就变成灰色的。让我们把这些概念运用到下面的这个例子中：

例23．一个菜单选项的例子

    #include <menu.h>
    #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
    #define CTRLD 	4

    char *choices[] = {
                            "Choice 1",
                            "Choice 2",
                            "Choice 3",
                            "Choice 4",
    				 "Choice 5",
    				 "Choice 6",
    				 "Choice 7",
                            "Exit",
                      };

    int main()
    {	ITEM **my_items;
    	int c;
    	MENU *my_menu;
    	int n_choices, i;
    	ITEM *cur_item;

    	/* 初始化curses */
    	initscr();
    	start_color();
    	cbreak();
    	noecho();
    	keypad(stdscr, TRUE);
    	init_pair(1, COLOR_RED, COLOR_BLACK);
    	init_pair(2, COLOR_GREEN, COLOR_BLACK);
    	init_pair(3, COLOR_MAGENTA, COLOR_BLACK);

    	/* 初始化菜单项 */
    	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);

    	/* 为菜单设置前景和背景 */
    	set_menu_fore(my_menu, COLOR_PAIR(1) | A_REVERSE);
    	set_menu_back(my_menu, COLOR_PAIR(2));
    	set_menu_grey(my_menu, COLOR_PAIR(3));

    	/* 传递菜单 */
    	mvprintw(LINES - 3, 0, "Press  to see the option selected");
    	mvprintw(LINES - 2, 0, "Up and Down arrow keys to naviage (F1 to Exit)");
    	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;
    		}
    	}
    	unpost_menu(my_menu);
    	for(i = 0; i < n_choices; ++i)
    	free_item(my_items[i]);
    	free_menu(my_menu);
    	endwin();
    }

### 17.9 大有作为的用户指针

  我们可以把一个用户指针和菜单中的一个菜单项联系起来。它的工作原理与面板里的用户指针一样，并不触及菜单系统。你可以在那里面存储任何你想要的东西。我经常用它来存储所选菜单选项要执行的函数（已选定并且用户按下了键）。

例24．菜单用户指针的用法

    #include <curses.h>
    #include <menu.h>
    #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
    #define CTRLD 	4

    char *choices[] = {
                            "Choice 1",
                            "Choice 2",
                            "Choice 3",
                            "Choice 4",
    				 "Choice 5",
    				 "Choice 6",
    				 "Choice 7",
                            "Exit",
                      };
    void func(char *name);

    int main()
    {	ITEM **my_items;
    	int c;
    	MENU *my_menu;
    	int n_choices, i;
    	ITEM *cur_item;

    	/* 初始化curses */
    	initscr();
    	start_color();
    	cbreak();
    	noecho();
    	keypad(stdscr, TRUE);
    	init_pair(1, COLOR_RED, COLOR_BLACK);
    	init_pair(2, COLOR_GREEN, COLOR_BLACK);
    	init_pair(3, COLOR_MAGENTA, COLOR_BLACK);

    	/* 初始化菜单项 */
    	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]);
    		/* 设置用户指针 */
    		set_item_userptr(my_items[i], func);
    	}
    	my_items[n_choices] = (ITEM *)NULL;

    	/* 创建菜单 */
    	my_menu = new_menu((ITEM **)my_items);

    	/* 传递菜单 */
    	mvprintw(LINES - 3, 0, "Press <ENTER> to see the option selected");
    	mvprintw(LINES - 2, 0, "Up and Down arrow keys to naviage (F1 to Exit)");
    	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 键 */
    			{	ITEM *cur;
    				void (*p)(char *);

    				cur = current_item(my_menu);
    				p = item_userptr(cur);
    				p((char *)item_name(cur));
    				pos_menu_cursor(my_menu);
    				break;
    			}
    			break;
    		}
    	}
    	unpost_menu(my_menu);
    	for(i = 0; i < n_choices; ++i)
    	free_item(my_items[i]);
    	free_menu(my_menu);
    	endwin();
    }

    void func(char *name)
    {	move(20, 0);
    	clrtoeol();
    	mvprintw(20, 0, "Item selected is : %s", name);
    }

回 目 录
上 一 章 下 一 章