ncurses視窗機制:newwin(),wprintw(),delwin(),box(),wborder(),

Koma_Wong發表於2018-06-21
  • Copyright(C) NCURSES Programming HOWTO

視窗機制

視窗(Window)機制是整個curses 系統的核心。通過前面的例子我們看到了基於“標準視窗”(stdscr)的一些操作函式。即使設計一個最簡單的圖形使用者介面( GUI),都需要用到視窗。你可能需要將螢幕分成幾個部分並分別處理,然而,將螢幕拆分成各個視窗,然後獨立處理每個視窗是比較高效的方法。使用視窗的另外一個重要原因是:你應當始終在你的程式中追求一種更好的、更易於管理的設計方式。如果你要設計一個大型的、複雜的使用者介面,事先設計好各個部分將會提高你的程式設計效率。

基本概念

一個視窗是通過呼叫newwin()函式建立的。但當你呼叫這個函式後螢幕上並不會有任何變化。因為這個函式的實際作用是給用來操作視窗的結構體分配記憶體,這個結構體包含了視窗的大小、起始座標等資訊。可見,在curses 裡,視窗是一個假想的概念,用來獨立處理螢幕上的各個部分。newwin()函式返回一個指向視窗結構的指標,像wprintw()等函式都需要以視窗指標作為引數。delwin()函式可以刪除一個視窗,並釋放用來儲存視窗結構的記憶體和資訊。

顯示視窗

可惜的是,當我們建立了一個視窗之後卻無法看見它,所以我們現在要做的就是讓視窗顯示出來。box()函式可以在已定義的視窗外圍畫上邊框。現在讓我們看看下面程式中的函式:

例:帶邊框的視窗:

/*
Compile: gcc main.c -lncurses
*/
#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(); /* 初始化並進入curses 模式*/
    cbreak(); /* 行緩衝禁止,傳遞所有控制資訊*/
    keypad(stdscr, TRUE); /* 程式需要使用F1 功能鍵*/
    height = 3;
    width = 10;
    starty = (LINES-height)/ 2; /*計算視窗中心位置的行數*/
    startx = (COLS-width)/ 2; /*計算視窗中心位置的列數*/
    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(); /*結束curses 模式*/
    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 是字元預設的行列起始位置*/
    wrefresh(local_win);        /*重新整理視窗緩衝,顯示box */
    return local_win;
}

void destroy_win(WINDOW *local_win)
{
    /* box(local_win, ' ', ' ');不會按照預期的那樣清除視窗邊框。
    而是在視窗的四個角落留下殘餘字元*/
    wborder(local_win, '1', '2', '3','4','5','6','7','8');
    /*引數註解9.3:
    * 1. win:當前操作的視窗
    * 2. ls:用於顯示視窗左邊界的字元
    * 3. rs:用於顯示視窗右邊界的字元
    * 4. ts:用於顯示視窗上邊界的字元
    * 5. bs:用於顯示視窗下邊界的字元
    * 6. tl:用於顯示視窗左上角的字元
    * 7. tr:用於顯示視窗右上角的字元
    * 8. bl:用於顯示視窗左下角的字元
    * 9. br:用於顯示視窗右下角的字元
    */
    wrefresh(local_win);
    delwin(local_win);
}
/**
結果:
                 ┌────────┐66666666666666666666666666666
                 │        │22222222222222222222222222222
                 └────────┘88888888888888888888888888888
                                              7444444448
                 53333333366666666666666      7444444448
                 53333333362222222222222      7444444448
                 53333333368888888888888      7444444448
                 5333333336   7444444448      7444444448
                 5333333336   7444444448      7444444448
                 5333333336   7444444448      7444444448
                 5333333336   7444444448      7444444448
                 5333333336   7444444448      7444444448
                 5333333336   7444444448      7444444448
                 5333333336   7444444448      7444444448
                 5333333336                   7444444448
                 5333333336                   7444444448
                 5333333336                   7444444448
                 555555555555555555555555555557444444448
                 111111111111111111111111111117444444448
                 777777777777777777777777777777444444448
*/

程式解析

別害怕,這的確是一個很大的程式,但它確實講解了一些很重要的東西:它建立了一個視窗,並且可以使用方向鍵來移動它。當使用者按下方向健的時候,它會刪除現有的視窗並在下一個位置建立新視窗,這樣就實現了視窗移動的效果。注意:移動視窗時不能超過視窗行列的限制。下面就讓我們逐行的分析這個程式:

creat_newwin()函式使用newwin()函式建立了一個視窗,並且使用box()函式給視窗新增了邊框。destory_win()函式首先使用空白字元填充視窗,從而起到清除螢幕的作用。之後呼叫delwin()函式回收分配給視窗的記憶體。隨著使用者按下方向鍵, startx 和starty 的值就會不斷改變並以新座標為起點建立一個新視窗。 在destory_win()中,我們使用了wborder 來替代box。這樣做的原因已經寫到程式註釋裡了(我知道你剛才忽略了,現在趕緊去看看!)。wborder()函式可以用字元來繪製視窗的邊框。這些邊框是由四條線和四個角組成的。為了理解得更明白一些,你可以試著這樣呼叫wborder()函式:

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

他所繪製的視窗會是以下這樣子:

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

更多的說明

從上面的例子中還可以看到,函式使用了COLS 和LINES 作為變數名。在initscr()函式初始化螢幕以後,這兩個變數分別儲存螢幕初始化後的行數和列數。這樣做是為了方便標記視窗尺寸和計算出螢幕的中心位置座標。getch()函式依然用來處理使用者的鍵盤輸入。同時,根據使用者的輸入做出程式中定義的操作。這種做法在互動式的圖形介面應用程式中非常普遍。

其它的邊框函式

上面這個例子所使用的方式是通過按下鍵盤上相應的按鈕撤消一個視窗並建立一個新的視窗,但是這樣的工作方式效率太低。現在讓我們來寫一些可以使視窗邊框的使用更加有效率的程式。 下面這個程式使用mvhline()mvvline()函式完成同樣的效果。這兩個函式非常簡單,它們將在指定的位置繪製出指定大小的視窗。

/*
Compile: gcc main.c -lncurses
*/
#include <ncurses.h>
//#define _DEBUG

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, int boolean);

int main(int argc, char *argv[])
{ 
    WIN win;
    int ch;
    initscr();              /* 初始化並進入curses 模式*/
    start_color();          /* 開啟彩色顯示功能*/
    cbreak();               /* 行緩衝禁止,傳遞所有控制資訊*/
    keypad(stdscr, TRUE);   /* 程式需要使用F1 功能鍵*/
    noecho();
    init_pair(1, COLOR_CYAN, COLOR_BLACK);
    /* 以下程式碼初始化視窗的引數*/
    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(); /* 結束curses 模式*/
    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, int boolean)
{ 
    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(boolean == 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();
}
/**
結果:
    +---------+
    |         |
    |         |
    +---------+
*/

相關文章