曲径通幽论坛

 找回密码
 立即注册
搜索
楼主: beyes
打印 上一主题 下一主题

[显示] [专题]通过 curses 管理基于文本的屏幕控制

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
11#
 楼主| 发表于 2009-3-21 01:21:10 | 只看该作者

键盘.键盘模式

读键盘例程是由工作模式控制的。对工作模式进行设置的函数如下:
#include <curses.h>
int echo(void);
int noecho(void);
int cbreak(void);
int nocbreak(void);
int raw(void);
int noraw(void);

两个 echo 函数简单的打开互和关闭输入字符的回显。余下的 4 个函数控制着在终端中输入的字符是如何被 curses 程序所用的。
为了解释 cbreak,就必须理解默认的输入模式。当一个 curses 程序由调用 initscr 开始时,输入模式被设置为 ”预处理模式“( cooked mode )。这意味着所有的处理都以文本行为单位的。也就是说,输入仅在用户敲入回车键后才有效。键盘的特殊字符处于被激活状态,所以键入一个合适的字符序列或组合可以在程序中产生一个信号。如果正在运行的终端是来自另一个终端,流控制( flow control )也处于激活状态。

通过调用 cbreak, 一个程序可以设置输入模式为 “字符终止模式”( cbreak mode ),即在用户一键入字符后,就会立马为程序所用,而不是先缓冲起来并等到用户键入回车后才可用。与“预处理模式”一样,“字符终止模式”里键盘特殊键也处于激活状态;但对于一些简单的按键,如“退格键”(Backspace),会直接传递到程序中被处理,所以如果希望 Backspace 键有自己所期望的功能,那只有自己对其编程。

raw 调用关闭特殊字符处理功能,所以在键入特殊字符序列时并不会产生信号和流控制。
nocbreak 把输入模式设置回“预处理模式”,但特殊字符处理功能保持不变。
noraw 还原“预处理模式”和特殊字符处理功能。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
12#
 楼主| 发表于 2009-3-21 10:59:11 | 只看该作者

键盘.输入

