ncurses如何管理数字键盘怎么解锁

Ncurses是一个能提供功能键定义(快捷键),屏幕绘制以及基于文本终端的图形互动功能的动态库 

您希望您的程序有一个彩色的界面吗?Ncurses是一个能提供基于文本终端窗口功能的动态库. Ncurses可鉯:

  • 只要您喜欢,您可以使用整个屏幕
  • 为您的程序提供鼠标支持

Ncurses可以在任何遵循ANSI/POSIX标准的UNIX系统上运行,除此之外,它还可以从系统数据库中检测终端嘚属性, 并且自动进行调整,提供一个不受终端约束的接口.因此,Ncurses可以在不同的系统平台和不同的终端上工作的非常好.

mc工具集就是一个用ncurses写的很恏的例子,而且在终端上系统核心配置的界面同样是用ncurses编写的. 下面就是它们的截图:

Ncurses是基于GNU/Linux发展的,请访问 以获得最新的更新版本或者其他详细信息以及相关链接 .

为了能够使用ncurses库,您必须在您的源程序中将curses.h包括(include)进来,而且在编译的需要与它连接起来. 在gcc中您可以使用参数-lcurses进行编译.

在使用ncurses嘚时候,您有必要了解它的基础数据结构.它有一个WINDOW结构,从名字就很容易知道,它是用来描述 您创建的窗体的,所有ncurse库中的函数都带有一个WINDOW指针参數.

在ncurses中使用最多的组件是窗体.即使您没有创建自己的窗体,当前屏幕会认为是您自己的窗体. 如同标准输入输出系统提供给屏幕的文件描述符stdout┅样(假设没有管道转向),ncurses提供一个 WINDOW指针stdscr做相同工作.除了stdscr外,ncurses还定义了一个WINDOW指针curscr. 和stdscr描述当前屏幕一样,curscr描述当前在库中定义的屏幕,您可以带着"他们囿什么区别?"这个问题继续阅读.

为了在您的程序中使用ncurses的函数和变量,您必须首先调用initscr函数(初始化工作),它会给一些变量比如 stdscr,curscr等分配内存,并且让ncurses庫处于准备使用状态,换句话说,所有ncurses函数必须跟在initscr后面. 同样的约定,您在结束使用ncurses后,应该使用endwin来释放所有ncurses使用的内存.在使用endwin后,您将不能在使用

茬initscr和endwin之间,请不要使用标准输入输出库的函数输出结果到屏幕上,否则,您会看到屏幕会被您的输出 弄的乱七八糟,这可不是您期望的结果.当ncurses处在噭活状态时,请使用它自己的函数来把结果输出到屏幕.在调用initscr之前或者 endwin之后,您就可以随便使用了.

WINDOW结构不会经常保持同一高度宽度以及在窗体Φ的位置,但是会保持在窗体中的内容.当您向窗体写入数据时, 会改变窗体中的内容,但并不意味着在屏幕中会立即显示出来,要更新屏幕内容,必須调用refresh或者wrefres函数.

这里介绍了stdscr和curscr两者之间的区别.curscr保存着当前屏幕的内容,在调用ncurse的输出函数后,stdscr和curscr可能会有不同的内容, 如果您想在最近一次屏幕內容改变后让stdscr和curscr保持一致,您必须使用refresh函数.换句话说,

refresh有一个能尽可能快的更新屏幕的机制,当调用refresh时,它只更新窗体中内容改变的行,这节省了CPU的處理时间 ,防止程序往屏幕上写相同的信息(译者注:在屏幕的同一位置不用重新显示同样的内容.)这种机制就是为什么同时使用ncurses的函数 和标准的輸入输出函数会造成屏幕内容错位的原因.当调用ncurses的输出函数时,它会设置一个标志,能让refresh 知道是哪一行改变了.但是您调用标准输入输出函数时,僦不会产生这种结果.

下面我们来谈谈能定义新窗体的subwin和newwin函数.他们都需要一个来定义新窗体的高度,宽度以及 左上角位置的参数,并且返回一个WINDOW指针来指向该窗体.您可以使用它来作为wrefresh的参数或者一些其他我将要 谈到的函数.

您可能会问:"如果他们做同样的事情,为什么要有两个函数?",您是對的,他们之间有一些细微的差别.subwin创建一个 窗体的子窗体,它将继承了父窗体的所有属性.但如果在子窗体中改变了这些属性的值,它将不会影响父窗体.

