ncurses

ncurses

ncurses,計算機語言,指的是提供字元終端處理庫。

簡短說明

captoinfo 將termcap描述轉化成terminfo描述。

clear 如果可能,就進行清屏操作。

infocmp 比較或顯示 terminfo 描述。

infotocap 將 terminfo 描述轉化成 termcat 描述。

reset 重新初始化終端。

tack terminfo 動作檢測器。主要用來測試 terminfo 資料庫中某一條目的正確性。

ticTic是terminfo項說明的編譯器。這個程式通過ncurses庫將原始碼格式的terminfo檔案轉換成編譯後格式(二進制)的檔案。 Terminfo檔案包含終端能力的信息。

toe 列出所有可用的終端類型,每種分別列出名稱和描述。

tput序利用terminfo資料庫使與終端相關的能力和信息值對shell可用,初始化和重新設定終端,或返回所要求終端為類型的長名。

tset 可以用來初始化終端。

libncurses*這些庫是基於系統用來在顯示器上顯示文本. 一個例子就是,ncurses用在核心的"make menuconfig"進程中。

libform* 在ncurses中使用表格。

libmenu* 在ncurses中使用選單。.

libpanel*在ncurses中使用面板。

依賴關係

Ncurses 依賴於: Bash, Binutils, Coreutils, Diffutils, Gawk, GCC, Glibc, Grep, Make, Sed.

用途

Ncurses 提供字元終端處理庫,包括面板和選單。

安裝下列程式: captoinfo (link to tic), clear, infocmp, infotocap (link to tic), reset (link to tset), tack, tic, toe, tput 和 tset

安裝下列庫檔案: libcurses.[a,so] (link to libncurses.[a,so]), libform.[a,so], libmenu.[a,so], libncurses++.a, libncurses.[a,so] 和 libpanel.[a,so]

套用及步驟

為了能夠使用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後,您將不能在使用 任何ncurses的函式,除非您再一次調用initscr函式.

注意事項

WINDOW結構不會經常保持同一高度寬度以及在窗體中的位置,但是會保持在窗體中的內容.當您向窗體寫入數據時, 會改變窗體中的內容,但並不意味著在螢幕中會立即顯示出來,要更新螢幕內容,必須調用refresh或者wrefres函式.

這裡介紹了stdscr和curscr兩者之間的區別.curscr保存著當前螢幕的內容,在調用ncurse的輸出函式後,stdscr和curscr可能會有不同的內容, 如果您想在最近一次螢幕內容改變後讓stdscr和curscr保持一致,您必須使用refresh函式.換句話說, refresh是唯一一個處理curscr的函式.千萬不要弄混淆了curscr和stdscr,應該在refresh函式中更新curscr中的內容

refresh有一個能儘可能快的更新螢幕的機制,當調用refresh時,它只更新窗體中內容改變的行,這節省了CPU的處理時間 ,防止程式往螢幕上寫相同的信息(譯者注:在螢幕的同一位置不用重新顯示同樣的內容.)這種機制就是為什麼同時使用ncurses的函式 和標準的輸入輸出函式會造成螢幕內容錯位的原因.當調用ncurses的輸出函式時,它會設定一個標誌,能讓refresh 知道是哪一行改變了.但是您調用標準輸入輸出函式時,就不會產生這種結果.

refresh和wrefresh其實做了同樣的事情.wrefresh需要一個WINDOW的指針參數,它僅僅刷新該窗體的內容. refresh()等同於wrefresh(stdscr).我在後面會提到,和wrefresh一樣,ncursers的許多函式都有許多這種為stdscr定義的宏函式.

創建新窗體

下面我們來談談能定義新窗體的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來獲得詳細信息.

同樣存在一些宏函式簡化了上述的移動和寫入等函式.您可以在addch,addstr,printw,getch,getstr,scanw等函式 的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碼值.

#include <curses.h> #include <stdlib.h>

#define ENTER 10

#define ESCAPE 27

