菜单库对cureses基础库进行了很好的扩展。你可以通过这个库所提供的函数方便的创建菜单。如果你想让它更美观,可以定制它的显示效果。下面我们就来看看这个库。
菜单是一个用来帮助用户选择子菜单项的屏幕。简而言之,菜单就是一个菜单项的集合,使你可以方便的从中选择相应的菜单命令。curses菜单库还提供编制多选菜单的功能。有些读者可能不了解多选菜单。这个我们稍后讨论,我们先来了解一下菜单库的基础知识。
要创建菜单,你首先要建立菜单项,然后发送并显示菜单。接下来,所有处理用户响应的工作就交给功能强大的menu_driver()函数来完成。这个函数是整个菜单库的核心。
一个菜单程序大致的控制流程如下:
1. 初始化curses。
2. 用函数new_item()创建菜单项,同时为菜单项指定名称并且描述其相应的功能。
3. 用函数new_menu()创建菜单,同时指定要添加的菜单项。
4. 用函数post_menu()递送菜单并刷新屏幕
5. 用一个循环处理用户的菜单请求。并用menu_driver()函数对菜单做必要的更新。
6. 用unpost_menu()取消菜单递送。
7. 用free_menu()释放分配给菜单的内存
8. 用free_item()释放分配给菜单项的内存
9. 结束curses
现在我们看一个简单菜单的示例程序,它是用方向键来更新当前菜单项的。
要使用菜单库中的函数,首先要把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()函数将会通过重新刷新屏幕上移或下移一个菜单项。
如你在上面的例子中所看到的,menu_driver在更新菜单时有着举足轻重的作用。所以了解它的各个选项和它们的作用就很有必要了。前面已经解释过,menu_driver()的第二个参数可以是一个导航请求。一个可打印的字符(ASCII码)或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。如果你单击菜单显示区内的某个菜单项,菜单的提示光标就定位在那个菜单项上了。
后面的程序中将会对以上的导航请求作详细的讲解。
每个已创建的菜单都对应着一个窗口和一个子窗口。菜单窗口显示菜单的标题或边框线。菜单子窗口显示当前可选的菜单项。而在上面的例子里我们并没有指定窗口或子窗口。当窗口未被指定时,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_sub()函数把菜单的子窗口也附加到这个窗口上,菜单项就可以在子窗口中显示。使用set_menu_mark()函数可以来设置标志串,标志串就会出现在所选菜单项的左边。
如果设置的子窗口不够显示所有的菜单项,菜单将变成可滚动的。在当前列的最后一个菜单项处传递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:
&