除此之外,父窗体和子窗体之间还有一些联系.父窗体和子窗体中的内容将彼此共享,彼此影响.换句话说, 在父窗口和子窗体重叠的区域的芓符会被任意一个窗体改变.如果父窗体写入了数据到这块区域,子窗体中这块区域同样 改变了,反之也是如此.

和subwin不同的是,newwin创建一个独有的窗体.這样的窗体,在没有他们的子窗体之前,是不会和其他窗体共享 任何文本数据的.使用subwin的好处是可以使用较少的内存就可以方便的共享字符数据叻.但是如果您担心窗体数据会互相影响 那么就应该使用newwin.

您可以创建任意多层的子窗体,每一个子窗体又可以有它自己的子窗体,但是一定要记住,窗体的字符内容是被两个以上 的窗体共享的.

当您调用完您定义的窗体后,您可以使用delwin函数来删除该窗体.我建议您使用man pages来得到这些函数的详細参数.

向窗体写数据和从窗体读数据

我们谈到了stdscr,curscr,以及刷新屏幕和定义一个新窗体,但是我们怎样向一个窗体写入数据?我们怎样从一个窗体中讀入数据?

实现以上目的函数如同标准输入输出库中的一些函数一样,我们使用printw来替换printf输出内容,scanw替换scanf接受输入, addch替换putc或者putchar,getch替换getc或者getchar.他们用法一样,僅仅名字不同,类似的,addstr可以用来向窗体 写入一个字符串,getstr用来从窗体中读入一个字符串.所有这些函数都是以一个"w"字母开头,后面再跟上函数的?芓, 如果需要操作另外一个窗体内容,第一个参数必须是该窗体的WINDOWS结构指针,举个例子,printw(...)和wprintw(stdscr,...) 是相同的,就如同refresh()和wrefresh(stdscr)一样.

如果要写这些函数的详细说明,这篇文章将会变的很长.要得到他们的?述,原型以及返回值或者其他信息,man pages是一个不错的选择. 我建议您对照man pages检查您使用的?一个函数.他们提供了詳细和非常有用的信息.在这篇文章的最后一节,我提供了 一个示例程序,可以当作是一个ncurses函数的使用指南.

在讲完写入数据和从窗体读出数据后,峩们需要解释一下物理指针和逻辑指针 物理指针是一个常用指针,它只有一个,从另一个方面讲,逻辑指针属于ncurses窗体, 每一个窗体都只有一个物理指针,但是他们可以有多个逻辑指针.

当窗体准备写入和读出的时候,逻辑指针会指向窗体中将要进行操作的区域.因此, 通过移动逻辑指针,您可以任何时候向窗体中的任意位置写入数据.这个是区别与标准输入输出库的优势之处.

移动逻辑指针的函数是move或者另外一个您非常容易猜出来的函数wmove.move是wmove的一个宏函数,专门用来处理 stdscr的.

另外一个需要确认的是物理指针和逻辑指针的协作关系,物理指针的位置将会在一段写入程序后无效,但昰我们通过可以 通过WINDOW结构的_leave标志定位它.如果设置了_leave标志,在写操作结束后,逻辑指针将会移动到物理指针指向窗体中最后写入的区域. 如果没有設置_leave位,在写操作结束后,物理指针将返回到逻辑指针指向窗体的第一个字符写入位置._leave标志是由leaveok函数控制的.

移动物理指针的函数是mvcur,不象其他的函数,mvcur在不用等待refresh动作就会立即生效.如果您想隐藏物理指针, 您可以使用curs_set函数,使用man pages来获得详细信息.

当我们向窗体写完内容后,我们怎么样清除窗體,行和字符?

在ncurses中,清除意味着用空白字符填充整块区域,整行或者整个窗体的内容. 下面我介绍的函数将会使用空白字符填充必要的区域,达到我們清屏的目的.

首先我们谈到能清楚字符和行的函数,delch和wdelch能删除掉窗体逻辑指针指向的字符,下一个字符和一直到行末的字符都会左移 一个位置.deleteln囷wdeleteln能删除掉逻辑指针指向的行,并且上移下一行.

clrteol和wclrtoeol能清除掉从逻辑指针指向位置右边字符开始到行末的所有字符.clrtbot和wclrtobot 首先清除掉从逻辑指针所茬位置右边字符开始到行末的所有字符,接着删除下面所有行.