當程式的時候,下面的函式會被調用.它首先調用initscr初始化指針接著調用start_color來顯示彩色. 整個程式中所使用的顏色對會在後面定義.調用curs_set(0)會禁止掉物理指針.noecho()將終止鍵盤上的輸入會在螢幕上顯示出來. 您可以使用noecho函式控制鍵盤輸入進來的字元,只允許需要的字元顯示.echo()將會禁止掉這種效果. 接著的函式keypad設定了可以在stdscr中接受鍵盤的功能鍵(快捷鍵),我們需要在後面的程式中定義F1,F2以及移動的游標鍵.

下面定義的這個函式定義了一個顯示在螢幕最頂部的選單欄, 您可以看下面的main段程式,它看上去好象只是螢幕最頂部的一行,其實實際上是stdscr窗體的一個子窗體,該子窗體只有 一行.下面的程式將指向該子窗體的指針作為它的參數,首先改變它的背景色,接著定義選單的?字,我們使用waddstr定義選單 的?字.需要注意的是wattron調用了另外一個不同的顏色對(序號3)以取代預設的顏色對(序號2).記住2號顏色對在最開始就 由wbkgd設定成預設的顏色對了.wattroff函式可以讓我們切換到預設的顏色對狀態.

void draw_menubar(WINDOW *menubar){ wbkgd(menubar,COLOR_PAIR(2));

waddstr(menubar,"Menu1");

wattron(menubar,COLOR_PAIR(3));

waddstr(menubar,"(F1)");

wattroff(menubar,COLOR_PAIR(3));

wmove(menubar,0,20);

waddstr(menubar,"Menu2");

wattron(menubar,COLOR_PAIR(3));

waddstr(menubar,"(F2)");

wattroff(menubar,COLOR_PAIR(3));

}

下一個函式顯示了當按下F1或者F2鍵顯示的選單,定義了一個在藍色背景上 選單欄顏色一樣的白色背景窗體,我們不希望這個新視窗會被顯示在背景色上的字覆蓋掉.它們應該停留在那裡直到 關閉了選單.這就是為什麼選單窗體不能定義為stdscr的子窗體,下面會提到,窗體items[0]是用newwin函式定義的, 其他8個窗體則都是定義成items[0]窗體的子窗體.這裡的items[0]被用來繪製一個圍繞在選單旁邊的框線,其他的 窗體則用來顯示選單中選中的單元.同樣的,他們不會覆蓋掉選單上的框線.為了區別選中和沒選中的狀態,有必要讓 選中的單元背景色和其他的不一樣.這就是這個函式中倒數第三句的作用了,選單中的第一個單元背景色和其他的不一樣, 這是因為選單彈出來後,第一個單元是選中狀態.

WINDOW **draw_menu(int start_col){ int i;

WINDOW **items;

items=(WINDOW **)malloc(9*sizeof(WINDOW *));

items[0]=newwin(10,19,1,start_col);

wbkgd(items[0],COLOR_PAIR(2));

box(items[0],ACS_VLINE,ACS_HLINE);

items[1]=subwin(items[0],1,17,2,start_col+1);

items[2]=subwin(items[0],1,17,3,start_col+1);

items[3]=subwin(items[0],1,17,4,start_col+1);

items[4]=subwin(items[0],1,17,5,start_col+1);

items[5]=subwin(items[0],1,17,6,start_col+1);

items[6]=subwin(items[0],1,17,7,start_col+1);

items[7]=subwin(items[0],1,17,8,start_col+1);

items[8]=subwin(items[0],1,17,9,start_col+1);

for (i=1;i<9;i++)

wprintw(items[i],"Item%d",i);

wbkgd(items[1],COLOR_PAIR(1));

wrefresh(items[0]);

return items;

}

下面這個函式簡單的刪除了上面函式定義的選單窗體.它首先用delwin函式刪除窗體, 接著釋放items指針的記憶體單元.

void delete_menu(WINDOW **items,int count){ int i;

for (i=0;i<count;i++)

delwin(items[i]);

free(items);

}

