9. 窗口

窗口是 curses 中最重要的概念。您已经在上面看到了标准窗口 stdscr,所有函数都隐式地在该窗口上操作。现在,为了设计即使是最简单的 GUI,您也需要求助于窗口。您可能想要使用窗口的主要原因是为了分别操作屏幕的各个部分,从而提高效率,仅更新需要更改的窗口,并为了更好的设计。我会说最后一个原因是在使用窗口时最重要的。您应该始终在程序中力求更好且易于管理的设计。如果您正在编写大型、复杂的 GUI,那么在您开始做任何事情之前,这一点至关重要。

9.1. 基础知识

可以通过调用以下函数来创建窗口newwin()。它实际上并没有在屏幕上创建任何东西。它为结构分配内存以操作窗口,并使用有关窗口的数据(如窗口的大小、beginy、beginx 等)更新结构。因此,在 curses 中,窗口只是一个假想窗口的抽象,可以独立于屏幕的其他部分进行操作。函数 newwin() 返回指向 WINDOW 结构的指针,该指针可以传递给窗口相关函数,如 wprintw() 等。最后,可以使用 delwin() 销毁窗口。它将释放与窗口结构关联的内存。

9.2. 创建一个窗口!!!

如果创建了一个窗口而我们看不到它,那有什么乐趣呢?因此,有趣的部分从显示窗口开始。函数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);
}

9.3. 解释

不要尖叫。我知道这是一个很大的例子。但我必须在这里解释一些重要的事情:-) 。这个程序创建了一个矩形窗口,可以使用左、右、上、下箭头键移动。它在用户按下按键时重复创建和销毁窗口。不要超出屏幕限制。检查这些限制留给读者作为练习。让我们逐行剖析它。

以下create_newwin()函数创建一个窗口,并使用 box 在其周围显示边框。函数newwin()和使用 box 在其周围显示边框。函数destroy_win()首先通过用 ' ' 字符绘制边框来从屏幕上擦除窗口,然后调用delwin()以释放与其相关的内存。根据用户按下的键,starty 或 startx 会被更改,并创建一个新窗口。

在 destroy_win 中,正如您所见,我使用了 wborder 而不是 box。原因写在注释中(您错过了。我知道。阅读代码:-))。wborder 使用给定的 4 个角点和 4 条线在窗口周围绘制边框。为了清楚地说明,如果您像下面这样调用了 wborder

    wborder(win, '|', '|', '-', '-', '+', '+', '+', '+');

它会产生类似这样的东西

    +------------+
    |            |
    |            |
    |            |
    |            |
    |            |
    |            |
    +------------+

9.4. 示例中的其他内容

您还可以在上面的示例中看到,我使用了变量 COLS、LINES,它们在 initscr() 之后被初始化为屏幕尺寸。它们可用于查找屏幕尺寸并找到屏幕的中心坐标,如上所示。函数getch()像往常一样从键盘获取按键,并根据按键执行相应的工作。这种类型的 switch-case 在任何基于 GUI 的程序中都非常常见。

9.5. 其他边框函数

上面的程序非常低效,因为每次按下按键,都会销毁一个窗口并创建另一个窗口。因此,让我们编写一个更高效的程序,它使用其他边框相关函数。

以下程序使用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();

}