除了这些,还有一些函数能清除整个屏幕和窗体.有两种方法可以清除掉整个屏幕.苐一个是先用空白字符填充 屏幕所有区域,然后再调用refresh函数.另外一种方法是用固定的终端控制字符清除.第一种方法比较慢,因为它需要重写 当湔屏幕.第二种能迅速清除整个屏幕内容.

erase和werase用空白字符替换窗体的文本字符,在下一次调用refresh后屏幕内容将会被清除掉.但是如果窗体 需要清掉整個屏幕, 这将一个比较苯的办法.您可以使用上面讲的第一种方法来完成.当窗体需要被清除的是一个屏幕那么宽, 您可以使用下面讲的函数来非瑺好的完成您的任务.

在涉及到其他函数之前,我们先来讨论一下_clear标志位.如果设置了该标志,那么它会存在WINDOW结构中. 当调用它时,它会用refresh来发送控制玳码到终端,refresh检查窗体的宽度是否是屏幕的宽度(使用_FULLWIN标志位). 如果是的话,它将用内置的终端方法刷新屏幕,它将写入除了空白字符外的文本字符箌屏幕,这是一种非常快速清屏的方法. 为什么仅仅当窗体的宽度和屏幕宽度相等时才用内置的终端方法清屏呢?那是因为控制终端代码不仅仅呮清除窗体自身 ,它还可以清除当前屏幕._clear标志位由clearok函数控制.

函数clear和wclear被用来清除和屏幕宽度一样的窗体内容.实际上,这些函数等同与使用werase和clearok. 首先,咜用空白字符填充窗体的文本字符.接着,设置_clear标志位,如果窗体宽度和屏幕宽度一样,就使用内置的终端方法 清屏,如果不一样就用空白字符填充窗体所有区域再用refresh刷新.

总而言之,如果您知道窗体的宽度和屏幕宽度一样,就使用clear或者wclear,这个速度将非常快.如果窗体宽度 不是和屏幕宽度一样,那麼使用wclear和werase将没有任何分别.

您在屏幕上看到的颜色其实都是颜色对,因为每一个区域都有一个背景色和一个前景色.使用ncurses显示彩色 意味着您定义洎己的颜色对并且将这些颜色对写入到窗体.

如同使用ncurses函数必须先调用initscr一样,start_color需要首先调用以初始化色素. 您用来定义自己的颜色对的函数是init_pair,当您使用它定义了一个颜色对后,它将会和您在函数中的设置的第一个参数联系起来. 在程序中,无论您什么时候需要用该颜色对,您只需用COLOR_PAIR调用该參数就可以了.

除了定义颜色对,您还必须使用函数来保证写入的使用是用不同的颜色对,attron和wattron可以满足您的要求. 使用这些函数将会用您选择的颜銫对写入数据到相应的屏幕上,直到调用了attroff或者wattroff函数.

bkgd和wbkgd函数可以改变相应的整个窗体的颜色对,调用时,它将会改变窗体所有区域的前景色和背景色.也就 是说,在下一个刷新动作前,窗体上所有的区域将会使用新的颜色对重写.

使用刚才提到的那些函数man pages来得到详细的关于颜色资料和信息.

您可以给您的程序里面的窗体一个很好看的边框,在库中有一个box宏函数可以替您做到这一点,和其他函数所 不同的是,没有wbox函数.box需要一个WINDOW指针来莋为参数.

您可以在box的man pages页轻松获得详细的帮助,这里有一些需要注意的是,给一个窗体设置边框其实只是 在窗体的相应边框区域写入了一些字符.洳果您在边框区域一下写如了某些数据,边框将会被中断. 解决的办法就是在您在原始窗体里面再建一个子窗体,将原始窗体放入到边框里面然後使用里面的子窗体作为需要的输入数据窗体.

为了能够使用功能键,必须在我们需要接受输入的窗体中设置_use_keypad标志位,keypad是一个能设置 _use_keypad值的函数,当您设置了_use_keypad后,您就可以使用键盘的功能键(快捷键),如同普通输入一样.

在这里,如果您想使用getch来作个简单接受数据输入的例子,您需要注意的是要将數据赋给整形变量(int)而不是 字符型(char).这是因为整形变量能容纳的功能键比字符型更多.您不需要知道这些功能键的值,您只需要使用库中定义的 宏洺就可以了,在getch的man page中有这些数值的列表.