scroll_menu函式允許我們在選單選擇項上上下移動,它通過getch讀取鍵盤上的鍵值,如果按下了鍵盤上的上移或者下移方向鍵, 選單選擇項的上一個項或者下一個項被選中.回憶一下剛才所講的,選中項的背景色將會和沒選中的不一樣.如果是向左或者向右 的方向鍵,當前選單將會關閉,另一個選單打開.如果按下了回車鍵,則返回選中的單元值.如果按下了ESC鍵,選單將會被關閉,並且沒有任何選擇項 ,下面的函式忽略了其他的輸入鍵.getch能從鍵盤上讀取鍵值,這是因為我們在程式開始使用了keypad(stdscr,TRUE) 並且將返回值賦給一個int型變數而不是char型變數,這是因為int型變數能表示比char型更大的值.

int scroll_menu(WINDOW **items,int count,int menu_start_col){ int key;

int selected=0;

while (1) {

key=getch();

if (key==KEY_DOWN || key==KEY_UP)

{

wbkgd(items[selected+1],COLOR_PAIR(2));

wnoutrefresh(items[selected+1]);

if (key==KEY_DOWN)

{

selected=(selected+1) % count;

} else {

selected=(selected+count-1) % count;

}

wbkgd(items[selected+1],COLOR_PAIR(1));

wnoutrefresh(items[selected+1]);

doupdate();

}

else if (key==KEY_LEFT || key==KEY_RIGHT)

{

delete_menu(items,count+1);

touchwin(stdscr); refresh();

items=draw_menu(20-menu_start_col);

return scroll_menu(items,8,20-menu_start_col);

}

else if (key==ESCAPE)

{

return -1;

}

else if (key==ENTER)

{

return selected;

}

}

}

最後就是我們的main部分了.它使用了上面所有我們所講述和編寫的函式來使程式合適的工作. 它同樣通過getch讀取鍵值來判斷F1或者F2是否按下了,並且用draw_menu來在相應的選單窗體上繪製選單. 接著調用scroll_menu函式讓用戶選擇某一個選單,當scroll_menu返回後,它刪除選單窗體並且顯示所選擇的單元內容 在信息欄里.

我必須提到的是函式touchwin.如果在選單關閉後沒有調用touchwin而立即刷新,那么最後打開的選單將一直停留在 螢幕上.這是因為在調用refresh時,menu函式根本就沒有完全改變stdscr的內容.它沒有重新寫入數據到stdscr上, 因為它以為窗體內容沒有改變.touchwin函式設定了所有WINDOW結構中的標誌位,它通知refresh刷新窗體中所有的行, 值都改變了,這樣在下一次刷新整個窗體時,即使窗體內容沒有改變也要重新寫入一次.在選單關閉後,選擇的選單信息會一直停留在 stdscr上面.選單沒有在stdscr上寫數據,因為它是開了一個新的子視窗.

int main() {

int key;

WINDOW *menubar,*messagebar;

init_curses();

bkgd(COLOR_PAIR(1));

menubar=subwin(stdscr,1,80,0,0);

messagebar=subwin(stdscr,1,79,23,1);

draw_menubar(menubar); move(2,1);

printw("Press F1 or F2 to open the menus. ");

printw("ESC quits.");

refresh();

do

{

int selected_item;

WINDOW **menu_items;

key=getch();

werase(messagebar);

wrefresh(messagebar);

if (key==KEY_F(1))

{

menu_items=draw_menu(0);

selected_item=scroll_menu(menu_items,8,0);

delete_menu(menu_items,9);

if (selected_item<0)

wprintw(messagebar,"You haven't selected any item.");

else

wprintw(messagebar, "You have selected menu item %d.",selected_item+1);

touchwin(stdscr); refresh();

}

else if (key==KEY_F(2))

{

menu_items=draw_menu(20);

selected_item=scroll_menu(menu_items,8,20);

delete_menu(menu_items,9);

if (selected_item<0)

wprintw(messagebar,"You haven't selected any item.");

else

wprintw(messagebar, "You have selected menu item %d.",selected_item+1);

touchwin(stdscr); refresh();

}

} while (key!=ESCAPE);

delwin(menubar);

delwin(messagebar);

endwin();

return 0;

}

如果您拷貝了代碼到一個檔案,假設名字是example.c,並且移走了我所有的注釋,您可以用下面這個方法編譯:

gcc -Wall example.c -o example -lcurses

相關詞條

相關搜尋

熱門詞條

聯絡我們