读键盘的操作较简单,有以下几个主要的函数:
#include <curses.h>
int getch(void);
int getstr(char *string);
int getnstr(char *string, int number_of_characters);
int scanw(char *format, ...);
[/table][table=100%,#f9f7ed]
这些函数与他们的 ”非curses“ 对等函数,如 getchar, gets, scanf 的行为非常相似。
注意:getstr() 函数并没有对返回的字符串的长度没做限制,所以必须非常小心的使用。
如果你的 curses 版本支持 getnstr,此函数可以限制读入字符的长度。建议使用此函数,而不是 getstr 。它们与 gets 和 fgets 的行为相类似。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
13#
 楼主| 发表于 2009-3-21 14:13:12 | 只看该作者

测试 cbreak mode 示例

测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

void input();

int main()
{
        initscr();

        standout();
        move(9,10);
        printw("%s", "----Test nocbeak mode----");
        refresh();
        standend();
        input();
        sleep(3);

        standout();
        move(9,10);
        printw("----Test cbreak-mode----");
        refresh();
        standend();

        cbreak();
        input();
        sleep(3);

        nocbreak();
        endwin();
        exit(EXIT_SUCCESS);
}

void input()
{

        move(10,10);
        printw("%s", "Inpuut y/n: ");
        refresh();

        if(getch()== 'y' || getch()== 'n') {
                move(12, 10);
                printw("%s", "Input correct");
                refresh();
        } else {
                move(12, 10);
                printw("%s", "Input Wrong");
                refresh();
        }
}

程序说明
一开始在默认情况下,程序处于 non-cbreak-mode 模式,也就是 getch() 需要等待键入回车后才能收到输入的字符;
在输入回车后,程序判断了输入字符的对错,然后进入 cbreak-mode 模式,这时候就只要输入一个字符,无需再回车,程序马上做出判断。此后显示完后退出。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
14#
 楼主| 发表于 2009-3-21 14:50:27 | 只看该作者

键盘输入(模式与检测)测试示例

键盘控制测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>
#include <string.h>

#define PW_LEN 256
#define NAME_LEN 256

int main()
{
        char name[NAME_LEN];
        char password[PW_LEN];
        const char *real_password = "xyzzy";
        int i = 0;

        initscr();
        printw("%s", "Please login:");

        move(7, 10);
        printw("%s", "User name: ");
        getstr(name);

        move(8, 10);
        printw("%s", "Password: ");
        refresh();

        cbreak();
        noecho();
        /*内存初始化,password所指向的内存区域的sizeof()个字节空间都为'\\0'*/
        memset(password, '\\0', sizeof(password));
        while(i < 5) {
                password = getch();
                if(password == '\\n') break;
                move(8, 20 + i);
                addch('*');
                refresh();
                i++;
        }

        echo();
        nocbreak();

        move(11, 10);
        if(strncmp(real_password, password, strlen(real_password)) == 0)
                printw("%s", "Correct");
        else    printw("%s", "Wrong");
        printw("%s", " password");
        refresh();
        sleep(3);

        endwin();
        exit(EXIT_SUCCESS);
}

程序说明
使用 noecho 函数关闭了键盘输入的回显功能,用 cbreak 函数把输入情况设置为 ” 字符中止模式“ 之后,开辟了一小块内存做好接收口令的准备工作。构成口令的每一个字符一经敲入就立刻得到处理。随后,在屏幕的下一个位置显示一个 "*" 号,注意每次要对屏幕进行刷新。用户按下回车后,我们使用 strncmp 把刚输入的口令和保存在程序里的口令字进行比较。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
15#
 楼主| 发表于 2009-3-21 19:09:10 | 只看该作者

窗口-窗口的结构

在物理屏幕上也可以同时显示多个不同尺寸的窗口。

stdscr 只是 WINDOW 结构的一个特例,就像 stdout 是一个文件流的特例一样。
WINDOW 结构一般在 curses.h 中有声明,对它的存取操作必须感规定的指令进行,程序永远不允许直接访问它,因为这个结构依赖于 curses 函数库的具体实现,它在不同版本的 curses 函数库里会有所变化。

你可以通过 newwindelwin 来创建和销毁窗口:
#include <curses.h>

WINDOW *newwin(int num_of_lines, int num_of_cols, int start_y, int start_x);
int delwin(WINDOW *window_to_delete);

newwin 函数创建一个新的窗口,窗口从屏幕位置(start_y, start_x)开始,尺寸由 num_of_lines 和 num_of_cols 来表示。此函数返回一个指向新窗口的指针,若创建失败则返回 null 。如果想让新窗口的右下角正好落在屏幕的右下角位置上,可以把它的行数和列数设置为 0 。窗口不论新旧大小,都不能超过当前屏幕。所以,若是新窗口的任何部分落在屏幕区域以外的地方 newwin 就会失败。由 newwin 创建的新窗口完全独立于任何现存的任何窗口。默认情况下,它会放在任何显存窗口的上面,遮盖(但不是改变)他们的内容。

delwin 函数删除由 newwin 创建的前一个窗口。因为调用 newwin 时可能已经分配过内存,所以当不再需要时应该将其删除掉。
注意:永远不要删除 curses 自己的窗口 -- stdscr 和 curscr 。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
16#
 楼主| 发表于 2009-3-21 20:31:31 | 只看该作者

窗口--通用化函数(generalized functions)

在前面已经使用过 addch 和 printw 函数在屏幕上增添字符。与其它函数一起,它们都可以在前面添加个前缀。如:
前缀 w 表示对窗口进行操作;
前缀 mv 表示光标移动;
前缀 mvw 表示移动和对。

如果到 curses 头文件里查看更多的 curses 实现,就会发现在前面接触到的许多函数是一些简单的宏定义(#define 语句),宏定义的内容则是更通用化的函数。

如果添加了 w 前缀,就必须在它的参数表面前增加一个 WINDOW 指针;

如果添加了 mv 前缀,就需要添加两个参数 --屏幕纵坐标 x 和屏幕横坐标 y ,这两个坐标值设设定了执行这一操作的屏幕位置。y 和 x 是相对于窗口的坐标值而不是相对于屏幕的坐标值,(0, 0)代表的是窗口的左上角。

添加了 mvw 前缀,需要添加 3 个参数,一个是 WINDOW 指针,另外两个是 y 和 x 值。WINOW 指针永远出现在屏幕坐标值的前面,可有时候从函数前缀上看好像应该把 y 和 x 坐标放在最前面,遇到这种情况千万不要犯糊涂,否则容易出问题。

下面是 addch 和 printw 系列全体函数的框架定义:
#include <curses.h>

int addch(const chtype char);
int waddch(WINDOW *window_pointer, const chtype char);
int mvaddch(int y, int x, const chtype char);
int mvwaddch(WINDOW *window_pointer, int y, int x, const chtype char);
int printw(char *format, ...);
int wprintw(WINDOW *window_pointer, char *format, ...);
int mvprintw(int y, int x, char *format, ...);
int mvwprintw(WINDOW *window_pointer, int y, int x, char *format, ...);
其它一些函数,比如 inch, 也有加 “mv" 和 "mvw" 前缀的通用化变体。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
17#
 楼主| 发表于 2009-3-21 20:59:38 | 只看该作者

窗口--移动和刷新窗口

以下函数可以移动和重新绘制窗口:
#include <curses.h>

int mvwin(WINDOW *window_to_move, int new_y, int new_x);
int wrefresh(WINDOW *window_ptr);
int wclear(WINDOW *window_ptr);
int werase(WINDOW *window_ptr);
int touchwin(WINDOW *window_ptr);
int scrollok(WINDOW *window_ptr, bool scroll_flag);
int scroll(WINDOW *window_ptr);

mvwin 函数在屏幕上移动窗口。因为不允许窗口任何部分超出屏幕区域。mvwin 如果试图移动窗口以致于它的任何部分落在了屏幕区域外头,那么忙这个函数就会失败。

wrefresh, wclear 和 werase 函数只是在前面所见过的函数的通用化版本。它们在加入了 WINDOW 指针后,它们就可以对指定的窗口进行操作,而不局限于 stdscr 。

touchwin 有些特殊。它通知 curses 库由参数指针所指向的窗口已经发生改变。这意味着,在下一次调用 wrefresh 时 curses 总将会重新绘制那个窗口。这个函数对于在屏幕上有几个函数堆叠的时需要安排哪一个窗口出来显示非常有用。

两个 scroll 函数控制窗口的卷屏情况。 scrollok 函数,当传递一个布尔值 true 时,其允许一个窗口卷屏。默认地,窗口是不会卷屏的。
scroll 函数就是简单的把窗口中的内容往上卷一行。一些 curses 实现也有 wsctl 函数,这可以一次卷几行内容,这个参数值还可能是负数。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
18#
 楼主| 发表于 2009-3-22 01:35:19 | 只看该作者

窗口函数的应用示例(多窗口)

测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
        WINDOW *new_window_ptr;
        WINDOW *popup_window_ptr;
        int x_loop;
        int y_loop;
        char a_letter = 'a';

        initscr();

        move(5, 5);
        printw("%s", "Testing multiple windows");
        refresh();
        sleep(2);


        for(y_loop = 0; y_loop < LINES - 1; y_loop++) {
                for(x_loop = 0; x_loop < COLS - 1; x_loop++) {
                        mvwaddch(stdscr, y_loop, x_loop, a_letter);
                        a_letter++;
                        if(a_letter > 'z') a_letter = 'a';
                }
        }
        /*刷新屏幕*/
        refresh();
        sleep(3);

        new_window_ptr = newwin(10, 20, 5, 5);/*创建新窗口*/
        mvwprintw(new_window_ptr, 2, 2, "%s", "Hello World");
        mvwprintw(new_window_ptr, 5, 2, "%s",
                        "Notice how very long lines wrap inside the window");
        wrefresh(new_window_ptr);
        sleep(3);

        a_letter = '0';
        for(y_loop = 0; y_loop < LINES - 1; y_loop++) {
                for(x_loop = 0; x_loop < COLS - 1; x_loop++) {
                        mvwaddch(stdscr, y_loop, x_loop, a_letter);
                        a_letter++;
                        if(a_letter > '9')
                                a_letter = '0';
                }
        }

        refresh();
        sleep(3);

        /*这里刷新并不会改变什么,因为确实没对new_window做什么改变*/
        wrefresh(new_window_ptr);
        sleep(6);

        /*new_window会被刷新带到前面来,因为 touchwin 函数欺骗了 curses ,以致 curses 认为 new_window 已经发生改变*/
        touchwin(new_window_ptr);
        wrefresh(new_window_ptr);
        sleep(3);

        popup_window_ptr = newwin(10, 20, 8, 8);
        box(popup_window_ptr, '+', '~'); /*给窗口指定边框*/
        mvwprintw(popup_window_ptr, 5, 2, "%s", "Pop Up Window!");
        wrefresh(popup_window_ptr);
        sleep(3);

        touchwin(new_window_ptr); /*切换到 new_window */
        wrefresh(new_window_ptr);
        sleep(3);
        wclear(new_window_ptr);   /*对 new_window 进行清屏*/
        wrefresh(new_window_ptr);
        sleep(3);
        delwin(new_window_ptr);  /*不再需要 new_window,释放掉*/

        touchwin(popup_window_ptr); /*切换到 popup_window */
        wrefresh(popup_window_ptr);
        sleep(3);

        delwin(popup_window_ptr); /*释放掉 popup */
        touchwin(stdscr);
        refresh();
        sleep(3);
        endwin();
        exit(EXIT_SUCCESS);
}

说明
curses 在刷新窗口的时候并不考虑窗口在屏幕上出现的先后顺序,所谓窗口之间的上下继承关系也无从谈起。为了确保 curses 能够以正确的顺序绘制窗口,就必须以正确的顺序对他们进行刷新。解决这个问题的办法之一是把全体窗口的指针都保存到一个数组或列表里,通过这个数组来安排它们显示到屏幕上去的正确顺序。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
19#
 楼主| 发表于 2009-3-22 01:49:31 | 只看该作者

窗口--优化窗口刷新

在上帖的例子中可见,刷新多窗口需要一些技巧,但并不至于无章可循。然而在一个慢速网络上,当刷新终端时,可能会出现较严重的潜在问题。幸运的是,这个问题在现在来说已经很少存在了。

我们的目标是,要尽可能的减少往屏幕上绘制的字符个数,因为在慢速的链路上对屏幕的绘制可能慢得令人无法忍受。curses 提供了一个非常手段 -- 用以下的两个函数:
#include <curses.h>

int wnoutrefresh(WINDOW *window_ptr);
int doupdate(void);

wnoutrefresh 函数决定需要往屏幕上送哪个字符,但实际上并没有发送它们。真正把字符发送到中断的工作由 doupdate 来完成。
如果你简单的调用 wnoutrefresh ,然后紧跟着调用 doupdate,其效果与调用 wrefresh 一样。
如果想重新绘制一叠窗口,可以对每个窗口都调用 wnoutrefresh (当然要有正确的顺序),然后在最后一个 wnoutrefresh 后统一调用 doupdate 。这样就可以让 curses 对轮流的对每一个窗口执行更新计算工作然后输出到屏幕上来。这种做法可以最大限度地减少需要 curses 发送的字符个数。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
20#
 楼主| 发表于 2009-3-22 15:06:17 | 只看该作者

窗口 -- 子窗口

多窗口的一种特殊情况是 "子窗口"。可以使用以下函数创建与销毁子窗口:
#include <curses.h>

WINDOW *subwin(WINDOW *parent, int num_of_lines, int num_of_cols, int start_y, int start_x);
int delwin(WINDOW *window_to_delete);

subwin 函数有着和 newwin  几乎完全一样的参数,删除子窗口和删除其它窗口一样,也是用 delwin 函数。

和对待新的窗口一样,我们可以使用 delwin 来删除子窗口,而且还可以使用带有 mvw 前缀的相关函数去写子窗口。确实,在大部分时候,子窗口的行为特征和新窗口非常相像,但是有一个非常重要的区别:
子窗口没有独立的屏幕字符存储区,不保存自己屏幕字符的集合;它和父窗口共享着同一块字符存储区,这个区域及其大小是创建子窗口时由父窗口指定的。这就意味着,在子窗口中的任何改变都将映射到它的父窗口中去;当子窗口被删除时,屏幕不会改变什么。

这乍一看(at first sight),子窗口看起来似乎没什么用。那为什么不直接就修改父窗口里的内容呢?
子窗口的主要用途是卷动其它窗口中里的部分内容。在编写 curses 程序时,需要卷动屏幕中一小块子区域那太稀松平常不过了。通过创建一个子窗口然后对子窗口进行卷屏,你可以得到你想要的结果。

使用子窗口的一个限制是:应用程序必须在刷新屏幕之前必须在父窗口里调用 touchwin 函数。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-4 00:36 , Processed in 0.095868 second(s), 19 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表