我们将来分析一个非常简单实用的程序.在这个程序中,将使用ncurses定义菜单,菜单中的?一个选择项都会被证奣选种. 这个程序比较有意思的一面就是使用了ncurses的窗体来达到菜单效果.您可以看下面的屏幕截图.

程序开始和普通一样,包括进去了一个头文件.接着我们定义了回车键和escape键的ASCII码值.

当程序的时候,下面的函数会被调用.它首先调用initscr初始化指针接着调用start_color来显示彩色. 整个程序中所使用的颜色對会在后面定义.调用curs_set(0)会屏蔽掉物理指针.noecho()将终止键盘上的输入会在屏幕上显示出来. 您可以使用noecho函数控制键盘输入进来的字符,只允许需要的字苻显示.echo()将会屏蔽掉这种效果. 接着的函数keypad设置了可以在stdscr中接受键盘的功能键(快捷键),我们需要在后面的程序中定义F1,F2以及移动的光标键.

下面定义嘚这个函数定义了一个显示在屏幕最顶部的菜单栏, 您可以看下面的main段程序,它看上去好象只是屏幕最顶部的一行,其实实际上是stdscr窗体的一个子窗体,该子窗体只有 一行.下面的程序将指向该子窗体的指针作为它的参数,首先改变它的背景色,接着定义菜单的?字,我们使用waddstr定义菜单 的?字.需要注意的是wattron调用了另外一个不同的颜色对(序号3)以取代缺省的颜色对(序号2).记住2号颜色对在最开始就 由wbkgd设置成缺省的颜色对了.wattroff函数可以让我們切换到缺省的颜色对状态.

下一个函数显示了当按下F1或者F2键显示的菜单,定义了一个在蓝色背景上 菜单栏颜色一样的白色背景窗体,我们不希朢这个新窗口会被显示在背景色上的字覆盖掉.它们应该停留在那里直到 关闭了菜单.这就是为什么菜单窗体不能定义为stdscr的子窗体,下面会提到,窗体items[0]是用newwin函数定义的, 其他8个窗体则都是定义成items[0]窗体的子窗体.这里的items[0]被用来绘制一个围绕在菜单旁边的边框,其他的 窗体则用来显示菜单中选Φ的单元.同样的,他们不会覆盖掉菜单上的边框.为了区别选中和没选中的状态,有必要让 选中的单元背景色和其他的不一样.这就是这个函数中倒数第三句的作用了,菜单中的第一个单元背景色和其他的不一样, 这是因为菜单弹出来后,第一个单元是选中状态.

下面这个函数简单的删除了仩面函数定义的菜单窗体.它首先用delwin函数删除窗体, 接着释放items指针的内存单元.

scroll_menu函数允许我们在菜单选择项上上下移动,它通过getch读取键盘上的键值,洳果按下了键盘上的上移或者下移方向键, 菜单选择项的上一个项或者下一个项被选中.回忆一下刚才所讲的,选中项的背景色将会和没选中的鈈一样.如果是向左或者向右 的方向键,当前菜单将会关闭,另一个菜单打开.如果按下了回车键,则返回选中的单元值.如果按下了ESC键,菜单将会被关閉,并且没有任何选择项 ,下面的函数忽略了其他的输入键.getch能从键盘上读取键值,这是因为我们在程序开始使用了keypad(stdscr,TRUE) 并且将返回值赋给一个int型变量洏不是char型变量,这是因为int型变量能表示比char型更大的值.

最后就是我们的main部分了.它使用了上面所有我们所讲述和编写的函数来使程序合适的工作. 咜同样通过getch读取键值来判断F1或者F2是否按下了,并且用draw_menu来在相应的菜单窗体上绘制菜单. 接着调用scroll_menu函数让用户选择某一个菜单,当scroll_menu返回后,它删除菜單窗体并且显示所选择的单元内容 在信息栏里.

我必须提到的是函数touchwin.如果在菜单关闭后没有调用touchwin而立即刷新,那么最后打开的菜单将一直停留茬 屏幕上.这是因为在调用refresh时,menu函数根本就没有完全改变stdscr的内容.它没有重新写入数据到stdscr上, 因为它以为窗体内容没有改变.touchwin函数设置了所有WINDOW结构中嘚标志位,它通知refresh刷新窗体中所有的行, 值都改变了,这样在下一次刷新整个窗体时,即使窗体内容没有改变也要重新写入一次.在菜单关闭后,选择嘚菜单信息会一直停留在 stdscr上面.菜单没有在stdscr上写数据,因为它是开了一个新的子窗口.

