v1.9, 2005-06-20
修订历史 | ||
---|---|---|
修订 1.9 | 2005-06-20 | 修订者:ppadala |
许可证已更改为 NCURSES 使用的 MIT 风格许可证。请注意,程序也根据此许可证重新许可。 | ||
修订 1.8 | 2005-06-17 | 修订者:ppadala |
大量更新。添加了参考资料和 perl 示例。更改了示例。对内容进行了许多语法和文体上的修改。更改了 NCURSES 历史记录。 | ||
修订 1.7.1 | 2002-06-25 | 修订者:ppadala |
添加了用于构建的 README 文件以及从源代码构建的说明。 | ||
修订 1.7 | 2002-06-25 | 修订者:ppadala |
添加了“其他格式”部分,并对程序进行了许多花哨的更改。程序内联已消失。 | ||
修订 1.6.1 | 2002-02-24 | 修订者:ppadala |
删除了旧的 Changelog 部分,清理了 makefile | ||
修订 1.6 | 2002-02-16 | 修订者:ppadala |
纠正了许多拼写错误,添加了 ACS 变量部分 | ||
修订 1.5 | 2002-01-05 | 修订者:ppadala |
更改了结构以呈现正确的 TOC | ||
修订 1.3.1 | 2001-07-26 | 修订者:ppadala |
纠正了维护者段落,纠正了稳定版本号 | ||
修订 1.3 | 2001-07-24 | 修订者:ppadala |
在主文档(LDP 许可证)和程序(GPL)中添加了版权声明,纠正了 printw_example。 | ||
修订 1.2 | 2001-06-05 | 修订者:ppadala |
纳入了 ravi 的更改。主要是介绍、菜单、表单、justforfun 部分 | ||
修订 1.1 | 2001-05-22 | 修订者:ppadala |
添加了“关于窗口的说明”部分,添加了 scanw_example。 |
本文档旨在成为使用 ncurses 及其姊妹库进行编程的“All in One”指南。我们从简单的“Hello World”程序逐步学习到更复杂的表单操作。假设您没有 ncurses 的先验经验。请将意见发送至 <此地址>
在老式的电传打字机终端时代,终端远离计算机,并通过串行电缆连接到计算机。可以通过发送一系列字节来配置终端。终端的所有功能(例如将光标移动到新位置、擦除屏幕的一部分、滚动屏幕、更改模式等)都可以通过这些字节序列访问。这些控制序列通常称为转义序列,因为它们以转义 (0x1B) 字符开头。即使在今天,通过适当的模拟,我们也可以向模拟器发送转义序列,并在终端窗口上实现相同的效果。
假设您想用彩色打印一行。请在您的控制台上尝试输入以下内容。
echo "^[[0;31;40mIn Color" |
第一个字符是转义字符,看起来像两个字符 ^ 和 [。要能够打印它,您必须按 CTRL+V,然后按 ESC 键。所有其他字符都是正常的打印字符。您应该能够看到红色的字符串“In Color”。它保持不变,要恢复到原始模式,请键入以下内容。
echo "^[[0;37;40m" |
现在,这些神奇的字符是什么意思?难以理解?它们甚至可能因不同的终端而异。因此,UNIX 的设计者发明了一种名为termcap的机制。它是一个文件,列出了特定终端的所有功能,以及实现特定效果所需的转义序列。在后来的几年里,这被terminfo取代。无需过多深入细节,此机制允许应用程序程序查询 terminfo 数据库并获取要发送到终端或终端模拟器的控制字符。
您可能想知道,所有这些技术术语的意义是什么。在上述场景中,每个应用程序程序都应该查询 terminfo 并执行必要的操作(发送控制字符等)。很快,管理这种复杂性变得困难,这催生了“CURSES”。Curses 是对名称“cursor optimization”(光标优化)的双关语。Curses 库形成了一个包装器,用于处理原始终端代码,并提供高度灵活和高效的 API(应用程序编程接口)。它提供了移动光标、创建窗口、生成颜色、操作鼠标等功能。应用程序程序无需担心底层终端功能。
那么什么是 NCURSES?NCURSES 是原始 System V Release 4.0 (SVr4) curses 的克隆。它是一个可自由分发的库,与旧版本的 curses 完全兼容。简而言之,它是一个管理应用程序在字符单元终端上的显示的函数库。在本文档的其余部分中,术语 curses 和 ncurses 交替使用。
有关 NCURSES 的详细历史记录,请参见源发行版中的 NEWS 文件。当前的软件包由 <Thomas Dickey> 维护。您可以通过 <bug-ncurses@gnu.org> 联系维护者。
NCURSES 不仅创建了终端功能的包装器,还提供了一个强大的框架,用于在文本模式下创建美观的 UI(用户界面)。它提供了创建窗口等功能。它的姊妹库 panel、menu 和 form 提供了对基本 curses 库的扩展。这些库通常与 curses 一起提供。可以创建包含多个窗口、菜单、面板和表单的应用程序。窗口可以独立管理,可以提供“可滚动性”,甚至可以隐藏。
菜单为用户提供了简单的命令选择选项。表单允许创建易于使用的数据输入和显示窗口。面板扩展了 ncurses 的功能,以处理重叠和堆叠的窗口。
这些只是我们可以用 ncurses 做的一些基本事情。随着我们继续前进,我们将看到这些库的所有功能。
好的,现在您已经了解了可以使用 ncurses 做什么,您一定渴望开始了。NCURSES 通常随您的安装一起提供。如果您没有该库或想自己编译它,请继续阅读。
编译软件包
可以从 <ftp://ftp.gnu.org/pub/gnu/ncurses/ncurses.tar.gz> 或 <https://gnu.ac.cn/order/ftp.html> 中提到的任何 ftp 站点获取 NCURSES。
请阅读 README 和 INSTALL 文件,了解有关如何安装它的详细信息。它通常涉及以下操作。
tar zxvf ncurses<version>.tar.gz # unzip and untar the archive cd ncurses<version> # cd to the directory ./configure # configure the build according to your # environment make # make it su root # become root make install # install it |
使用 RPM
可以从 <http://rpmfind.net > 找到并下载 NCURSES RPM。以 root 身份登录后,可以使用以下命令安装 RPM。
rpm -i <downloaded rpm> |
本文档旨在成为使用 ncurses 及其姊妹库进行编程的“All in One”指南。我们从简单的“Hello World”程序逐步学习到更复杂的表单操作。假设您没有 ncurses 的先验经验。写作风格非正式,但为每个示例提供了很多细节。
文档中的所有程序都以压缩形式 <在此处> 提供。解压缩并解压它。目录结构如下所示。
ncurses | |----> JustForFun -- just for fun programs |----> basics -- basic programs |----> demo -- output files go into this directory after make | | | |----> exe -- exe files of all example programs |----> forms -- programs related to form library |----> menus -- programs related to menus library |----> panels -- programs related to panels library |----> perl -- perl equivalents of the examples (contributed | by Anuradha Ratnaweera) |----> Makefile -- the top level Makefile |----> README -- the top level README file. contains instructions |----> COPYING -- copyright notice |
各个目录包含以下文件。
Description of files in each directory -------------------------------------- JustForFun | |----> hanoi.c -- The Towers of Hanoi Solver |----> life.c -- The Game of Life demo |----> magic.c -- An Odd Order Magic Square builder |----> queens.c -- The famous N-Queens Solver |----> shuffle.c -- A fun game, if you have time to kill |----> tt.c -- A very trivial typing tutor basics | |----> acs_vars.c -- ACS_ variables example |----> hello_world.c -- Simple "Hello World" Program |----> init_func_example.c -- Initialization functions example |----> key_code.c -- Shows the scan code of the key pressed |----> mouse_menu.c -- A menu accessible by mouse |----> other_border.c -- Shows usage of other border functions apa | -- rt from box() |----> printw_example.c -- A very simple printw() example |----> scanw_example.c -- A very simple getstr() example |----> simple_attr.c -- A program that can print a c file with | -- comments in attribute |----> simple_color.c -- A simple example demonstrating colors |----> simple_key.c -- A menu accessible with keyboard UP, DOWN | -- arrows |----> temp_leave.c -- Demonstrates temporarily leaving curses mode |----> win_border.c -- Shows Creation of windows and borders |----> with_chgat.c -- chgat() usage example forms | |----> form_attrib.c -- Usage of field attributes |----> form_options.c -- Usage of field options |----> form_simple.c -- A simple form example |----> form_win.c -- Demo of windows associated with forms menus | |----> menu_attrib.c -- Usage of menu attributes |----> menu_item_data.c -- Usage of item_name() etc.. functions |----> menu_multi_column.c -- Creates multi columnar menus |----> menu_scroll.c -- Demonstrates scrolling capability of menus |----> menu_simple.c -- A simple menu accessed by arrow keys |----> menu_toggle.c -- Creates multi valued menus and explains | -- REQ_TOGGLE_ITEM |----> menu_userptr.c -- Usage of user pointer |----> menu_win.c -- Demo of windows associated with menus panels | |----> panel_browse.c -- Panel browsing through tab. Usage of user | -- pointer |----> panel_hide.c -- Hiding and Un hiding of panels |----> panel_resize.c -- Moving and resizing of panels |----> panel_simple.c -- A simple panel example perl |----> 01-10.pl -- Perl equivalents of first ten example programs |
主目录中包含一个顶级 Makefile。它构建所有文件并将可直接使用的 exes 放在 demo/exe 目录中。您也可以通过进入相应的目录来选择性地进行 make。每个目录都包含一个 README 文件,说明该目录中每个 c 文件的用途。
对于每个示例,我都包含了相对于 examples 目录的文件路径名。
如果您喜欢浏览单个程序,请将您的浏览器指向 <https://tldp.cn/HOWTO/NCURSES-Programming-HOWTO/ncurses_programs/>
所有程序均根据 ncurses (MIT 风格) 使用的相同许可证发布。这使您能够进行几乎任何操作,但不能声称它们是您的。请随意在您的程序中酌情使用它们。
本 HOWTO 还在 tldp.org 站点上以各种其他格式提供。以下是本文档其他格式的链接。
如果以上链接已损坏,或者您想尝试 sgml,请继续阅读。
Get both the source and the tar,gzipped programs, available at http://cvsview.tldp.org/index.cgi/LDP/howto/docbook/ NCURSES-HOWTO/NCURSES-Programming-HOWTO.sgml http://cvsview.tldp.org/index.cgi/LDP/howto/docbook/ NCURSES-HOWTO/ncurses_programs.tar.gz Unzip ncurses_programs.tar.gz with tar zxvf ncurses_programs.tar.gz Use jade to create various formats. For example if you just want to create the multiple html files, you would use jade -t sgml -i html -d <path to docbook html stylesheet> NCURSES-Programming-HOWTO.sgml to get pdf, first create a single html file of the HOWTO with jade -t sgml -i html -d <path to docbook html stylesheet> -V nochunks NCURSES-Programming-HOWTO.sgml > NCURSES-ONE-BIG-FILE.html then use htmldoc to get pdf file with htmldoc --size universal -t pdf --firstpage p1 -f <output file name.pdf> NCURSES-ONE-BIG-FILE.html for ps, you would use htmldoc --size universal -t ps --firstpage p1 -f <output file name.ps> NCURSES-ONE-BIG-FILE.html |
有关更多详细信息,请参见 <LDP 作者指南>。如果所有其他方法都失败,请发送邮件至 <ppadala@gmail.com>
感谢 <Sharath> 和 Emre Akbas 在一些章节中对我的帮助。简介最初由 sharath 撰写。我重写了它,并摘录了他最初工作中的一些内容。Emre 帮助编写了 printw 和 scanw 部分。
示例程序的 Perl 等效程序由 <Anuradha Ratnaweera> 贡献。
然后是 <Ravi Parimi>,我最亲爱的朋友,甚至在写下第一行代码之前他就参与了这个项目。他不断向我提出建议,并耐心地审阅了全文。他还检查了 Linux 和 Solaris 上的每个程序。
这是愿望清单,按优先级排序。如果您有愿望或想努力实现愿望,请发送邮件至 <我>。
在表单部分的最后部分添加示例。
准备一个演示,展示所有程序,并允许用户浏览每个程序的描述。让用户编译并查看程序的运行情况。首选基于对话框的界面。
添加调试信息。_tracef, _tracemouse 内容。
使用 ncurses 软件包提供的函数访问 termcap、terminfo。
同时在两个终端上工作。
在杂项部分添加更多内容。
版权所有 © 2001 Pradeep Padala。
特此授予许可,免费向任何获得本软件和相关文档文件(“软件”)副本的人员授予许可,以不受限制地处理本软件,包括但不限于使用、复制、修改、合并、发布、分发、修改分发、再许可和/或销售软件副本的权利,并允许向其提供软件的人员这样做,但须满足以下条件
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
本软件按“原样”提供,不提供任何形式的明示或暗示保证,包括但不限于适销性、特定用途适用性和非侵权保证。在任何情况下,上述版权持有人均不对因合同、侵权或其他原因引起的、与软件或软件的使用或其他交易相关的任何索赔、损害或其他责任承担责任。
除非本声明另有规定,否则未经事先书面授权,不得在广告或其他方面使用上述版权持有人的姓名来推销、使用或以其他方式处理本软件。
欢迎来到 curses 的世界。在我们深入研究库并了解其各种功能之前,让我们编写一个简单的程序并向世界问好。
要使用 ncurses 库函数,您必须在程序中包含 ncurses.h。要将程序与 ncurses 链接,应添加标志 -lncurses。
#include <ncurses.h> . . . compile and link: gcc <program file> -lncurses |
上面的程序在屏幕上打印“Hello World !!!”并退出。该程序演示了如何初始化 curses 并进行屏幕操作以及结束 curses 模式。让我们逐行剖析它。
函数 initscr() 在 curses 模式下初始化终端。在某些实现中,它会清除屏幕并呈现一个空白屏幕。要使用 curses 软件包进行任何屏幕操作,必须首先调用此函数。此函数初始化 curses 系统并为我们当前的窗口(称为stdscr)和一些其他数据结构分配内存。在极端情况下,此函数可能会由于内存不足而无法为 curses 库的数据结构分配内存而失败。
完成此操作后,我们可以执行各种初始化以自定义我们的 curses 设置。这些详细信息将在 <稍后> 解释。
下一行 printw 在屏幕上打印字符串“Hello World !!!”。此函数在所有方面都类似于普通 printf,不同之处在于它在当前 (y,x) 坐标处的名为 stdscr 的窗口上打印数据。由于我们当前的坐标为 0,0,因此字符串打印在窗口的左上角。
这使我们想到了神秘的 refresh()。好吧,当我们调用 printw 时,数据实际上是写入到一个假想的窗口中,该窗口尚未在屏幕上更新。printw 的工作是更新一些标志和数据结构,并将数据写入与 stdscr 对应的缓冲区。为了在屏幕上显示它,我们需要调用 refresh() 并告诉 curses 系统将内容转储到屏幕上。
这一切背后的理念是允许程序员在假想屏幕或窗口上进行多次更新,并在完成所有屏幕更新后执行一次刷新。refresh() 检查窗口并仅更新已更改的部分。这提高了性能并提供了更大的灵活性。但是,有时会让初学者感到沮丧。初学者常犯的错误是在通过 printw() 类函数进行一些更新后忘记调用 refresh()。我有时仍然忘记添加它 :-)
我们现在知道,要初始化 curses 系统,必须调用函数 initscr()。在完成此初始化之后,可以调用一些函数来自定义我们的 curses 会话。我们可以要求 curses 系统将终端设置为原始模式或初始化颜色或初始化鼠标等。让我们讨论一些通常在 initscr() 之后立即调用的函数;
通常,终端驱动程序会缓冲用户键入的字符,直到遇到新行或回车符。但是大多数程序都要求字符在用户键入后立即可用。以上两个函数用于禁用行缓冲。这两个函数之间的区别在于控制字符(如挂起 (CTRL-Z)、中断和退出 (CTRL-C))传递给程序的方式。在 raw() 模式下,这些字符直接传递给程序,而不会生成信号。在cbreak()模式下,这些控制字符被终端驱动程序解释为任何其他字符。我个人更喜欢使用 raw(),因为我可以更好地控制用户的操作。
这些函数控制用户键入的字符是否回显到终端。noecho()关闭回显。您可能想要这样做原因是获得对回显的更多控制权,或者在通过 getch() 等函数从用户获取输入时抑制不必要的回显。大多数交互式程序在noecho()初始化时调用,并以受控方式回显字符。它使程序员可以灵活地在窗口中的任何位置回显字符,而无需更新当前 (y,x) 坐标。
这是我最喜欢的初始化函数。它启用了功能键(如 F1、F2、方向键等)的读取。几乎每个交互式程序都启用此功能,因为方向键是任何用户界面的主要组成部分。执行keypad(stdscr, TRUE)以在常规屏幕 (stdscr) 上启用此功能。您将在本文档的后续章节中了解有关按键管理的更多信息。
虽然此函数不常用,但有时很有用。调用 halfdelay() 以启用半延迟模式,该模式类似于 cbreak() 模式,因为键入的字符可以立即供程序使用。但是,它会等待 'X' 十分之几秒来获取输入,如果没有任何输入可用,则返回 ERR。'X' 是传递给函数 halfdelay() 的超时值。当您想要求用户输入,如果他在一定时间内没有响应,我们可以做其他事情时,此函数很有用。一个可能的示例是密码提示处的超时。
让我们编写一个程序来阐明这些函数的使用。
示例 2. 初始化函数使用示例
#include <ncurses.h>
int main()
{ int ch;
initscr(); /* Start curses mode */
raw(); /* Line buffering disabled */
keypad(stdscr, TRUE); /* We get F1, F2 etc.. */
noecho(); /* Don't echo() while we do getch */
printw("Type any character to see it in bold\n");
ch = getch(); /* If raw() hadn't been called
* we have to press enter before it
* gets to the program */
if(ch == KEY_F(1)) /* Without keypad enabled this will */
printw("F1 Key pressed");/* not get to us either */
/* Without noecho() some ugly escape
* charachters might have been printed
* on screen */
else
{ printw("The pressed key is ");
attron(A_BOLD);
printw("%c", ch);
attroff(A_BOLD);
}
refresh(); /* Print it on to the real screen */
getch(); /* Wait for user input */
endwin(); /* End curses mode */
return 0;
} |
此程序是不言自明的。但是我使用了尚未解释的函数。函数getch()用于从用户获取字符。它等效于普通的getchar(),但我们可以禁用行缓冲以避免输入后出现 <enter>。查找有关getch()的更多信息以及 <按键管理部分> 中的读取按键。函数 attron 和 attroff 用于分别打开和关闭某些属性。在示例中,我使用它们以粗体打印字符。这些函数将在稍后详细解释。
在我们深入研究无数的 ncurses 函数之前,让我澄清一些关于窗口的事情。窗口在以下 <章节> 中详细解释
窗口是 curses 系统定义的假想屏幕。窗口并不意味着您通常在 Win9X 平台上看到的带边框的窗口。当 curses 初始化时,它会创建一个名为stdscr的默认窗口,该窗口表示您的 80x25(或您正在运行的窗口大小)屏幕。如果您正在执行简单的任务,例如打印一些字符串、读取输入等,您可以安全地使用此单个窗口来完成所有目的。您还可以创建窗口并调用显式作用于指定窗口的函数。
例如,如果您调用
printw("Hi There !!!"); refresh(); |
它会在 stdscr 上的当前光标位置打印字符串。类似地,对 refresh() 的调用仅作用于 stdscr。
假设您已创建 <窗口>,那么您必须调用一个函数,并在常用函数中添加“w”。
wprintw(win, "Hi There !!!"); wrefresh(win); |
正如您将在文档的其余部分中看到的那样,函数的命名遵循相同的约定。对于每个函数,通常还有三个函数。
printw(string); /* Print on stdscr at present cursor position */ mvprintw(y, x, string);/* Move to (y, x) then print string */ wprintw(win, string); /* Print on window win at present cursor position */ /* in the window */ mvwprintw(win, y, x, string); /* Move to (y, x) relative to window */ /* co-ordinates and then print */ |
通常,无 w 函数是宏,它们扩展为相应的 w 函数,并将 stdscr 作为窗口参数。
我想您已经迫不及待地想看到一些操作了。回到我们的 curses 函数之旅。现在 curses 已初始化,让我们与世界互动。
您可以使用三类函数在屏幕上执行输出。
addch() 类:打印带有属性的单个字符
printw() 类:打印类似于 printf() 的格式化输出
addstr() 类:打印字符串
这些函数可以互换使用,使用哪一类函数只是风格问题。让我们详细了解每一类。
这些函数将单个字符放入当前光标位置并前进光标的位置。您可以给出要打印的字符,但它们通常用于打印具有某些属性的字符。属性在文档的后续 <章节> 中详细解释。如果字符与属性(粗体、反向视频等)关联,则当 curses 打印字符时,它将以该属性打印。
为了将字符与某些属性组合起来,您有两种选择
通过将单个字符与所需的属性宏进行 OR 运算。这些属性宏可以在头文件ncurses.h中找到。例如,您想以粗体和下划线打印字符 ch(字符类型),您可以按如下方式调用 addch()。
addch(ch | A_BOLD | A_UNDERLINE); |
通过使用诸如attrset()、attron()、attroff()之类的函数。这些函数在 <属性> 部分中解释。简而言之,它们操作给定窗口的当前属性。一旦设置,在窗口中打印的字符将与属性关联,直到属性被关闭。
此外,curses为基于字符的图形提供了一些特殊字符。您可以绘制表格、水平线或垂直线等。您可以在头文件ncurses.h中找到所有可用的字符。尝试在此文件中查找以ACS_开头的宏。
mvaddch()用于将光标移动到给定点,然后打印。因此,调用
move(row,col); /* moves the cursor to rowth row and colth column */ addch(ch); |
mvaddch(row,col,ch); |
waddch()代替,它类似于addch(),不同之处在于它将字符添加到给定的窗口中。(请注意,addch()将字符添加到窗口stdscr.)
类似地,mvwaddch()函数用于在给定坐标处的给定窗口中添加字符。
现在,我们熟悉了基本输出函数addch()。但是,如果我们想打印一个字符串,逐个字符地打印它会很烦人。幸运的是,ncurses提供了printf-like 或puts-like 函数。
这些函数类似于printf(),并增加了在屏幕上的任何位置打印的功能。
这两个函数的工作方式很像printf(). mvprintw()可以用于将光标移动到某个位置,然后打印。如果您想先移动光标,然后使用printw()函数打印,请首先使用move(),然后再使用printw(),尽管我不明白为什么要避免使用mvprintw(),但您可以灵活地进行操作。
示例 3. 一个简单的 printw 示例
#include <ncurses.h> /* ncurses.h includes stdio.h */
#include <string.h>
int main()
{
char mesg[]="Just a string"; /* message to be appeared on the screen */
int row,col; /* to store the number of rows and *
* the number of colums of the screen */
initscr(); /* start the curses mode */
getmaxyx(stdscr,row,col); /* get the number of rows and columns */
mvprintw(row/2,(col-strlen(mesg))/2,"%s",mesg);
/* print the message at the center of the screen */
mvprintw(row-2,0,"This screen has %d rows and %d columns\n",row,col);
printw("Try resizing your window(if possible) and then run this program again");
refresh();
getch();
endwin();
return 0;
} |
上面的程序演示了使用printw。 您只需提供坐标和屏幕上显示的消息,它就会执行您想要的操作。
上面的程序向我们介绍了一个新函数getmaxyx(),一个宏定义在ncurses.h中。 它给出了给定窗口中的列数和行数。getmaxyx()通过更新提供给它的变量来做到这一点。 因为getmaxyx()不是一个函数,我们不向它传递指针,我们只给出两个整型变量。
addstr()用于将字符串放入给定的窗口。 此函数类似于为给定字符串中的每个字符调用addch()一次。 这对于所有输出函数都是如此。 此系列中还有其他函数,例如mvaddstr(),mvwaddstr()和waddstr(),它们遵循 curses 的命名约定。(例如,mvaddstr() 类似于分别调用 move() 然后调用 addstr()。)此系列的另一个函数是 addnstr(),它另外接受一个整型参数(比如 n)。 此函数最多将 n 个字符放入屏幕。 如果 n 为负数,则将添加整个字符串。
所有这些函数都首先接受 y 坐标,然后在参数中接受 x 坐标。 初学者常犯的一个错误是以 x,y 的顺序传递。 如果您正在对 (y,x) 坐标进行太多操作,请考虑将屏幕划分为窗口并分别操作每个窗口。 窗口在 窗口 部分中解释。
嗯,没有输入的打印是很无聊的。 让我们看看允许我们从用户那里获取输入的函数。 这些函数也可以分为三类。
getch() 类:获取一个字符
scanw() 类:获取格式化输入
getstr() 类:获取字符串
这些函数从终端读取单个字符。 但是,有几个微妙的事实需要考虑。 例如,如果您不使用函数 cbreak(),curses 将不会连续读取您的输入字符,而是在遇到换行符或 EOF 后才开始读取它们。 为了避免这种情况,必须使用 cbreak() 函数,以便字符可以立即供您的程序使用。 另一个广泛使用的函数是 noecho()。 顾名思义,当设置(使用)此函数时,用户键入的字符不会显示在屏幕上。 cbreak() 和 noecho() 这两个函数是密钥管理的典型示例。 此类型的函数在 密钥管理部分 中进行了解释。
这些函数类似于scanf(),并增加了从屏幕上任何位置获取输入的功能。
这些函数用于从终端获取字符串。 本质上,此函数执行的任务与一系列调用getch()直到收到换行符、回车符或文件结尾符为止所实现的任务相同。 字符的结果字符串由str指向,这是一个用户提供的字符指针。
例 4. 一个简单的 scanw 示例
#include <ncurses.h> /* ncurses.h includes stdio.h */
#include <string.h>
int main()
{
char mesg[]="Enter a string: "; /* message to be appeared on the screen */
char str[80];
int row,col; /* to store the number of rows and *
* the number of colums of the screen */
initscr(); /* start the curses mode */
getmaxyx(stdscr,row,col); /* get the number of rows and columns */
mvprintw(row/2,(col-strlen(mesg))/2,"%s",mesg);
/* print the message at the center of the screen */
getstr(str);
mvprintw(LINES - 2, 0, "You Entered: %s", str);
getch();
endwin();
return 0;
} |
我们已经看到了如何使用属性来打印具有一些特殊效果的字符的示例。 当谨慎设置属性时,可以以简单易懂的方式呈现信息。 以下程序将 C 文件作为输入,并以粗体打印文件中的注释。 浏览代码。
例 5. 一个简单的属性示例
/* pager functionality by Joseph Spainhour" <spainhou@bellsouth.net> */
#include <ncurses.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int ch, prev, row, col;
prev = EOF;
FILE *fp;
int y, x;
if(argc != 2)
{
printf("Usage: %s <a c file name>\n", argv[0]);
exit(1);
}
fp = fopen(argv[1], "r");
if(fp == NULL)
{
perror("Cannot open input file");
exit(1);
}
initscr(); /* Start curses mode */
getmaxyx(stdscr, row, col); /* find the boundaries of the screeen */
while((ch = fgetc(fp)) != EOF) /* read the file till we reach the end */
{
getyx(stdscr, y, x); /* get the current curser position */
if(y == (row - 1)) /* are we are at the end of the screen */
{
printw("<-Press Any Key->"); /* tell the user to press a key */
getch();
clear(); /* clear the screen */
move(0, 0); /* start at the beginning of the screen */
}
if(prev == '/' && ch == '*') /* If it is / and * then only
* switch bold on */
{
attron(A_BOLD); /* cut bold on */
getyx(stdscr, y, x); /* get the current curser position */
move(y, x - 1); /* back up one space */
printw("%c%c", '/', ch); /* The actual printing is done here */
}
else
printw("%c", ch);
refresh();
if(prev == '*' && ch == '/')
attroff(A_BOLD); /* Switch it off once we got *
* and then / */
prev = ch;
}
endwin(); /* End curses mode */
fclose(fp);
return 0;
} |
不要担心所有这些初始化和其他废话。 专注于 while 循环。 它读取文件中的每个字符并搜索模式 /*。 一旦发现该模式,它就会使用attron()打开 BOLD 属性。 当我们得到模式 */ 时,它会被attroff() .
关闭。 上面的程序还向我们介绍了两个有用的函数getyx()和move()。 第一个函数将当前光标的坐标获取到变量 y、x 中。 由于 getyx() 是一个宏,我们不必向变量传递指针。 函数move()将光标移动到给定的坐标。
上面的程序确实是一个简单的程序,没有做太多事情。 在这些方面,人们可以编写一个更有用的程序,该程序读取 C 文件,解析它并以不同的颜色打印它。 人们甚至可以将其扩展到其他语言。
让我们深入了解属性的更多细节。 函数attron(), attroff(), attrset(),及其姊妹函数attr_get()等等。 可以用于打开/关闭属性,获取属性并产生彩色显示。
函数 attron 和 attroff 接受属性的位掩码,并分别打开或关闭它们。 以下视频属性在 <curses.h> 中定义,可以传递给这些函数。
A_NORMAL Normal display (no highlight) A_STANDOUT Best highlighting mode of the terminal. A_UNDERLINE Underlining A_REVERSE Reverse video A_BLINK Blinking A_DIM Half bright A_BOLD Extra bright or bold A_PROTECT Protected mode A_INVIS Invisible or blank mode A_ALTCHARSET Alternate character set A_CHARTEXT Bit-mask to extract a character COLOR_PAIR(n) Color-pair number n |
最后一个是最丰富多彩的 :-) 颜色在 下一节中解释。
我们可以 OR(|) 任意数量的上述属性以获得组合效果。 如果您想要带有闪烁字符的反向视频,您可以使用
attron(A_REVERSE | A_BLINK); |
那么 attron() 和 attrset() 之间有什么区别呢? attrset 设置窗口的属性,而 attron 只是打开给定的属性。 因此,attrset() 完全覆盖窗口先前拥有的任何属性,并将其设置为新属性。 同样,attroff() 只是关闭作为参数给定的属性。 这为我们提供了轻松管理属性的灵活性。 但是,如果您粗心使用它们,您可能会丢失窗口具有哪些属性的跟踪,并扰乱显示。 在使用颜色和突出显示管理菜单时尤其如此。 因此,请确定一致的策略并坚持下去。 您始终可以使用standend(),它等同于attrset(A_NORMAL),它关闭所有属性并将您带回正常模式。
函数 attr_get() 获取窗口的当前属性和颜色对。 虽然我们可能不会像上面函数那样经常使用它,但这在扫描屏幕区域时很有用。 假设我们想要在屏幕上进行一些复杂的更新,并且我们不确定每个字符与哪个属性相关联。 然后,此函数可以与 attrset 或 attron 一起使用以产生所需的效果。
chgat() 函数在手册页 curs_attr 的末尾列出。 它实际上是一个有用的函数。 此函数可用于设置一组字符的属性而无需移动。 我的意思是它!!! 无需移动光标 :-) 它更改从当前光标位置开始的给定数量的字符的属性。
我们可以将 -1 作为字符计数给出,以更新到行尾。 如果您想更改从当前位置到行尾的字符的属性,请使用此功能。
chgat(-1, A_REVERSE, 0, NULL); |
当更改屏幕上已有的字符的属性时,此函数很有用。 移动到您要更改的字符并更改属性。
其他函数 wchgat()、mvchgat()、wchgat() 的行为类似,只是 w 函数在特定窗口上运行。 mv 函数首先移动光标,然后执行给定的工作。 实际上,chgat 是一个宏,它被 wchgat() 替换,并将 stdscr 作为窗口。 大多数“无 w”函数都是宏。
例 6. Chgat() 用法示例
#include <ncurses.h>
int main(int argc, char *argv[])
{ initscr(); /* Start curses mode */
start_color(); /* Start color functionality */
init_pair(1, COLOR_CYAN, COLOR_BLACK);
printw("A Big string which i didn't care to type fully ");
mvchgat(0, 0, -1, A_BLINK, 1, NULL);
/*
* First two parameters specify the position at which to start
* Third parameter number of characters to update. -1 means till
* end of line
* Forth parameter is the normal attribute you wanted to give
* to the charcter
* Fifth is the color index. It is the index given during init_pair()
* use 0 if you didn't want color
* Sixth one is always NULL
*/
refresh();
getch();
endwin(); /* End curses mode */
return 0;
} |
此示例还向我们介绍了 curses 的色彩世界。 颜色将在稍后详细解释。 使用 0 表示无颜色。
窗口构成 curses 中最重要的概念。 您已经在上面的标准窗口 stdscr 中看到,所有函数都隐式地在此窗口上运行。 现在,要设计即使是最简单的 GUI,您也需要求助于窗口。 您可能想要使用窗口的主要原因是为了单独操作屏幕的各个部分,以提高效率,仅更新需要更改的窗口,并获得更好的设计。 我会说,最后一个原因是在窗口中最重要的原因。 您应该始终在程序中力求更好且易于管理的设计。 如果您正在编写大型、复杂的 GUI,这在您开始做任何事情之前都至关重要。
可以通过调用函数newwin()来创建窗口。 它实际上并没有在屏幕上创建任何东西。 它为结构分配内存以操作窗口,并使用有关窗口的数据(如大小、beginy、beginx 等)更新结构。 因此,在 curses 中,窗口只是一个虚构窗口的抽象,可以独立于屏幕的其他部分进行操作。 函数 newwin() 返回指向结构 WINDOW 的指针,该指针可以传递给窗口相关函数,如 wprintw() 等。 最后,可以使用 delwin() 销毁窗口。 它将释放与窗口结构关联的内存。
如果创建了一个窗口而我们看不到它,那有什么乐趣呢? 因此,乐趣的部分从显示窗口开始。 函数box()可用于在窗口周围绘制边框。 让我们在这个例子中更详细地探讨这些函数。
例 7. 窗口边框示例
#include <ncurses.h>
WINDOW *create_newwin(int height, int width, int starty, int startx);
void destroy_win(WINDOW *local_win);
int main(int argc, char *argv[])
{ WINDOW *my_win;
int startx, starty, width, height;
int ch;
initscr(); /* Start curses mode */
cbreak(); /* Line buffering disabled, Pass on
* everty thing to me */
keypad(stdscr, TRUE); /* I need that nifty F1 */
height = 3;
width = 10;
starty = (LINES - height) / 2; /* Calculating for a center placement */
startx = (COLS - width) / 2; /* of the window */
printw("Press F1 to exit");
refresh();
my_win = create_newwin(height, width, starty, startx);
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case KEY_LEFT:
destroy_win(my_win);
my_win = create_newwin(height, width, starty,--startx);
break;
case KEY_RIGHT:
destroy_win(my_win);
my_win = create_newwin(height, width, starty,++startx);
break;
case KEY_UP:
destroy_win(my_win);
my_win = create_newwin(height, width, --starty,startx);
break;
case KEY_DOWN:
destroy_win(my_win);
my_win = create_newwin(height, width, ++starty,startx);
break;
}
}
endwin(); /* End curses mode */
return 0;
}
WINDOW *create_newwin(int height, int width, int starty, int startx)
{ WINDOW *local_win;
local_win = newwin(height, width, starty, startx);
box(local_win, 0 , 0); /* 0, 0 gives default characters
* for the vertical and horizontal
* lines */
wrefresh(local_win); /* Show that box */
return local_win;
}
void destroy_win(WINDOW *local_win)
{
/* box(local_win, ' ', ' '); : This won't produce the desired
* result of erasing the window. It will leave it's four corners
* and so an ugly remnant of window.
*/
wborder(local_win, ' ', ' ', ' ',' ',' ',' ',' ',' ');
/* The parameters taken are
* 1. win: the window on which to operate
* 2. ls: character to be used for the left side of the window
* 3. rs: character to be used for the right side of the window
* 4. ts: character to be used for the top side of the window
* 5. bs: character to be used for the bottom side of the window
* 6. tl: character to be used for the top left corner of the window
* 7. tr: character to be used for the top right corner of the window
* 8. bl: character to be used for the bottom left corner of the window
* 9. br: character to be used for the bottom right corner of the window
*/
wrefresh(local_win);
delwin(local_win);
} |
不要尖叫。 我知道这是一个很大的例子。 但我必须在这里解释一些重要的事情 :-)。 此程序创建一个矩形窗口,可以使用左、右、上、下箭头键移动。 它在用户按下键时重复创建和销毁窗口。 不要超出屏幕限制。 检查这些限制留给读者练习。 让我们逐行剖析它。
函数create_newwin()使用newwin()创建一个窗口,并使用 box 在其周围显示边框。 函数destroy_win()首先通过用 ' ' 字符绘制边框来从屏幕上擦除窗口,然后调用delwin()以释放与之相关的内存。 根据用户按下的键,starty 或 startx 会发生变化,并创建一个新窗口。
在 destroy_win 中,您可以看到,我使用了 wborder 而不是 box。 原因写在注释中(您错过了它。我知道。阅读代码 :-))。 wborder 使用作为 4 个角点和 4 条线给出的字符在窗口周围绘制边框。 清楚地说,如果您像下面这样调用了 wborder
wborder(win, '|', '|', '-', '-', '+', '+', '+', '+'); |
,它会产生类似
+------------+ | | | | | | | | | | | | +------------+ |
您还可以在上面的示例中看到,我使用了变量 COLS、LINES,它们在 initscr() 之后被初始化为屏幕大小。 它们可用于查找屏幕尺寸和查找屏幕的中心坐标,如上所示。 函数getch()像往常一样从键盘获取键,并根据键执行相应的工作。 这种类型的 switch-case 在任何基于 GUI 的程序中都很常见。
上面的程序非常低效,因为每次按下键时,都会销毁一个窗口并创建另一个窗口。 因此,让我们编写一个更高效的程序,该程序使用其他边框相关函数。
以下程序使用mvhline()和mvvline()来实现类似的效果。 这两个函数很简单。 它们在指定位置创建指定长度的水平线或垂直线。
例 8. 更多边框函数
#include <ncurses.h>
typedef struct _win_border_struct {
chtype ls, rs, ts, bs,
tl, tr, bl, br;
}WIN_BORDER;
typedef struct _WIN_struct {
int startx, starty;
int height, width;
WIN_BORDER border;
}WIN;
void init_win_params(WIN *p_win);
void print_win_params(WIN *p_win);
void create_box(WIN *win, bool flag);
int main(int argc, char *argv[])
{ WIN win;
int ch;
initscr(); /* Start curses mode */
start_color(); /* Start the color functionality */
cbreak(); /* Line buffering disabled, Pass on
* everty thing to me */
keypad(stdscr, TRUE); /* I need that nifty F1 */
noecho();
init_pair(1, COLOR_CYAN, COLOR_BLACK);
/* Initialize the window parameters */
init_win_params(&win);
print_win_params(&win);
attron(COLOR_PAIR(1));
printw("Press F1 to exit");
refresh();
attroff(COLOR_PAIR(1));
create_box(&win, TRUE);
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case KEY_LEFT:
create_box(&win, FALSE);
--win.startx;
create_box(&win, TRUE);
break;
case KEY_RIGHT:
create_box(&win, FALSE);
++win.startx;
create_box(&win, TRUE);
break;
case KEY_UP:
create_box(&win, FALSE);
--win.starty;
create_box(&win, TRUE);
break;
case KEY_DOWN:
create_box(&win, FALSE);
++win.starty;
create_box(&win, TRUE);
break;
}
}
endwin(); /* End curses mode */
return 0;
}
void init_win_params(WIN *p_win)
{
p_win->height = 3;
p_win->width = 10;
p_win->starty = (LINES - p_win->height)/2;
p_win->startx = (COLS - p_win->width)/2;
p_win->border.ls = '|';
p_win->border.rs = '|';
p_win->border.ts = '-';
p_win->border.bs = '-';
p_win->border.tl = '+';
p_win->border.tr = '+';
p_win->border.bl = '+';
p_win->border.br = '+';
}
void print_win_params(WIN *p_win)
{
#ifdef _DEBUG
mvprintw(25, 0, "%d %d %d %d", p_win->startx, p_win->starty,
p_win->width, p_win->height);
refresh();
#endif
}
void create_box(WIN *p_win, bool flag)
{ int i, j;
int x, y, w, h;
x = p_win->startx;
y = p_win->starty;
w = p_win->width;
h = p_win->height;
if(flag == TRUE)
{ mvaddch(y, x, p_win->border.tl);
mvaddch(y, x + w, p_win->border.tr);
mvaddch(y + h, x, p_win->border.bl);
mvaddch(y + h, x + w, p_win->border.br);
mvhline(y, x + 1, p_win->border.ts, w - 1);
mvhline(y + h, x + 1, p_win->border.bs, w - 1);
mvvline(y + 1, x, p_win->border.ls, h - 1);
mvvline(y + 1, x + w, p_win->border.rs, h - 1);
}
else
for(j = y; j <= y + h; ++j)
for(i = x; i <= x + w; ++i)
mvaddch(j, i, ' ');
refresh();
} |
没有颜色的生活似乎很枯燥。 Curses 有一个很好的机制来处理颜色。 让我们用一个小程序深入了解事物的本质。
例 9. 一个简单的颜色示例
#include <ncurses.h>
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string);
int main(int argc, char *argv[])
{ initscr(); /* Start curses mode */
if(has_colors() == FALSE)
{ endwin();
printf("Your terminal does not support color\n");
exit(1);
}
start_color(); /* Start color */
init_pair(1, COLOR_RED, COLOR_BLACK);
attron(COLOR_PAIR(1));
print_in_middle(stdscr, LINES / 2, 0, 0, "Viola !!! In color ...");
attroff(COLOR_PAIR(1));
getch();
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();
}
|
正如您所看到的,要开始使用颜色,您应该首先调用函数start_color()。 之后,您可以使用终端的颜色功能,使用各种函数。 要找出终端是否具有颜色功能,您可以使用has_colors()函数,如果终端不支持颜色,则返回 FALSE。
当调用 start_color() 时,Curses 初始化终端支持的所有颜色。 这些可以通过定义的常量访问,例如COLOR_BLACK等等。 现在要真正开始使用颜色,您必须定义对。 颜色总是成对使用。 这意味着您必须使用函数init_pair()来为您给出的对号定义前景色和背景色。 之后,该对号可以作为普通属性与COLOR_PAIR()函数一起使用。 这起初似乎很麻烦。 但这种优雅的解决方案使我们能够非常轻松地管理颜色对。 为了欣赏它,您必须查看“dialog”的源代码,这是一个用于从 shell 脚本显示对话框的实用程序。 开发人员为他们可能需要的所有颜色定义了前景和背景组合,并在开始时进行了初始化。 这使得仅通过访问我们已经定义为常量的对来设置属性非常容易。
以下颜色在curses.h中定义。 您可以将这些用作各种颜色函数的参数。
COLOR_BLACK 0 COLOR_RED 1 COLOR_GREEN 2 COLOR_YELLOW 3 COLOR_BLUE 4 COLOR_MAGENTA 5 COLOR_CYAN 6 COLOR_WHITE 7 |
函数init_color()可用于更改 curses 最初定义的颜色的 rgb 值。 假设您想要稍微减轻红色颜色的强度。 然后,您可以使用此函数,如下所示
init_color(COLOR_RED, 700, 0, 0); /* param 1 : color name * param 2, 3, 4 : rgb content min = 0, max = 1000 */ |
如果您的终端无法更改颜色定义,则该函数返回 ERR。 函数can_change_color()可用于找出终端是否具有更改颜色内容的功能。 rgb 内容的比例从 0 到 1000。 最初,红色定义的内容为 1000(r)、0(g)、0(b)。
没有强大的用户界面,GUI 是不完整的,为了与用户交互,curses 程序应该对用户按键或鼠标操作敏感。 让我们先处理按键。
正如您在几乎所有上述示例中看到的那样,从用户那里获取按键输入非常容易。 获取按键的一种简单方法是使用getch()函数。 应该启用 cbreak 模式,以便在您有兴趣读取单个按键而不是整行文本(通常以回车符结尾)时读取键。 应启用键盘,以获取功能键、箭头键等。 有关详细信息,请参阅初始化部分。
getch()返回与按下的键对应的整数。 如果是普通字符,则整数值将等效于该字符。 否则,它返回一个数字,该数字可以与curses.h中定义的常量匹配。 例如,如果用户按下 F1,则返回的整数为 265。这可以使用 curses.h 中定义的宏 KEY_F() 来检查。 这使得读取键可移植且易于管理。
例如,如果您像这样调用 getch()
int ch; ch = getch(); |
getch() 将等待用户按下键(除非您指定了超时),当用户按下键时,将返回相应的整数。 然后,您可以检查返回的值与 curses.h 中定义的常量进行匹配,以对照您想要的键。
以下代码片段将完成该工作。
if(ch == KEY_LEFT) printw("Left arrow is pressed\n"); |
让我们编写一个小程序,创建一个可以使用向上和向下箭头导航的菜单。
例 10. 一个简单的密钥用法示例
#include <stdio.h>
#include <ncurses.h>
#define WIDTH 30
#define HEIGHT 10
int startx = 0;
int starty = 0;
char *choices[] = {
"Choice 1",
"Choice 2",
"Choice 3",
"Choice 4",
"Exit",
};
int n_choices = sizeof(choices) / sizeof(char *);
void print_menu(WINDOW *menu_win, int highlight);
int main()
{ WINDOW *menu_win;
int highlight = 1;
int choice = 0;
int c;
initscr();
clear();
noecho();
cbreak(); /* Line buffering disabled. pass on everything */
startx = (80 - WIDTH) / 2;
starty = (24 - HEIGHT) / 2;
menu_win = newwin(HEIGHT, WIDTH, starty, startx);
keypad(menu_win, TRUE);
mvprintw(0, 0, "Use arrow keys to go up and down, Press enter to select a choice");
refresh();
print_menu(menu_win, highlight);
while(1)
{ c = wgetch(menu_win);
switch(c)
{ case KEY_UP:
if(highlight == 1)
highlight = n_choices;
else
--highlight;
break;
case KEY_DOWN:
if(highlight == n_choices)
highlight = 1;
else
++highlight;
break;
case 10:
choice = highlight;
break;
default:
mvprintw(24, 0, "Charcter pressed is = %3d Hopefully it can be printed as '%c'", c, c);
refresh();
break;
}
print_menu(menu_win, highlight);
if(choice != 0) /* User did a choice come out of the infinite loop */
break;
}
mvprintw(23, 0, "You chose choice %d with choice string %s\n", choice, choices[choice - 1]);
clrtoeol();
refresh();
endwin();
return 0;
}
void print_menu(WINDOW *menu_win, int highlight)
{
int x, y, i;
x = 2;
y = 2;
box(menu_win, 0, 0);
for(i = 0; i < n_choices; ++i)
{ if(highlight == i + 1) /* High light the present choice */
{ wattron(menu_win, A_REVERSE);
mvwprintw(menu_win, y, x, "%s", choices[i]);
wattroff(menu_win, A_REVERSE);
}
else
mvwprintw(menu_win, y, x, "%s", choices[i]);
++y;
}
wrefresh(menu_win);
}
|
现在您已经了解了如何获取键,让我们从鼠标执行相同的操作。 通常,每个 UI 都允许用户与键盘和鼠标进行交互。
在您执行任何其他操作之前,您要接收的事件必须使用mousemask().
mousemask( mmask_t newmask, /* The events you want to listen to */ mmask_t *oldmask) /* The old events mask */ |
启用。 上述函数的第一个参数是您要侦听的事件的位掩码。 默认情况下,所有事件都已关闭。 位掩码ALL_MOUSE_EVENTS可用于获取所有事件。
以下是所有事件掩码
Name Description --------------------------------------------------------------------- BUTTON1_PRESSED mouse button 1 down BUTTON1_RELEASED mouse button 1 up BUTTON1_CLICKED mouse button 1 clicked BUTTON1_DOUBLE_CLICKED mouse button 1 double clicked BUTTON1_TRIPLE_CLICKED mouse button 1 triple clicked BUTTON2_PRESSED mouse button 2 down BUTTON2_RELEASED mouse button 2 up BUTTON2_CLICKED mouse button 2 clicked BUTTON2_DOUBLE_CLICKED mouse button 2 double clicked BUTTON2_TRIPLE_CLICKED mouse button 2 triple clicked BUTTON3_PRESSED mouse button 3 down BUTTON3_RELEASED mouse button 3 up BUTTON3_CLICKED mouse button 3 clicked BUTTON3_DOUBLE_CLICKED mouse button 3 double clicked BUTTON3_TRIPLE_CLICKED mouse button 3 triple clicked BUTTON4_PRESSED mouse button 4 down BUTTON4_RELEASED mouse button 4 up BUTTON4_CLICKED mouse button 4 clicked BUTTON4_DOUBLE_CLICKED mouse button 4 double clicked BUTTON4_TRIPLE_CLICKED mouse button 4 triple clicked BUTTON_SHIFT shift was down during button state change BUTTON_CTRL control was down during button state change BUTTON_ALT alt was down during button state change ALL_MOUSE_EVENTS report all button state changes REPORT_MOUSE_POSITION report mouse movement |
一旦启用了一类鼠标事件,每次发生某些鼠标事件时,getch() 函数类都会返回 KEY_MOUSE。 然后可以使用getmouse().
检索鼠标事件。 代码大致如下所示
MEVENT event; ch = getch(); if(ch == KEY_MOUSE) if(getmouse(&event) == OK) . /* Do some thing with the event */ . . |
getmouse() 将事件返回到给定的指针中。 它是一个结构,包含
typedef struct { short id; /* ID to distinguish multiple devices */ int x, y, z; /* event coordinates */ mmask_t bstate; /* button state bits */ } |
函数bstate是我们感兴趣的主要变量。 它告诉鼠标的按钮状态。
然后使用如下代码片段,我们可以找出发生了什么。
if(event.bstate & BUTTON1_PRESSED) printw("Left Button Pressed"); |
这几乎就是与鼠标交互。 让我们创建相同的菜单并启用鼠标交互。 为了使事情更简单,删除了按键处理。
例 11. 使用鼠标访问菜单!!!
#include <ncurses.h>
#define WIDTH 30
#define HEIGHT 10
int startx = 0;
int starty = 0;
char *choices[] = { "Choice 1",
"Choice 2",
"Choice 3",
"Choice 4",
"Exit",
};
int n_choices = sizeof(choices) / sizeof(char *);
void print_menu(WINDOW *menu_win, int highlight);
void report_choice(int mouse_x, int mouse_y, int *p_choice);
int main()
{ int c, choice = 0;
WINDOW *menu_win;
MEVENT event;
/* Initialize curses */
initscr();
clear();
noecho();
cbreak(); //Line buffering disabled. pass on everything
/* Try to put the window in the middle of screen */
startx = (80 - WIDTH) / 2;
starty = (24 - HEIGHT) / 2;
attron(A_REVERSE);
mvprintw(23, 1, "Click on Exit to quit (Works best in a virtual console)");
refresh();
attroff(A_REVERSE);
/* Print the menu for the first time */
menu_win = newwin(HEIGHT, WIDTH, starty, startx);
print_menu(menu_win, 1);
/* Get all the mouse events */
mousemask(ALL_MOUSE_EVENTS, NULL);
while(1)
{ c = wgetch(menu_win);
switch(c)
{ case KEY_MOUSE:
if(getmouse(&event) == OK)
{ /* When the user clicks left mouse button */
if(event.bstate & BUTTON1_PRESSED)
{ report_choice(event.x + 1, event.y + 1, &choice);
if(choice == -1) //Exit chosen
goto end;
mvprintw(22, 1, "Choice made is : %d String Chosen is \"%10s\"", choice, choices[choice - 1]);
refresh();
}
}
print_menu(menu_win, choice);
break;
}
}
end:
endwin();
return 0;
}
void print_menu(WINDOW *menu_win, int highlight)
{
int x, y, i;
x = 2;
y = 2;
box(menu_win, 0, 0);
for(i = 0; i < n_choices; ++i)
{ if(highlight == i + 1)
{ wattron(menu_win, A_REVERSE);
mvwprintw(menu_win, y, x, "%s", choices[i]);
wattroff(menu_win, A_REVERSE);
}
else
mvwprintw(menu_win, y, x, "%s", choices[i]);
++y;
}
wrefresh(menu_win);
}
/* Report the choice according to mouse position */
void report_choice(int mouse_x, int mouse_y, int *p_choice)
{ int i,j, choice;
i = startx + 2;
j = starty + 3;
for(choice = 0; choice < n_choices; ++choice)
if(mouse_y == j + choice && mouse_x >= i && mouse_x <= i + strlen(choices[choice]))
{ if(choice == n_choices - 1)
*p_choice = -1;
else
*p_choice = choice + 1;
break;
}
} |
在本节中,我们将研究一些函数,这些函数允许我们有效地管理屏幕并编写一些花哨的程序。 这在编写游戏时尤为重要。
函数getyx()可用于找出当前光标坐标。 它将在给定的参数中填充 x 和 y 坐标的值。 由于 getyx() 是一个宏,因此您不必传递变量的地址。 它可以被称为
getyx(win, y, x); /* win: window pointer * y, x: y, x co-ordinates will be put into this variables */ |
函数 getparyx() 获取子窗口相对于主窗口的起始坐标。 有时这对于更新子窗口很有用。 在设计花哨的东西(如编写多个菜单)时,存储菜单位置、它们的第一个选项坐标等变得困难。 解决此问题的一个简单方法是在子窗口中创建菜单,然后稍后使用 getparyx() 查找菜单的起始坐标。
函数 getbegyx() 和 getmaxyx() 存储当前窗口的起始坐标和最大坐标。 这些函数在管理窗口和子窗口方面与上面相同的方式很有用。
现在您已经了解了足够的功能来编写一个好的 curses 程序,具有所有花里胡哨的功能。 在各种情况下,还有一些其他有用的函数。 让我们直接进入其中一些。
有时您可能想要暂时返回到 cooked 模式(正常行缓冲模式)。 在这种情况下,您首先需要通过调用def_prog_mode()来保存 tty 模式,然后调用endwin()以结束 curses 模式。 这将使您处于原始 tty 模式。 要在完成后返回到 curses,请调用reset_prog_mode()。 此函数将 tty 返回到由def_prog_mode()存储的状态。 然后执行 refresh(),您将返回到 curses 模式。 这是一个示例,显示了要完成的操作顺序。
例 12. 暂时离开 Curses 模式
#include <ncurses.h>
int main()
{
initscr(); /* Start curses mode */
printw("Hello World !!!\n"); /* Print Hello World */
refresh(); /* Print it on to the real screen */
def_prog_mode(); /* Save the tty modes */
endwin(); /* End curses mode temporarily */
system("/bin/sh"); /* Do whatever you like in cooked mode */
reset_prog_mode(); /* Return to the previous tty mode*/
/* stored by def_prog_mode() */
refresh(); /* Do refresh() to restore the */
/* Screen contents */
printw("Another String\n"); /* Back to curses use the full */
refresh(); /* capabilities of curses */
endwin(); /* End curses mode */
return 0;
} |
如果您曾经在 DOS 中编程,您就会了解扩展字符集中的那些漂亮的字符。 它们只能在某些终端上打印。 NCURSES 函数,如box()使用这些字符。 所有这些变量都以 ACS 开头,意思是替代字符集。 您可能已经注意到我在上面的一些程序中使用了这些字符。 这是一个显示所有字符的示例。
例 13. ACS 变量示例
#include <ncurses.h>
int main()
{
initscr();
printw("Upper left corner "); addch(ACS_ULCORNER); printw("\n");
printw("Lower left corner "); addch(ACS_LLCORNER); printw("\n");
printw("Lower right corner "); addch(ACS_LRCORNER); printw("\n");
printw("Tee pointing right "); addch(ACS_LTEE); printw("\n");
printw("Tee pointing left "); addch(ACS_RTEE); printw("\n");
printw("Tee pointing up "); addch(ACS_BTEE); printw("\n");
printw("Tee pointing down "); addch(ACS_TTEE); printw("\n");
printw("Horizontal line "); addch(ACS_HLINE); printw("\n");
printw("Vertical line "); addch(ACS_VLINE); printw("\n");
printw("Large Plus or cross over "); addch(ACS_PLUS); printw("\n");
printw("Scan Line 1 "); addch(ACS_S1); printw("\n");
printw("Scan Line 3 "); addch(ACS_S3); printw("\n");
printw("Scan Line 7 "); addch(ACS_S7); printw("\n");
printw("Scan Line 9 "); addch(ACS_S9); printw("\n");
printw("Diamond "); addch(ACS_DIAMOND); printw("\n");
printw("Checker board (stipple) "); addch(ACS_CKBOARD); printw("\n");
printw("Degree Symbol "); addch(ACS_DEGREE); printw("\n");
printw("Plus/Minus Symbol "); addch(ACS_PLMINUS); printw("\n");
printw("Bullet "); addch(ACS_BULLET); printw("\n");
printw("Arrow Pointing Left "); addch(ACS_LARROW); printw("\n");
printw("Arrow Pointing Right "); addch(ACS_RARROW); printw("\n");
printw("Arrow Pointing Down "); addch(ACS_DARROW); printw("\n");
printw("Arrow Pointing Up "); addch(ACS_UARROW); printw("\n");
printw("Board of squares "); addch(ACS_BOARD); printw("\n");
printw("Lantern Symbol "); addch(ACS_LANTERN); printw("\n");
printw("Solid Square Block "); addch(ACS_BLOCK); printw("\n");
printw("Less/Equal sign "); addch(ACS_LEQUAL); printw("\n");
printw("Greater/Equal sign "); addch(ACS_GEQUAL); printw("\n");
printw("Pi "); addch(ACS_PI); printw("\n");
printw("Not equal "); addch(ACS_NEQUAL); printw("\n");
printw("UK pound sign "); addch(ACS_STERLING); printw("\n");
refresh();
getch();
endwin();
return 0;
} |
现在您已经精通 curses,您想要做一些大的事情。 您创建了许多重叠的窗口,以提供专业的窗口类型外观。 不幸的是,很快就变得难以管理这些窗口。 多次刷新、更新使您陷入噩梦。 每当您忘记以正确的顺序刷新窗口时,重叠的窗口都会产生污点。
不要绝望。 面板库中提供了一个优雅的解决方案。 用 ncurses 开发人员的话来说
当您的界面设计使得窗口可能在运行时更深入地潜入可见性堆栈或弹出到顶部时,由此产生的簿记工作可能既繁琐又难以做好。 因此有了面板库。
如果您有很多重叠的窗口,那么面板库是最佳选择。 它避免了进行一系列 wnoutrefresh()、doupdate() 的需要,并减轻了正确执行它的负担(自下而上)。 该库维护有关窗口顺序、它们的重叠的信息,并正确更新屏幕。 那还在等什么? 让我们仔细看看面板。
面板对象是一个窗口,它被隐式地视为包含所有其他面板对象的甲板的一部分。 甲板被视为堆栈,顶部面板完全可见,其他面板可能会或可能不会根据其位置而被遮挡。 因此,基本思想是创建一个重叠面板堆栈,并使用面板库来正确显示它们。 有一个类似于 refresh() 的函数,当调用时,以正确的顺序显示面板。 提供了用于隐藏或显示面板、移动面板、更改其大小等的功能。 在所有对这些函数的调用期间,重叠问题由面板库管理。
面板程序的一般流程如下
创建要附加到面板的窗口(使用 newwin())。
使用选定的可见性顺序创建面板。 根据所需的可见性堆叠它们。 函数 new_panel() 用于创建面板。
调用 update_panels() 将面板以正确的可见性顺序写入虚拟屏幕。 执行 doupdate() 以在屏幕上显示它。
使用 show_panel()、hide_panel()、move_panel() 等操作面板。 利用辅助函数,如 panel_hidden() 和 panel_window()。 利用用户指针存储面板的自定义数据。 使用函数 set_panel_userptr() 和 panel_userptr() 设置和获取面板的用户指针。
当您完成面板操作后,使用 del_panel() 删除面板。
让我们用一些程序来阐明概念。 以下是一个简单的程序,它创建 3 个重叠的面板并将它们显示在屏幕上。
要使用面板库函数,您必须包含 panel.h,并且要将程序与面板库链接,应按顺序添加标志 -lpanel 以及 -lncurses。
#include <panel.h> . . . compile and link: gcc <program file> -lpanel -lncurses |
例 14. 面板基础知识
#include <panel.h>
int main()
{ WINDOW *my_wins[3];
PANEL *my_panels[3];
int lines = 10, cols = 40, y = 2, x = 4, i;
initscr();
cbreak();
noecho();
/* Create windows for the panels */
my_wins[0] = newwin(lines, cols, y, x);
my_wins[1] = newwin(lines, cols, y + 1, x + 5);
my_wins[2] = newwin(lines, cols, y + 2, x + 10);
/*
* Create borders around the windows so that you can see the effect
* of panels
*/
for(i = 0; i < 3; ++i)
box(my_wins[i], 0, 0);
/* Attach a panel to each window */ /* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
doupdate();
getch();
endwin();
}
|
正如您所看到的,上面的程序遵循了如上所述的简单流程。 窗口是使用 newwin() 创建的,然后使用 new_panel() 将它们附加到面板。 当我们一个接一个地附加面板时,面板堆栈会更新。 要将它们放在屏幕上,请调用 update_panels() 和 doupdate()。
下面给出了一个稍微复杂的例子。 此程序创建 3 个窗口,可以使用 Tab 键循环浏览这些窗口。 看一下代码。
例 15. 面板窗口浏览示例
#include <panel.h>
#define NLINES 10
#define NCOLS 40
void init_wins(WINDOW **wins, int n);
void win_show(WINDOW *win, char *label, int label_color);
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);
int main()
{ WINDOW *my_wins[3];
PANEL *my_panels[3];
PANEL *top;
int ch;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize all the colors */
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
init_pair(3, COLOR_BLUE, COLOR_BLACK);
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_wins(my_wins, 3);
/* Attach a panel to each window */ /* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
/* Set up the user pointers to the next panel */
set_panel_userptr(my_panels[0], my_panels[1]);
set_panel_userptr(my_panels[1], my_panels[2]);
set_panel_userptr(my_panels[2], my_panels[0]);
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
attron(COLOR_PAIR(4));
mvprintw(LINES - 2, 0, "Use tab to browse through the windows (F1 to Exit)");
attroff(COLOR_PAIR(4));
doupdate();
top = my_panels[2];
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case 9:
top = (PANEL *)panel_userptr(top);
top_panel(top);
break;
}
update_panels();
doupdate();
}
endwin();
return 0;
}
/* Put all the windows */
void init_wins(WINDOW **wins, int n)
{ int x, y, i;
char label[80];
y = 2;
x = 10;
for(i = 0; i < n; ++i)
{ wins[i] = newwin(NLINES, NCOLS, y, x);
sprintf(label, "Window Number %d", i + 1);
win_show(wins[i], label, i + 1);
y += 3;
x += 7;
}
}
/* Show the window with a border and a label */
void win_show(WINDOW *win, char *label, int label_color)
{ int startx, starty, height, width;
getbegyx(win, starty, startx);
getmaxyx(win, height, width);
box(win, 0, 0);
mvwaddch(win, 2, 0, ACS_LTEE);
mvwhline(win, 2, 1, ACS_HLINE, width - 2);
mvwaddch(win, 2, width - 1, ACS_RTEE);
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
}
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_panel_userptr()设置面板的用户指针。 可以使用函数panel_userptr()访问它,该函数将返回作为参数给出的面板的用户指针。 在找到循环中的下一个面板后,使用函数 top_panel() 将其带到顶部。 此函数将作为参数给出的面板带到面板堆栈的顶部。
函数move_panel()可用于将面板移动到所需位置。 它不会更改面板在堆栈中的位置。 确保您在与面板关联的窗口上使用 move_panel() 而不是 mvwin()。
调整面板大小稍微复杂。 没有直接的函数可以仅调整与面板关联的窗口的大小。 调整面板大小的解决方案是创建一个具有所需大小的新窗口,使用 replace_panel() 更改与面板关联的窗口。 不要忘记删除旧窗口。 可以使用函数 panel_window() 找到与面板关联的窗口。
以下程序在所谓的简单程序中显示了这些概念。 您可以像往常一样使用 <TAB> 键循环浏览窗口。 要调整活动面板的大小或移动活动面板,请按 'r' 调整大小,按 'm' 移动。 然后使用箭头键调整大小或将其移动到所需的方式,然后按 Enter 键结束调整大小或移动。 此示例利用用户数据来获取执行操作所需的数据。
例 16. 面板移动和调整大小示例
#include <panel.h>
typedef struct _PANEL_DATA {
int x, y, w, h;
char label[80];
int label_color;
PANEL *next;
}PANEL_DATA;
#define NLINES 10
#define NCOLS 40
void init_wins(WINDOW **wins, int n);
void win_show(WINDOW *win, char *label, int label_color);
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);
void set_user_ptrs(PANEL **panels, int n);
int main()
{ WINDOW *my_wins[3];
PANEL *my_panels[3];
PANEL_DATA *top;
PANEL *stack_top;
WINDOW *temp_win, *old_win;
int ch;
int newx, newy, neww, newh;
int size = FALSE, move = FALSE;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize all the colors */
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
init_pair(3, COLOR_BLUE, COLOR_BLACK);
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_wins(my_wins, 3);
/* Attach a panel to each window */ /* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
set_user_ptrs(my_panels, 3);
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
attron(COLOR_PAIR(4));
mvprintw(LINES - 3, 0, "Use 'm' for moving, 'r' for resizing");
mvprintw(LINES - 2, 0, "Use tab to browse through the windows (F1 to Exit)");
attroff(COLOR_PAIR(4));
doupdate();
stack_top = my_panels[2];
top = (PANEL_DATA *)panel_userptr(stack_top);
newx = top->x;
newy = top->y;
neww = top->w;
newh = top->h;
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case 9: /* Tab */
top = (PANEL_DATA *)panel_userptr(stack_top);
top_panel(top->next);
stack_top = top->next;
top = (PANEL_DATA *)panel_userptr(stack_top);
newx = top->x;
newy = top->y;
neww = top->w;
newh = top->h;
break;
case 'r': /* Re-Size*/
size = TRUE;
attron(COLOR_PAIR(4));
mvprintw(LINES - 4, 0, "Entered Resizing :Use Arrow Keys to resize and press <ENTER> to end resizing");
refresh();
attroff(COLOR_PAIR(4));
break;
case 'm': /* Move */
attron(COLOR_PAIR(4));
mvprintw(LINES - 4, 0, "Entered Moving: Use Arrow Keys to Move and press <ENTER> to end moving");
refresh();
attroff(COLOR_PAIR(4));
move = TRUE;
break;
case KEY_LEFT:
if(size == TRUE)
{ --newx;
++neww;
}
if(move == TRUE)
--newx;
break;
case KEY_RIGHT:
if(size == TRUE)
{ ++newx;
--neww;
}
if(move == TRUE)
++newx;
break;
case KEY_UP:
if(size == TRUE)
{ --newy;
++newh;
}
if(move == TRUE)
--newy;
break;
case KEY_DOWN:
if(size == TRUE)
{ ++newy;
--newh;
}
if(move == TRUE)
++newy;
break;
case 10: /* Enter */
move(LINES - 4, 0);
clrtoeol();
refresh();
if(size == TRUE)
{ old_win = panel_window(stack_top);
temp_win = newwin(newh, neww, newy, newx);
replace_panel(stack_top, temp_win);
win_show(temp_win, top->label, top->label_color);
delwin(old_win);
size = FALSE;
}
if(move == TRUE)
{ move_panel(stack_top, newy, newx);
move = FALSE;
}
break;
}
attron(COLOR_PAIR(4));
mvprintw(LINES - 3, 0, "Use 'm' for moving, 'r' for resizing");
mvprintw(LINES - 2, 0, "Use tab to browse through the windows (F1 to Exit)");
attroff(COLOR_PAIR(4));
refresh();
update_panels();
doupdate();
}
endwin();
return 0;
}
/* Put all the windows */
void init_wins(WINDOW **wins, int n)
{ int x, y, i;
char label[80];
y = 2;
x = 10;
for(i = 0; i < n; ++i)
{ wins[i] = newwin(NLINES, NCOLS, y, x);
sprintf(label, "Window Number %d", i + 1);
win_show(wins[i], label, i + 1);
y += 3;
x += 7;
}
}
/* Set the PANEL_DATA structures for individual panels */
void set_user_ptrs(PANEL **panels, int n)
{ PANEL_DATA *ptrs;
WINDOW *win;
int x, y, w, h, i;
char temp[80];
ptrs = (PANEL_DATA *)calloc(n, sizeof(PANEL_DATA));
for(i = 0;i < n; ++i)
{ win = panel_window(panels[i]);
getbegyx(win, y, x);
getmaxyx(win, h, w);
ptrs[i].x = x;
ptrs[i].y = y;
ptrs[i].w = w;
ptrs[i].h = h;
sprintf(temp, "Window Number %d", i + 1);
strcpy(ptrs[i].label, temp);
ptrs[i].label_color = i + 1;
if(i + 1 == n)
ptrs[i].next = panels[0];
else
ptrs[i].next = panels[i + 1];
set_panel_userptr(panels[i], &ptrs[i]);
}
}
/* Show the window with a border and a label */
void win_show(WINDOW *win, char *label, int label_color)
{ int startx, starty, height, width;
getbegyx(win, starty, startx);
getmaxyx(win, height, width);
box(win, 0, 0);
mvwaddch(win, 2, 0, ACS_LTEE);
mvwhline(win, 2, 1, ACS_HLINE, width - 2);
mvwaddch(win, 2, width - 1, ACS_RTEE);
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
}
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();
} |
专注于主 while 循环。 一旦它找出按下的键类型,它就会采取适当的措施。 如果按下 'r',则启动调整大小模式。 在此之后,当用户按下箭头键时,新大小会更新。 当用户按下 <ENTER> 键时,当前选择结束,并通过使用解释的概念调整面板大小。 在调整大小模式下,程序不会显示窗口如何调整大小。 将其作为练习留给读者,以便在调整大小到新位置时打印虚线边框。
当用户按下 'm' 时,移动模式启动。 这比调整大小简单一些。 当按下箭头键时,新位置会更新,并且按下 <ENTER> 键会导致通过调用函数 move_panel() 来移动面板。
在此程序中,用户数据(表示为 PANEL_DATA)在查找与面板关联的信息方面起着非常重要的作用。 正如注释中所写,PANEL_DATA 存储面板大小、标签、标签颜色以及指向循环中下一个面板的指针。
可以使用函数 hide_panel() 隐藏面板。 此函数仅将其从面板堆栈中移除,从而在您执行 update_panels() 和 doupdate() 后将其隐藏在屏幕上。 它不会销毁与隐藏面板关联的 PANEL 结构。 可以使用 show_panel() 函数再次显示它。
以下程序显示了面板的隐藏。 按 'a' 或 'b' 或 'c' 分别显示或隐藏第一个、第二个和第三个窗口。 它使用带有小变量 hide 的用户数据,该变量跟踪窗口是否隐藏。 由于某些原因,函数panel_hidden()无法正常工作,该函数指示面板是否隐藏。 Michael Andres 也提交了一个错误报告 此处
例 17. 面板隐藏和显示示例
#include <panel.h>
typedef struct _PANEL_DATA {
int hide; /* TRUE if panel is hidden */
}PANEL_DATA;
#define NLINES 10
#define NCOLS 40
void init_wins(WINDOW **wins, int n);
void win_show(WINDOW *win, char *label, int label_color);
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);
int main()
{ WINDOW *my_wins[3];
PANEL *my_panels[3];
PANEL_DATA panel_datas[3];
PANEL_DATA *temp;
int ch;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize all the colors */
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_GREEN, COLOR_BLACK);
init_pair(3, COLOR_BLUE, COLOR_BLACK);
init_pair(4, COLOR_CYAN, COLOR_BLACK);
init_wins(my_wins, 3);
/* Attach a panel to each window */ /* Order is bottom up */
my_panels[0] = new_panel(my_wins[0]); /* Push 0, order: stdscr-0 */
my_panels[1] = new_panel(my_wins[1]); /* Push 1, order: stdscr-0-1 */
my_panels[2] = new_panel(my_wins[2]); /* Push 2, order: stdscr-0-1-2 */
/* Initialize panel datas saying that nothing is hidden */
panel_datas[0].hide = FALSE;
panel_datas[1].hide = FALSE;
panel_datas[2].hide = FALSE;
set_panel_userptr(my_panels[0], &panel_datas[0]);
set_panel_userptr(my_panels[1], &panel_datas[1]);
set_panel_userptr(my_panels[2], &panel_datas[2]);
/* Update the stacking order. 2nd panel will be on top */
update_panels();
/* Show it on the screen */
attron(COLOR_PAIR(4));
mvprintw(LINES - 3, 0, "Show or Hide a window with 'a'(first window) 'b'(Second Window) 'c'(Third Window)");
mvprintw(LINES - 2, 0, "F1 to Exit");
attroff(COLOR_PAIR(4));
doupdate();
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case 'a':
temp = (PANEL_DATA *)panel_userptr(my_panels[0]);
if(temp->hide == FALSE)
{ hide_panel(my_panels[0]);
temp->hide = TRUE;
}
else
{ show_panel(my_panels[0]);
temp->hide = FALSE;
}
break;
case 'b':
temp = (PANEL_DATA *)panel_userptr(my_panels[1]);
if(temp->hide == FALSE)
{ hide_panel(my_panels[1]);
temp->hide = TRUE;
}
else
{ show_panel(my_panels[1]);
temp->hide = FALSE;
}
break;
case 'c':
temp = (PANEL_DATA *)panel_userptr(my_panels[2]);
if(temp->hide == FALSE)
{ hide_panel(my_panels[2]);
temp->hide = TRUE;
}
else
{ show_panel(my_panels[2]);
temp->hide = FALSE;
}
break;
}
update_panels();
doupdate();
}
endwin();
return 0;
}
/* Put all the windows */
void init_wins(WINDOW **wins, int n)
{ int x, y, i;
char label[80];
y = 2;
x = 10;
for(i = 0; i < n; ++i)
{ wins[i] = newwin(NLINES, NCOLS, y, x);
sprintf(label, "Window Number %d", i + 1);
win_show(wins[i], label, i + 1);
y += 3;
x += 7;
}
}
/* Show the window with a border and a label */
void win_show(WINDOW *win, char *label, int label_color)
{ int startx, starty, height, width;
getbegyx(win, starty, startx);
getmaxyx(win, height, width);
box(win, 0, 0);
mvwaddch(win, 2, 0, ACS_LTEE);
mvwhline(win, 2, 1, ACS_HLINE, width - 2);
mvwaddch(win, 2, width - 1, ACS_RTEE);
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
}
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();
} |
菜单库为基本 curses 提供了很好的扩展,通过它可以创建菜单。 它提供了一组函数来创建菜单。 但是,必须自定义它们以提供更好看的外观,包括颜色等。 让我们深入了解细节。
菜单是一个屏幕显示,可帮助用户从给定的项目集中选择某些子集。 简单来说,菜单是项目的集合,可以从中选择一个或多个项目。 一些读者可能不知道多项选择功能。 菜单库提供了编写菜单的功能,用户可以从中选择多个项目作为首选。 这将在后面的章节中讨论。 现在是学习一些基本知识的时候了。
要创建菜单,您首先要创建项目,然后将菜单发布到显示器。 之后,所有用户响应的处理都在一个优雅的函数 menu_driver() 中完成,该函数是任何菜单程序的主力军。
菜单程序的一般控制流程如下所示。
初始化 curses
使用 new_item() 创建项目。您可以为项目指定名称和描述。
使用 new_menu() 创建菜单,并通过指定要附加的项目。
使用 menu_post() 发布菜单并刷新屏幕。
使用循环处理用户请求,并使用 menu_driver 对菜单进行必要的更新。
使用 menu_unpost() 取消发布菜单
使用 free_menu() 释放分配给菜单的内存
使用 free_item() 释放分配给项目的内存
结束 curses
让我们看一个程序,它打印一个简单的菜单,并使用向上和向下箭头更新当前选择。
要使用菜单库函数,您必须包含 menu.h,并且为了将程序与菜单库链接,应添加标志 -lmenu 以及 -lncurses,顺序不能颠倒。
#include <menu.h> . . . compile and link: gcc <program file> -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 Move left to an item. REQ_RIGHT_ITEM Move right to an item. REQ_UP_ITEM Move up to an item. REQ_DOWN_ITEM Move down to an item. REQ_SCR_ULINE Scroll up a line. REQ_SCR_DLINE Scroll down a line. REQ_SCR_DPAGE Scroll down a page. REQ_SCR_UPAGE Scroll up a page. REQ_FIRST_ITEM Move to the first item. REQ_LAST_ITEM Move to the last item. REQ_NEXT_ITEM Move to the next item. REQ_PREV_ITEM Move to the previous item. REQ_TOGGLE_ITEM Select/deselect an item. REQ_CLEAR_PATTERN Clear the menu pattern buffer. REQ_BACK_PATTERN Delete the previous character from the pattern buffer. REQ_NEXT_MATCH Move to the next item matching the pattern match. REQ_PREV_MATCH Move to the previous item matching the pattern match. |
不要被大量的选项吓倒。我们将慢慢地逐个了解它们。此示例中感兴趣的选项是 REQ_UP_ITEM 和 REQ_DOWN_ITEM。当将这两个选项传递给 menu_driver 时,菜单驱动程序会将当前项目更新为向上或向下移动一项。
正如您在上面的示例中看到的,menu_driver 在更新菜单中起着重要的作用。理解它接受的各种选项及其作用非常重要。如上所述,menu_driver() 的第二个参数可以是导航请求、可打印字符或 KEY_MOUSE 键。让我们剖析不同的导航请求。
REQ_LEFT_ITEM 和 REQ_RIGHT_ITEM
菜单可以显示多列,以便容纳多个项目。这可以通过使用menu_format()函数来完成。当显示多列菜单时,这些请求会导致菜单驱动程序将当前选择移动到左侧或右侧。
REQ_UP_ITEM 和 REQ_DOWN_ITEM
您在上面的示例中已经看到了这两个选项。给出这些选项时,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 字符的最接近匹配项。每当 ASCII 字符被提供给 menu_driver 时,它都会放入模式缓冲区。它还会尝试在项目列表中查找与模式最接近的匹配项,并将当前选择移动到该项目。请求 REQ_CLEAR_PATTERN 清除模式缓冲区。请求 REQ_BACK_PATTERN 删除模式缓冲区中的上一个字符。如果模式匹配多个项目,则可以通过 REQ_NEXT_MATCH 和 REQ_PREV_MATCH 循环浏览匹配的项目,这将分别将当前选择移动到下一个和上一个匹配项。
鼠标请求
在 KEY_MOUSE 请求的情况下,根据鼠标位置采取相应的操作。要采取的操作在手册页中解释为:
If the second argument is the KEY_MOUSE special key, the associated mouse event is translated into one of the above pre-defined requests. Currently only clicks in the user window (e.g. inside the menu display area or the decora� tion window) are handled. If you click above the display region of the menu, a REQ_SCR_ULINE is generated, if you doubleclick a REQ_SCR_UPAGE is generated and if you tripleclick a REQ_FIRST_ITEM is generated. If you click below the display region of the menu, a REQ_SCR_DLINE is generated, if you doubleclick a REQ_SCR_DPAGE is generated and if you tripleclick a REQ_LAST_ITEM is generated. If you click at an item inside the display area of the menu, the menu cursor is positioned to that 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;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
init_pair(1, COLOR_RED, COLOR_BLACK);
/* Create items */
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]);
/* Crate menu */
my_menu = new_menu((ITEM **)my_items);
/* Create the window to be associated with the menu */
my_menu_win = newwin(10, 40, 4, 4);
keypad(my_menu_win, TRUE);
/* Set main window and sub window */
set_menu_win(my_menu, my_menu_win);
set_menu_sub(my_menu, derwin(my_menu_win, 6, 38, 3, 1));
/* Set menu mark to the string " * " */
set_menu_mark(my_menu, " * ");
/* Print a border around the main window and print a title */
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 the menu */
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 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, 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() 设置标记字符串,该字符串将显示在选定项目的左侧。
如果为窗口提供的子窗口不够大,无法显示所有项目,则菜单将是可滚动的。当您位于当前列表中的最后一个项目时,如果您发送 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;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_CYAN, COLOR_BLACK);
/* Create items */
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]);
/* Crate menu */
my_menu = new_menu((ITEM **)my_items);
/* Create the window to be associated with the menu */
my_menu_win = newwin(10, 40, 4, 4);
keypad(my_menu_win, TRUE);
/* Set main window and sub window */
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 to the string " * " */
set_menu_mark(my_menu, " * ");
/* Print a border around the main window and print a title */
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 the menu */
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 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, 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();
} |
此程序是不言自明的。在此示例中,选择的数量已增加到十个,这大于我们的子窗口大小,该子窗口最多可容纳 6 个项目。必须使用函数 set_menu_format() 将此消息明确传达给菜单系统。在这里,我们指定要为单页显示的行数和列数。我们可以在 rows 变量中指定要显示的任意数量的项目,如果它小于子窗口的高度。如果用户按下的键是向上翻页或向下翻页,则由于提供给 menu_driver() 的请求(REQ_SCR_DPAGE 和 REQ_SCR_UPAGE),菜单将滚动一页。
在上面的示例中,您已经看到了如何使用函数 set_menu_format()。我没有提到 cols 变量(第三个参数)的作用。嗯,如果您的子窗口足够宽,您可以选择每行显示多个项目。这可以在 cols 变量中指定。为了使事情更简单,以下示例不显示项目的描述。
示例 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;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_CYAN, COLOR_BLACK);
/* Create items */
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]);
/* Crate menu */
my_menu = new_menu((ITEM **)my_items);
/* Set menu option not to show the description */
menu_opts_off(my_menu, O_SHOWDESC);
/* Create the window to be associated with the menu */
my_menu_win = newwin(10, 70, 4, 4);
keypad(my_menu_win, TRUE);
/* Set main window and sub window */
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, " * ");
/* Print a border around the main window and print a title */
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 the menu */
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 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();
} |
请注意对 set_menu_format() 的函数调用。它指定列数为 3,因此每行显示 3 个项目。我们还使用函数 menu_opts_off() 关闭了显示描述。有几个函数 set_menu_opts()、menu_opts_on() 和 menu_opts() 可用于操作菜单选项。可以指定以下菜单选项。
O_ONEVALUE Only one item can be selected for this menu. O_SHOWDESC Display the item descriptions when the menu is posted. O_ROWMAJOR Display the menu in row-major order. O_IGNORECASE Ignore the case when pattern-matching. O_SHOWMATCH Move the cursor to within the item name while pat� tern-matching. O_NONCYCLIC Don't wrap around next-item and previous-item, requests to the other end of the menu. |
默认情况下,所有选项都处于开启状态。您可以使用 menu_opts_on() 和 menu_opts_off() 函数打开或关闭特定属性。您还可以使用 set_menu_opts() 直接指定选项。此函数的参数应是这些常量中的一些进行 OR 运算的值。函数 menu_opts() 可用于找出菜单的当前选项。
您可能想知道,如果您关闭选项 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;
/* Initialize curses */
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize items */
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);
/* Make the menu multi valued */
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();
}
|
哇,很多新函数。让我们逐个了解它们。首先是 REQ_TOGGLE_ITEM。在多值菜单中,应允许用户选择或取消选择多个项目。请求 REQ_TOGGLE_ITEM 切换当前选择。在本例中,当按下空格键时,REQ_TOGGLE_ITEM 请求将发送到 menu_driver 以实现结果。
现在,当用户按下 <ENTER> 键时,我们显示他当前选择的项目。首先,我们使用函数 menu_items() 找出与菜单关联的项目。然后,我们循环遍历这些项目,以找出项目是否被选中。如果项目被选中,函数 item_value() 返回 TRUE。函数 item_count() 返回菜单中项目的数量。项目名称可以使用 item_name() 找到。您还可以使用 item_description() 找到与项目关联的描述。
嗯,到目前为止,您一定渴望菜单有所不同,并且具有许多功能。我知道。您想要颜色!!!您想要创建类似于那些文本模式 dos 游戏 的漂亮菜单。函数 set_menu_fore() 和 set_menu_back() 可用于更改选定项目和未选定项目的属性。名称具有误导性。它们不会更改菜单的前景或背景,这将是无用的。
函数 set_menu_grey() 可用于设置菜单中不可选择项目的显示属性。这使我们想到了项目的一个有趣的选项,也是唯一的选项 O_SELECTABLE。我们可以通过函数 item_opts_off() 关闭它,之后该项目将变为不可选择。这就像那些花哨的 Windows 菜单中的灰色项目。让我们通过这个示例将这些概念付诸实践
示例 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;
/* Initialize 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);
/* Initialize items */
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);
/* Create menu */
my_menu = new_menu((ITEM **)my_items);
/* Set fore ground and back ground of the menu */
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));
/* Post the menu */
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 */
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();
}
|
我们可以将用户指针与菜单中的每个项目关联起来。它的工作方式与面板中的用户指针相同。它不受菜单系统的影响。您可以将任何您喜欢的东西存储在其中。我通常使用它来存储在选择菜单选项时要执行的函数(它被选中,并且可能是用户按下了 <ENTER> 键);
示例 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;
/* Initialize 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);
/* Initialize items */
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 the user pointer */
set_item_userptr(my_items[i], func);
}
my_items[n_choices] = (ITEM *)NULL;
/* Create menu */
my_menu = new_menu((ITEM **)my_items);
/* Post the menu */
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);
} |
嗯。如果您见过那些网页上的表单,它们接受用户的输入并执行各种操作,您可能想知道如何在文本模式显示中创建此类表单。在纯粹的 ncurses 中编写那些漂亮的表单非常困难。表单库尝试提供一个基本框架,以便轻松构建和维护表单。它具有许多功能(函数),可以管理验证、字段的动态扩展等。让我们看看它的完整流程。
表单是字段的集合;每个字段可以是标签(静态文本)或数据输入位置。表单库还提供了将表单划分为多个页面的功能。
表单的创建方式与菜单非常相似。首先,使用 new_field() 创建与表单相关的字段。您可以为字段设置选项,以便可以使用一些花哨的属性显示它们,在字段失去焦点之前进行验证等。然后将字段附加到表单。之后,可以发布表单以进行显示,并准备接收输入。与 menu_driver() 类似,表单使用 form_driver() 进行操作。我们可以向 form_driver 发送请求,以将焦点移动到某个字段,将光标移动到字段末尾等。在用户在字段中输入值并完成验证后,可以取消发布表单并释放分配的内存。
表单程序的一般控制流程如下所示。
初始化 curses
使用 new_field() 创建字段。您可以指定字段的高度和宽度,以及其在表单上的位置。
通过指定要附加的字段,使用 new_form() 创建表单。
使用 form_post() 发布表单并刷新屏幕。
使用循环处理用户请求,并使用 form_driver 对表单进行必要的更新。
使用 form_unpost() 取消发布菜单
使用 free_form() 释放分配给菜单的内存
使用 free_field() 释放分配给项目的内存
结束 curses
如您所见,使用表单库与处理菜单库非常相似。以下示例将探索表单处理的各个方面。让我们首先开始简单的旅程。
要使用表单库函数,您必须包含 form.h,并且为了将程序与表单库链接,应添加标志 -lform 以及 -lncurses,顺序不能颠倒。
#include <form.h> . . . compile and link: gcc <program file> -lform -lncurses |
示例 25. 表单基础知识
#include <form.h>
int main()
{ FIELD *field[3];
FORM *my_form;
int ch;
/* Initialize curses */
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize the fields */
field[0] = new_field(1, 10, 4, 18, 0, 0);
field[1] = new_field(1, 10, 6, 18, 0, 0);
field[2] = NULL;
/* Set field options */
set_field_back(field[0], A_UNDERLINE); /* Print a line for the option */
field_opts_off(field[0], O_AUTOSKIP); /* Don't go to next field when this */
/* Field is filled up */
set_field_back(field[1], A_UNDERLINE);
field_opts_off(field[1], O_AUTOSKIP);
/* Create the form and post it */
my_form = new_form(field);
post_form(my_form);
refresh();
mvprintw(4, 10, "Value 1:");
mvprintw(6, 10, "Value 2:");
refresh();
/* Loop through to get user requests */
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case KEY_DOWN:
/* Go to next field */
form_driver(my_form, REQ_NEXT_FIELD);
/* Go to the end of the present buffer */
/* Leaves nicely at the last character */
form_driver(my_form, REQ_END_LINE);
break;
case KEY_UP:
/* Go to previous field */
form_driver(my_form, REQ_PREV_FIELD);
form_driver(my_form, REQ_END_LINE);
break;
default:
/* If this is a normal character, it gets */
/* Printed */
form_driver(my_form, ch);
break;
}
}
/* Un post form and free the memory */
unpost_form(my_form);
free_form(my_form);
free_field(field[0]);
free_field(field[1]);
endwin();
return 0;
} |
以上示例非常直接。它使用new_field()创建了两个字段。new_field() 接受高度、宽度、起始 Y 坐标、起始 X 坐标、屏幕外行数和额外的缓冲区数。第五个参数屏幕外行数指定要显示字段的多少。如果为零,则始终显示整个字段,否则当用户访问字段的未显示部分时,表单将是可滚动的。表单库为每个字段分配一个缓冲区,以存储用户输入的数据。使用 new_field() 的最后一个参数,我们可以指定它分配一些额外的缓冲区。这些可以用于任何您喜欢的目的。
创建字段后,它们的背景属性被设置为下划线,使用 set_field_back()。AUTOSKIP 选项使用 field_opts_off() 关闭。如果此选项开启,则一旦活动字段完全填满,焦点将移动到表单中的下一个字段。
将字段附加到表单后,它将被发布。从此以后,用户输入将在 while 循环中处理,方法是向 form_driver 发出相应的请求。所有 form_driver() 请求的详细信息将在稍后解释。
每个表单字段都与许多属性相关联。可以操作它们以获得所需的效果并获得乐趣!!!还等什么?
我们在创建字段时给定的参数可以使用 field_info() 检索。它将高度、宽度、起始 Y 坐标、起始 X 坐标、屏幕外行数和额外的缓冲区数返回到给定的参数中。它有点像 new_field() 的逆运算。
int field_info( FIELD *field, /* field from which to fetch */ int *height, *int width, /* field size */ int *top, int *left, /* upper left corner */ int *offscreen, /* number of offscreen rows */ int *nbuf); /* number of working buffers */ |
字段的位置可以使用 move_field() 移动到不同的位置。
int move_field( FIELD *field, /* field to alter */ int top, int left); /* new upper-left corner */ |
与往常一样,可以使用 field_infor() 查询更改后的位置。
可以使用函数 set_field_just() 固定字段的对齐方式。
int set_field_just(FIELD *field, /* field to alter */ int justmode); /* mode to set */ int field_just(FIELD *field); /* fetch justify mode of field */ |
这些函数接受和返回的对齐模式值是 NO_JUSTIFICATION、JUSTIFY_RIGHT、JUSTIFY_LEFT 或 JUSTIFY_CENTER。
如您所见,在上面的示例中,可以使用 set_field_fore() 和 setfield_back() 设置字段的显示属性。这些函数设置字段的前景和背景属性。您还可以指定一个填充字符,该字符将填充字段的未填充部分。填充字符通过调用 set_field_pad() 设置。默认填充值是一个空格。函数 field_fore()、field_back、field_pad() 可用于查询字段的当前前景、背景属性和填充字符。以下列表给出了函数的用法。
int set_field_fore(FIELD *field, /* field to alter */ chtype attr); /* attribute to set */ chtype field_fore(FIELD *field); /* field to query */ /* returns foreground attribute */ int set_field_back(FIELD *field, /* field to alter */ chtype attr); /* attribute to set */ chtype field_back(FIELD *field); /* field to query */ /* returns background attribute */ int set_field_pad(FIELD *field, /* field to alter */ int pad); /* pad character to set */ chtype field_pad(FIELD *field); /* field to query */ /* returns present pad character */ |
尽管上面的函数看起来很简单,但在开始时使用颜色和 set_field_fore() 可能会令人沮丧。让我首先解释一下字段的前景和背景属性。前景属性与字符关联。这意味着字段中的字符将使用您使用 set_field_fore() 设置的属性打印。背景属性是用于填充字段背景的属性,无论是否有字符。那么颜色呢?由于颜色始终成对定义,因此显示彩色字段的正确方法是什么?这是一个阐明颜色属性的示例。
示例 26. 表单属性示例
#include <form.h>
int main()
{ FIELD *field[3];
FORM *my_form;
int ch;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize few color pairs */
init_pair(1, COLOR_WHITE, COLOR_BLUE);
init_pair(2, COLOR_WHITE, COLOR_BLUE);
/* Initialize the fields */
field[0] = new_field(1, 10, 4, 18, 0, 0);
field[1] = new_field(1, 10, 6, 18, 0, 0);
field[2] = NULL;
/* Set field options */
set_field_fore(field[0], COLOR_PAIR(1));/* Put the field with blue background */
set_field_back(field[0], COLOR_PAIR(2));/* and white foreground (characters */
/* are printed in white */
field_opts_off(field[0], O_AUTOSKIP); /* Don't go to next field when this */
/* Field is filled up */
set_field_back(field[1], A_UNDERLINE);
field_opts_off(field[1], O_AUTOSKIP);
/* Create the form and post it */
my_form = new_form(field);
post_form(my_form);
refresh();
set_current_field(my_form, field[0]); /* Set focus to the colored field */
mvprintw(4, 10, "Value 1:");
mvprintw(6, 10, "Value 2:");
mvprintw(LINES - 2, 0, "Use UP, DOWN arrow keys to switch between fields");
refresh();
/* Loop through to get user requests */
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case KEY_DOWN:
/* Go to next field */
form_driver(my_form, REQ_NEXT_FIELD);
/* Go to the end of the present buffer */
/* Leaves nicely at the last character */
form_driver(my_form, REQ_END_LINE);
break;
case KEY_UP:
/* Go to previous field */
form_driver(my_form, REQ_PREV_FIELD);
form_driver(my_form, REQ_END_LINE);
break;
default:
/* If this is a normal character, it gets */
/* Printed */
form_driver(my_form, ch);
break;
}
}
/* Un post form and free the memory */
unpost_form(my_form);
free_form(my_form);
free_field(field[0]);
free_field(field[1]);
endwin();
return 0;
} |
玩转颜色对,并尝试理解前景和背景属性。在我的程序中使用颜色属性时,我通常只使用 set_field_back() 设置背景。Curses 根本不允许定义单独的颜色属性。
还有大量的字段选项位,您可以设置它们来控制表单处理的各个方面。您可以使用以下函数来操作它们
int set_field_opts(FIELD *field, /* field to alter */ int attr); /* attribute to set */ int field_opts_on(FIELD *field, /* field to alter */ int attr); /* attributes to turn on */ int field_opts_off(FIELD *field, /* field to alter */ int attr); /* attributes to turn off */ int field_opts(FIELD *field); /* field to query */ |
函数 set_field_opts() 可用于直接设置字段的属性,或者您可以选择使用 field_opts_on() 和 field_opts_off() 有选择地打开和关闭一些属性。任何时候您都可以使用 field_opts() 查询字段的属性。以下是可用选项的列表。默认情况下,所有选项都处于开启状态。
控制字段是否在屏幕上可见。可以在表单处理期间使用,以根据父字段的值隐藏或弹出字段。
控制字段在表单处理期间是否处于活动状态(即,是否被表单导航键访问)。可用于使标签或具有缓冲区值的派生字段可由表单应用程序更改,而不是用户更改。
控制在字段输入期间是否显示数据。如果关闭字段上的此选项,库将接受和编辑该字段中的数据,但不会显示该数据,并且可见字段光标不会移动。您可以关闭 O_PUBLIC 位以定义密码字段。
控制字段的数据是否可以被修改。当此选项关闭时,除REQ_PREV_CHOICE和REQ_NEXT_CHOICE之外的所有编辑请求都将失败。此类只读字段可能对帮助消息很有用。
控制多行字段中的自动换行。通常,当(空格分隔的)单词的任何字符到达当前行末尾时,整个单词都会被换行到下一行(假设存在下一行)。当此选项关闭时,单词将在换行符处拆分。
控制字段清空。当此选项开启时,在第一个字段位置输入字符会擦除整个字段(除了刚刚输入的字符)。
控制在此字段填满时是否自动跳到下一个字段。通常,当表单用户尝试在字段中键入超出字段容量的数据时,编辑位置会跳转到下一个字段。当此选项关闭时,用户的光标将悬停在字段末尾。在尚未达到其大小限制的动态字段中,此选项将被忽略。
控制是否对空字段应用验证。通常,情况并非如此;用户可以使字段为空,而无需在退出时调用通常的验证检查。如果字段上的此选项关闭,则退出该字段将调用验证检查。
控制验证是在每次退出时发生,还是仅在字段被修改后发生。通常后者为真。如果您的字段的验证函数可能在表单处理期间更改,则设置 O_PASSOK 可能很有用。
控制字段是否固定为其初始尺寸。如果关闭此选项,则字段将变为动态字段,并将伸展以适应输入的数据。
字段的选项在当前选定字段时无法更改。但是,可以在未处于当前状态的已发布字段上更改选项。
选项值是位掩码,并且可以以显而易见的方式与逻辑或组合。您已经看到了关闭 O_AUTOSKIP 选项的用法。以下示例阐明了更多选项的用法。其他选项将在适当的地方进行解释。
示例 27. 字段选项用法示例
#include <form.h>
#define STARTX 15
#define STARTY 4
#define WIDTH 25
#define N_FIELDS 3
int main()
{ FIELD *field[N_FIELDS];
FORM *my_form;
int ch, i;
/* Initialize curses */
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize the fields */
for(i = 0; i < N_FIELDS - 1; ++i)
field[i] = new_field(1, WIDTH, STARTY + i * 2, STARTX, 0, 0);
field[N_FIELDS - 1] = NULL;
/* Set field options */
set_field_back(field[1], A_UNDERLINE); /* Print a line for the option */
field_opts_off(field[0], O_ACTIVE); /* This field is a static label */
field_opts_off(field[1], O_PUBLIC); /* This filed is like a password field*/
field_opts_off(field[1], O_AUTOSKIP); /* To avoid entering the same field */
/* after last character is entered */
/* Create the form and post it */
my_form = new_form(field);
post_form(my_form);
refresh();
set_field_just(field[0], JUSTIFY_CENTER); /* Center Justification */
set_field_buffer(field[0], 0, "This is a static Field");
/* Initialize the field */
mvprintw(STARTY, STARTX - 10, "Field 1:");
mvprintw(STARTY + 2, STARTX - 10, "Field 2:");
refresh();
/* Loop through to get user requests */
while((ch = getch()) != KEY_F(1))
{ switch(ch)
{ case KEY_DOWN:
/* Go to next field */
form_driver(my_form, REQ_NEXT_FIELD);
/* Go to the end of the present buffer */
/* Leaves nicely at the last character */
form_driver(my_form, REQ_END_LINE);
break;
case KEY_UP:
/* Go to previous field */
form_driver(my_form, REQ_PREV_FIELD);
form_driver(my_form, REQ_END_LINE);
break;
default:
/* If this is a normal character, it gets */
/* Printed */
form_driver(my_form, ch);
break;
}
}
/* Un post form and free the memory */
unpost_form(my_form);
free_form(my_form);
free_field(field[0]);
free_field(field[1]);
endwin();
return 0;
} |
此示例虽然无用,但显示了选项的用法。如果使用得当,它们可以在表单中非常有效地呈现信息。第二个字段不是 O_PUBLIC,因此不显示您正在键入的字符。
字段状态指定字段是否已被编辑。它最初设置为 FALSE,当用户输入某些内容并且数据缓冲区被修改时,它变为 TRUE。因此,可以查询字段的状态以找出它是否已被修改。以下函数可以协助这些操作。
int set_field_status(FIELD *field, /* field to alter */ int status); /* status to set */ int field_status(FIELD *field); /* fetch status of field */ |
最好仅在离开字段后检查字段的状态,因为数据缓冲区可能尚未更新,因为验证仍在进行中。为了保证返回正确的状态,请在以下情况下调用 field_status():(1)在字段的退出验证检查例程中,(2)从字段或表单的初始化或终止钩子中,或(3)刚好在表单驱动程序处理 REQ_VALIDATION 请求之后
每个字段结构都包含一个指针,用户可以将其用于各种目的。它不受表单库的影响,用户可以将其用于任何目的。以下函数设置和获取用户指针。
int set_field_userptr(FIELD *field, char *userptr); /* the user pointer you wish to associate */ /* with the field */ char *field_userptr(FIELD *field); /* fetch user pointer of the field */ |
如果您想要一个宽度可变的动态变化的字段,这就是您想要充分利用的功能。这将允许用户输入比字段的原始大小更多的数据,并让字段增长。根据字段方向,它将水平或垂直滚动以包含新数据。
要使字段动态可增长,应关闭选项 O_STATIC。这可以使用
field_opts_off(field_pointer, O_STATIC); |
但这通常不建议允许字段无限增长。您可以使用
int set_max_field(FIELD *field, /* Field on which to operate */ int max_growth); /* maximum growth allowed for the field */ |
检索动态可增长字段的字段信息,通过
int dynamic_field_info( FIELD *field, /* Field on which to operate */ int *prows, /* number of rows will be filled in this */ int *pcols, /* number of columns will be filled in this*/ int *pmax) /* maximum allowable growth will be filled */ /* in this */ |
回想一下库例程 new_field;使用高度设置为 1 创建的新字段将被定义为单行字段。使用高度大于 1 创建的新字段将被定义为多行字段。
关闭 O_STATIC 的单行字段(动态可增长字段)将包含单个固定行,但是如果用户输入的数据超过初始字段的容量,则列数可以增加。显示的列数将保持固定,并且额外的数据将水平滚动。
关闭 O_STATIC 的多行字段(动态可增长字段)将包含固定数量的列,但是如果用户输入的数据超过初始字段的容量,则行数可以增加。显示的行数将保持固定,并且额外的数据将垂直滚动。
以上两段几乎描述了动态可增长字段的行为。表单库的其他部分的行为如下所述
如果选项 O_STATIC 关闭并且没有为字段指定最大增长,则字段选项 O_AUTOSKIP 将被忽略。目前,当用户在字段的最后一个字符位置键入时,O_AUTOSKIP 会生成自动 REQ_NEXT_FIELD 表单驱动程序请求。在没有指定最大增长的可增长字段上,没有最后一个字符位置。如果指定了最大增长,则如果字段已增长到其最大大小,则 O_AUTOSKIP 选项将照常工作。
如果选项 O_STATIC 关闭,则字段对齐将被忽略。目前,set_field_just 可用于将单行字段的内容左对齐、右对齐、居中对齐。可增长的单行字段,根据定义,将水平增长和滚动,并且可能包含比可以对齐的更多数据。field_just 的返回值将保持不变。
如果字段选项 O_STATIC 关闭并且没有为字段指定最大增长,则重载的表单驱动程序请求 REQ_NEW_LINE 将以相同方式操作,而与 O_NL_OVERLOAD 表单选项无关。目前,如果表单选项 O_NL_OVERLOAD 开启,则如果从字段的最后一行调用 REQ_NEW_LINE,则它会隐式生成 REQ_NEXT_FIELD。如果字段可以无限增长,则没有最后一行,因此 REQ_NEW_LINE 将永远不会隐式生成 REQ_NEXT_FIELD。如果指定了最大增长限制并且 O_NL_OVERLOAD 表单选项开启,则仅当字段已增长到其最大大小时且用户位于最后一行时,REQ_NEW_LINE 才会隐式生成 REQ_NEXT_FIELD。
库调用 dup_field 将照常工作;它将复制字段,包括当前缓冲区大小和被复制字段的内容。任何指定的最大增长也将被复制。
库调用 link_field 将照常工作;它将复制所有字段属性,并与被链接的字段共享缓冲区。如果随后由共享缓冲区的字段更改 O_STATIC 字段选项,则系统对尝试在字段中输入超出缓冲区当前容量的更多数据的反应将取决于当前字段中选项的设置。
库调用 field_info 将照常工作;变量 nrow 将包含对 new_field 的原始调用的值。用户应使用上面描述的 dynamic_field_info 来查询缓冲区的当前大小。
上述某些点只有在解释表单驱动程序后才有意义。我们将在接下来的几节中研究它。
表单窗口概念与菜单窗口非常相似。每个表单都与一个主窗口和一个子窗口关联。表单主窗口显示与表单关联的任何标题或边框,或用户希望的任何内容。然后,子窗口包含所有字段,并根据其位置显示它们。这为非常轻松地操作花哨的表单显示提供了灵活性。
由于这与菜单窗口非常相似,因此我提供了一个示例,但没有太多解释。这些函数是相似的,并且它们的工作方式相同。
示例 28. 表单窗口示例
#include <form.h>
void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);
int main()
{
FIELD *field[3];
FORM *my_form;
WINDOW *my_form_win;
int ch, rows, cols;
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize few color pairs */
init_pair(1, COLOR_RED, COLOR_BLACK);
/* Initialize the fields */
field[0] = new_field(1, 10, 6, 1, 0, 0);
field[1] = new_field(1, 10, 8, 1, 0, 0);
field[2] = NULL;
/* Set field options */
set_field_back(field[0], A_UNDERLINE);
field_opts_off(field[0], O_AUTOSKIP); /* Don't go to next field when this */
/* Field is filled up */
set_field_back(field[1], A_UNDERLINE);
field_opts_off(field[1], O_AUTOSKIP);
/* Create the form and post it */
my_form = new_form(field);
/* Calculate the area required for the form */
scale_form(my_form, &rows, &cols);
/* Create the window to be associated with the form */
my_form_win = newwin(rows + 4, cols + 4, 4, 4);
keypad(my_form_win, TRUE);
/* Set main window and sub window */
set_form_win(my_form, my_form_win);
set_form_sub(my_form, derwin(my_form_win, rows, cols, 2, 2));
/* Print a border around the main window and print a title */
box(my_form_win, 0, 0);
print_in_middle(my_form_win, 1, 0, cols + 4, "My Form", COLOR_PAIR(1));
post_form(my_form);
wrefresh(my_form_win);
mvprintw(LINES - 2, 0, "Use UP, DOWN arrow keys to switch between fields");
refresh();
/* Loop through to get user requests */
while((ch = wgetch(my_form_win)) != KEY_F(1))
{ switch(ch)
{ case KEY_DOWN:
/* Go to next field */
form_driver(my_form, REQ_NEXT_FIELD);
/* Go to the end of the present buffer */
/* Leaves nicely at the last character */
form_driver(my_form, REQ_END_LINE);
break;
case KEY_UP:
/* Go to previous field */
form_driver(my_form, REQ_PREV_FIELD);
form_driver(my_form, REQ_END_LINE);
break;
default:
/* If this is a normal character, it gets */
/* Printed */
form_driver(my_form, ch);
break;
}
}
/* Un post form and free the memory */
unpost_form(my_form);
free_form(my_form);
free_field(field[0]);
free_field(field[1]);
endwin();
return 0;
}
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();
} |
默认情况下,字段将接受用户输入的任何数据。可以将验证附加到字段。然后,当用户尝试离开字段时,如果字段包含与验证类型不匹配的数据,则将失败。一些验证类型还具有字符有效性检查,用于每次在字段中输入字符时进行检查。
可以使用以下函数将验证附加到字段。
int set_field_type(FIELD *field, /* field to alter */ FIELDTYPE *ftype, /* type to associate */ ...); /* additional arguments*/ |
FIELDTYPE *field_type(FIELD *field); /* field to query */ |
表单驱动程序仅在最终用户输入数据时才验证字段中的数据。当发生以下情况时,不会进行验证
应用程序程序通过调用 set_field_buffer 更改字段值。
链接的字段值被间接更改 -- 通过更改它们链接到的字段
以下是预定义的验证类型。您还可以指定自定义验证,但这有点棘手且繁琐。
此字段类型接受字母数据;没有空格,没有数字,没有特殊字符(这在字符输入时进行检查)。它使用
int set_field_type(FIELD *field, /* field to alter */ TYPE_ALPHA, /* type to associate */ int width); /* maximum width of field */ |
width 参数设置数据的最小宽度。用户必须至少输入 width 个字符才能离开字段。通常,您需要将其设置为字段宽度;如果它大于字段宽度,则验证检查将始终失败。最小宽度为零使字段完成成为可选。
此字段类型接受字母数据和数字;没有空格,没有特殊字符(这在字符输入时进行检查)。它使用
int set_field_type(FIELD *field, /* field to alter */ TYPE_ALNUM, /* type to associate */ int width); /* maximum width of field */ |
width 参数设置数据的最小宽度。与 TYPE_ALPHA 相同,通常您需要将其设置为字段宽度;如果它大于字段宽度,则验证检查将始终失败。最小宽度为零使字段完成成为可选。
此类型允许您将字段的值限制为一组指定的字符串值(例如,美国州的两个字母邮政编码)。它使用
int set_field_type(FIELD *field, /* field to alter */ TYPE_ENUM, /* type to associate */ char **valuelist; /* list of possible values */ int checkcase; /* case-sensitive? */ int checkunique); /* must specify uniquely? */ |
valuelist 参数必须指向以 NULL 结尾的有效字符串列表。如果 checkcase 参数为 true,则使与字符串的比较区分大小写。
当用户退出 TYPE_ENUM 字段时,验证过程会尝试将缓冲区中的数据完成为有效条目。如果已输入完整的选择字符串,则当然有效。但是,也可以输入有效字符串的前缀,并让它为您完成。
默认情况下,如果您输入这样的前缀,并且它与字符串列表中的多个值匹配,则前缀将被完成为第一个匹配值。但是,如果 checkunique 参数为 true,则要求前缀匹配必须是唯一的才能有效。
REQ_NEXT_CHOICE 和 REQ_PREV_CHOICE 输入请求对于这些字段尤其有用。
此字段类型接受整数。它的设置如下
int set_field_type(FIELD *field, /* field to alter */ TYPE_INTEGER, /* type to associate */ int padding, /* # places to zero-pad to */ int vmin, int vmax); /* valid range */ |
有效字符包括可选的前导负号和数字。范围检查在退出时执行。如果范围最大值小于或等于最小值,则范围将被忽略。
如果值通过其范围检查,则会用尽可能多的前导零数字填充它,以满足填充参数。
TYPE_INTEGER 值缓冲区可以方便地使用 C 库函数 atoi(3) 进行解释。
此字段类型接受十进制数。它的设置如下
int set_field_type(FIELD *field, /* field to alter */ TYPE_NUMERIC, /* type to associate */ int padding, /* # places of precision */ int vmin, int vmax); /* valid range */ |
有效字符包括可选的前导负号和数字。可能包括小数点。范围检查在退出时执行。如果范围最大值小于或等于最小值,则范围将被忽略。
如果值通过其范围检查,则会用尽可能多的尾随零数字填充它,以满足填充参数。
TYPE_NUMERIC 值缓冲区可以方便地使用 C 库函数 atof(3) 进行解释。
此字段类型接受与正则表达式匹配的数据。它的设置如下
int set_field_type(FIELD *field, /* field to alter */ TYPE_REGEXP, /* type to associate */ char *regexp); /* expression to match */ |
正则表达式的语法与 regcomp(3) 的语法相同。正则表达式匹配的检查在退出时执行。
与菜单系统中一样,form_driver() 在表单系统中起着非常重要的作用。所有类型的表单系统请求都应通过 form_driver() 传递。
int form_driver(FORM *form, /* form on which to operate */ int request) /* form request code */ |
正如您在上面的一些示例中看到的,您必须在一个循环中查找用户输入,然后决定它是字段数据还是表单请求。然后将表单请求传递给 form_driver() 以完成工作。
这些请求大致可以分为以下几类。下面解释了不同的请求及其用法
这些请求导致页面级别的移动通过表单,触发新表单屏幕的显示。表单可以由多个页面组成。如果您有一个包含许多字段和逻辑部分的大型表单,则可以将表单分为多个页面。使用函数 set_new_page() 在指定的字段处设置新页面。
int set_new_page(FIELD *field,/* Field at which page break to be set or unset */ bool new_page_flag); /* should be TRUE to put a break */ |
以下请求允许您移动到不同的页面
REQ_NEXT_PAGE 移动到下一个表单页面。
REQ_PREV_PAGE 移动到上一个表单页面。
REQ_FIRST_PAGE 移动到第一个表单页面。
REQ_LAST_PAGE 移动到最后一个表单页面。
这些请求将列表视为循环的;也就是说,从最后一页的 REQ_NEXT_PAGE 会转到第一页,而从第一页的 REQ_PREV_PAGE 会转到最后一页。
这些请求处理同一页面上字段之间的导航。
REQ_NEXT_FIELD 移动到下一个字段。
REQ_PREV_FIELD 移动到上一个字段。
REQ_FIRST_FIELD 移动到第一个字段。
REQ_LAST_FIELD 移动到最后一个字段。
REQ_SNEXT_FIELD 移动到排序后的下一个字段。
REQ_SPREV_FIELD 移动到排序后的上一个字段。
REQ_SFIRST_FIELD 移动到排序后的第一个字段。
REQ_SLAST_FIELD 移动到排序后的最后一个字段。
REQ_LEFT_FIELD 向左移动到字段。
REQ_RIGHT_FIELD 向右移动到字段。
REQ_UP_FIELD 向上移动到字段。
REQ_DOWN_FIELD 向下移动到字段。
这些请求将页面上的字段列表视为循环的;也就是说,从最后一个字段的 REQ_NEXT_FIELD 会转到第一个字段,而从第一个字段的 REQ_PREV_FIELD 会转到最后一个字段。 这些请求(以及 REQ_FIRST_FIELD 和 REQ_LAST_FIELD 请求)的字段顺序仅仅是表单数组中字段指针的顺序(由 new_form() 或 set_form_fields() 设置)。
也可以像字段已按屏幕位置顺序排序一样遍历字段,因此顺序是从左到右和从上到下。 为此,请使用第二组四个排序移动请求。
最后,可以使用视觉方向上下左右在字段之间移动。 为此,请使用第三组四个请求。 但是请注意,对于这些请求,表单的位置是其左上角。
例如,假设您有一个多行字段 B,以及两个与 B 在同一行上的单行字段 A 和 C,其中 A 在 B 的左侧,C 在 B 的右侧。 从 A 发出的 REQ_MOVE_RIGHT 只有在 A、B 和 C 都共享同一第一行时才会转到 B;否则它将跳过 B 转到 C。
这些请求驱动编辑光标在当前选定字段内的移动。
REQ_NEXT_CHAR 移动到下一个字符。
REQ_PREV_CHAR 移动到上一个字符。
REQ_NEXT_LINE 移动到下一行。
REQ_PREV_LINE 移动到上一行。
REQ_NEXT_WORD 移动到下一个单词。
REQ_PREV_WORD 移动到上一个单词。
REQ_BEG_FIELD 移动到字段的开头。
REQ_END_FIELD 移动到字段的末尾。
REQ_BEG_LINE 移动到行的开头。
REQ_END_LINE 移动到行的末尾。
REQ_LEFT_CHAR 在字段中向左移动。
REQ_RIGHT_CHAR 在字段中向右移动。
REQ_UP_CHAR 在字段中向上移动。
REQ_DOWN_CHAR 在字段中向下移动。
每个单词都与前一个和后一个字符用空格分隔。 移动到行或字段开头和结尾的命令会在其范围内查找第一个或最后一个非填充字符。
动态增长的字段以及显式创建了屏幕外行的字段是可滚动的。 单行字段水平滚动;多行字段垂直滚动。 大多数滚动是由编辑和字段内移动触发的(库滚动字段以保持光标可见)。 可以使用以下请求显式请求滚动
REQ_SCR_FLINE 垂直向前滚动一行。
REQ_SCR_BLINE 垂直向后滚动一行。
REQ_SCR_FPAGE 垂直向前滚动一页。
REQ_SCR_BPAGE 垂直向后滚动一页。
REQ_SCR_FHPAGE 垂直向前滚动半页。
REQ_SCR_BHPAGE 垂直向后滚动半页。
REQ_SCR_FCHAR 水平向前滚动一个字符。
REQ_SCR_BCHAR 水平向后滚动一个字符。
REQ_SCR_HFLINE 水平向前滚动一个字段宽度。
REQ_SCR_HBLINE 水平向后滚动一个字段宽度。
REQ_SCR_HFHALF 水平向前滚动半个字段宽度。
REQ_SCR_HBHALF 水平向后滚动半个字段宽度。
为了滚动,字段的一页是其可见部分的高度。
当您将 ASCII 字符传递给表单驱动程序时,它被视为将字符添加到字段数据缓冲区的请求。 这是否是插入还是替换取决于字段的编辑模式(默认情况下是插入)。
以下请求支持编辑字段和更改编辑模式
REQ_INS_MODE 设置插入模式。
REQ_OVL_MODE 设置覆盖模式。
REQ_NEW_LINE 新行请求(说明见下文)。
REQ_INS_CHAR 在字符位置插入空格。
REQ_INS_LINE 在字符位置插入空行。
REQ_DEL_CHAR 删除光标处的字符。
REQ_DEL_PREV 删除光标处的上一个单词。
REQ_DEL_LINE 删除光标处的行。
REQ_DEL_WORD 删除光标处的单词。
REQ_CLR_EOL 清除到行尾。
REQ_CLR_EOF 清除到字段末尾。
REQ_CLR_FIELD 清除整个字段。
REQ_NEW_LINE 和 REQ_DEL_PREV 请求的行为很复杂,并且部分由一对表单选项控制。 当光标位于字段的开头或字段的最后一行时,会触发特殊情况。
首先,我们考虑 REQ_NEW_LINE
在插入模式下,REQ_NEW_LINE 的正常行为是在编辑光标的位置断开当前行,将光标后的当前行部分作为新行插入到当前行之后,并将光标移动到该新行的开头(您可以将其视为在字段缓冲区中插入换行符)。
在覆盖模式下,REQ_NEW_LINE 的正常行为是清除从编辑光标位置到行尾的当前行。 然后光标移动到下一行的开头。
但是,字段开头的 REQ_NEW_LINE 或字段最后一行的 REQ_NEW_LINE 会执行 REQ_NEXT_FIELD。 如果 O_NL_OVERLOAD 选项关闭,则此特殊操作将被禁用。
现在,让我们考虑 REQ_DEL_PREV
REQ_DEL_PREV 的正常行为是删除上一个字符。 如果插入模式开启,并且光标位于行的开头,并且该行上的文本可以容纳在前一行中,则它会将当前行的内容附加到前一行,并删除当前行(您可以将其视为从字段缓冲区中删除换行符)。
但是,字段开头的 REQ_DEL_PREV 会被视为 REQ_PREV_FIELD。
如果 O_BS_OVERLOAD 选项关闭,则此特殊操作将被禁用,并且表单驱动程序只会返回 E_REQUEST_DENIED。
既然您已经了解了 ncurses 及其姊妹库的功能,您正准备撸起袖子,为一个大量操作屏幕的项目做准备。 但是等等... 在纯 ncurses 中甚至使用附加库编写和维护复杂的 GUI 小部件可能非常困难。 有一些现成的工具和 Widget 库可以使用,而不是编写自己的 Widget。 您可以使用其中的一些,从代码中获取灵感,甚至可以扩展它们。
用作者的话来说
CDK 代表 “Curses 开发工具包”,它目前包含 21 个现成的 Widget,这些 Widget 有助于快速开发全屏 curses 程序。
该工具包提供了一些有用的 Widget,可以直接在您的程序中使用。 它编写得很好,文档也非常好。 示例目录中的示例对于初学者来说是一个很好的起点。 CDK 可以从 http://invisible-island.net/cdk/ 下载。 按照 README 文件中的说明进行安装。
以下是 cdk 提供的 Widget 列表及其描述。
Widget Type Quick Description =========================================================================== Alphalist Allows a user to select from a list of words, with the ability to narrow the search list by typing in a few characters of the desired word. Buttonbox This creates a multiple button widget. Calendar Creates a little simple calendar widget. Dialog Prompts the user with a message, and the user can pick an answer from the buttons provided. Entry Allows the user to enter various types of information. File Selector A file selector built from Cdk base widgets. This example shows how to create more complicated widgets using the Cdk widget library. Graph Draws a graph. Histogram Draws a histogram. Item List Creates a pop up field which allows the user to select one of several choices in a small field. Very useful for things like days of the week or month names. Label Displays messages in a pop up box, or the label can be considered part of the screen. Marquee Displays a message in a scrolling marquee. Matrix Creates a complex matrix with lots of options. Menu Creates a pull-down menu interface. Multiple Line Entry A multiple line entry field. Very useful for long fields. (like a description field) Radio List Creates a radio button list. Scale Creates a numeric scale. Used for allowing a user to pick a numeric value and restrict them to a range of values. Scrolling List Creates a scrolling list/menu list. Scrolling Window Creates a scrolling log file viewer. Can add information into the window while its running. A good widget for displaying the progress of something. (akin to a console window) Selection List Creates a multiple option selection list. Slider Akin to the scale widget, this widget provides a visual slide bar to represent the numeric value. Template Creates a entry field with character sensitive positions. Used for pre-formatted fields like dates and phone numbers. Viewer This is a file/information viewer. Very useful when you need to display loads of information. =========================================================================== |
最近的版本中,Thomas Dickey 修改了一些 Widget。
除了使用户更容易使用现成的 Widget 之外,cdk 还优雅地解决了打印多色字符串和对齐字符串的一个令人沮丧的问题。 特殊的格式化标签可以嵌入到传递给 CDK 函数的字符串中。 例如
如果字符串
"</B/1>This line should have a yellow foreground and a blue background.<!1>" |
作为 newCDKLabel() 的参数给出,它将以黄色前景和蓝色背景打印该行。 还有其他标签可用于对齐字符串、嵌入特殊绘图字符等。 有关详细信息,请参阅手册页 cdk_display(3X)。 该手册页通过很好的示例解释了用法。
很久很久以前,在 1994 年 9 月,当很少有人知道 linux 时,Jeff Tranter 在 Linux Journal 上撰写了一篇关于 dialog 的 文章。 他以这些话开始了这篇文章……
Linux 基于 Unix 操作系统,但也具有许多独特而有用的内核特性和应用程序,这些特性和应用程序通常超越了 Unix 下可用的功能。 一个鲜为人知的瑰宝是 “dialog”,它是一个用于从 shell 脚本中创建外观专业的对话框的实用程序。 本文对 dialog 实用程序进行了教程式的介绍,并展示了如何以及在何处使用它的示例。
正如他解释的那样,dialog 是轻松制作外观专业的对话框的真正瑰宝。 它可以创建各种对话框、菜单、复选框列表等。它通常默认安装。 如果没有,您可以从 Thomas Dickey 的站点下载它。
上述文章很好地概述了它的用途和功能。 手册页有更多详细信息。 它可以用于各种情况。 一个很好的例子是在文本模式下构建 linux 内核。 Linux 内核使用了针对其需求量身定制的 dialog 修改版本。
dialog 最初设计用于 shell 脚本。 如果您想在 c 程序中使用其功能,那么您可以使用 libdialog。 关于这方面的文档很少。 权威参考是库附带的 dialog.h 头文件。 您可能需要在这里和那里进行一些 hack 才能获得所需的输出。 源代码很容易自定义。 我曾在多次场合通过修改代码来使用它。
perl 模块 Curses、Curses::Form 和 Curses::Widgets 允许从 perl 访问 curses。 如果您安装了 curses 和 basic perl,您可以从 CPAN 所有模块页面 获取这些模块。 获取 Curses 类别中的三个压缩模块。 安装后,您可以像使用任何其他模块一样从 perl 脚本中使用这些模块。 有关 perl 模块的更多信息,请参阅 perlmod 手册页。 上述模块附带良好的文档,并且它们有一些演示脚本来测试功能。 虽然提供的 Widget 非常简陋,但这些模块提供了从 perl 访问 curses 库的良好途径。
我的一些代码示例由 Anuradha Ratnaweera 转换为 perl,它们可在perl目录中找到。
有关更多信息,请参阅手册页 Curses(3)、Curses::Form(3) 和 Curses::Widgets(3)。 这些页面仅在获取并安装上述模块后才安装。
本节包含一些我为了好玩而编写的程序。 它们并不代表更好的编程实践或使用 ncurses 的最佳方式。 提供它们是为了让初学者获得灵感并将更多程序添加到本节。 如果您编写了一些不错的、简单的 curses 程序并希望将它们包含在此处,请联系 我。
生命游戏是数学的奇迹。 用 Paul Callahan 的话说
The Game of Life (or simply Life) is not a game in the conventional sense. There are no players, and no winning or losing. Once the "pieces" are placed in the starting position, the rules determine everything that happens later. Nevertheless, Life is full of surprises! In most cases, it is impossible to look at a starting position (or pattern) and see what will happen in the future. The only way to find out is to follow the rules of the game. |
该程序从一个简单的倒 U 形图案开始,并展示了生命是如何奇妙地运作的。 该程序还有很大的改进空间。 您可以让用户输入他选择的图案,甚至从文件中获取输入。 您还可以更改规则并尝试许多变体。 在 google 上搜索有关生命游戏的有趣信息。
文件路径:JustForFun/life.c
幻方,另一个数学奇迹,非常容易理解,但制作起来非常困难。 在幻方中,每行、每列的数字之和都相等。 甚至对角线之和也可以相等。 有许多具有特殊性质的变体。
该程序创建一个简单的奇数阶幻方。
文件路径:JustForFun/magic.c
NCURSES 手册页
NCURSES FAQ,网址:http://invisible-island.net/ncurses/ncurses.faq.html
Eric Raymond 和 Zeyd M. Ben-Halim 撰写的《使用 NCURSES 编写程序》,网址:http://invisible-island.net/ncurses/ncurses-intro.html - 有些过时。 我受到了本文档的启发,并且本 HOWTO 的结构源于原始文档