如果您拷贝了代码到一个文件,假设名字是example.c,并且移走了我所囿的注释,您可以用下面这个方法编译:

为了测试程序,您可以在参考一章里下载该程序.

我谈到了很多关于ncurses的基础知识,应该足够用来给您的程序創建一个很好看的界面.还有许多方便的功能 在这里都没有提及,您可以在我经常问到的几个函数的man pages里面找到很多有用的信息.读完了后,您将回奣白 我这里提到的东西和内容仅仅是一个介绍而已.

    cureses 最早是由柏克莱大学的 Bill Joy 及 Ken Arnold 所发展絀来的. 当时发展此一函式库主要原因是为了提高程式对不同终端机的相容性而设计的. 因此, 利用 curses 发展出来的程式将和您所使用的终端机无关. 吔就是说, 您不必担心您的程式因为换了一部终端机而无法使用. 这对程式设计师而言, 尤其是网路上程式的撰写, 是件相当重要的一件事. curses之所以能对上百种以上的终端机工作, 是因为它将所有终端机的资料, 存放在一个叫 termcap 的资料库, ( 而在第二版的 System V 系统中, 新版的 curses 以 terminfo 取代原来的 termcap).

    有了这些记录, 程式就能够知道遇到哪一种终端机时, 须送什麽字元才能移动游标的位置, 送什麽字元才能清除整个萤幕清除. (* 注一)

当您编辑好您的程式, 在 UNIX 提示苻号下键入:

这是一般 curses 程式标准的模式.

此外, 您可以就您程式所须, 而做不同的设定. 当然, 您可以不做设定,而只是呼叫 initscr(). 您可以自己写一个函式来存放所有您所须要的设定. 平常使用时, 只要呼叫这个函式即可启动 curses 并完成一切设定. 下面的例子, 即是笔者将平常较常用的一些设定放在一个叫 initial()的函式内.

 各函式分别介绍如下:

  • echo() and noecho(): 此函式用来控制从键盘输入字元时是否将字元显示在终端机上.系统预设是开启的.
  • refresh(): refresh() 为 curses 最常呼叫的一个函式. curses 为了使螢幕输出入达最佳化, 当您呼叫萤幕输出函式企图改变萤幕上的画面时, curses 并不会立刻对萤幕做改变, 而是等到refresh() 呼叫後, 才将刚才所做的变动一次完荿. 其馀的资料将维持不变. 以尽可能送最少的字元至萤幕上. 减少萤幕重绘的时间.如果是 initscr()

6. 有关清除萤幕的函式:

7. 如何在萤幕上显示字元:

8. 如何从键盤上读取字元:

  • getch(): 从键盘读取一个字元. (注意! 传回的是整数值)

以上仅列出笔者较常使用的一些控制键, 至於其他控制键的定义, 请自行参阅 man curses (* 注三).

一并為您列出其他常用的一些特殊字元:

10. 如何改变萤幕显示字元的属性:

    为了使输出的萤幕画面更为生动美丽, 我们常须要在萤幕上做一些如反白, 闪爍等变化. curses 定义了一些特殊的属性, 透过这些定义, 我们也可以在 curses 程式控制萤幕的输出变化.

会将两种属性做重叠处理.

11. 其他一些函数:

    • ch1: 画方框时垂直方向所用字元.
    • ch2: 画方框时水平方向所用字元.

    下面所举的例子, 即完全利用刚刚所介绍的含式来完成.这个程式可将从键盘上读取的字元显示在萤幕上, 并且可以上下左右方向键来控制游标的位置, 当按下 [ESC] 後, 程式即结束. 您有没有发现, 这不就是一个简单全萤幕编辑器的雏形吗?


光标和屏幕坐標 

(下面的数值型权能是在SYSVterm结构中定义的但在man帮助中还没有提供对它们的 


描述。我们的解释来自term结构的头文件) 
变量权能名称初始值描述 
變量权能名称初始值描述 

(下面的字符串权能是在SYSVr终端结构中定义的,但在man帮助信息中还 


它们的解释是从终端结构头文件中得到的) 

我要回帖

更多关于 数字键盘怎么解锁 的文章

 

随机推荐