1. 程式人生 > >C++控制檯操作(基本操作的程式碼)

C++控制檯操作(基本操作的程式碼)

控制檯視窗介面程式設計控制

〇、摘要

一、概述

二、控制檯文字視窗的一般控制步驟

三、控制檯視窗操作

四、文字屬性操作

五、文字輸出

六、文字操作示例

七、滾動和移動

八、游標操作

九、讀取鍵盤資訊

十、讀取滑鼠資訊

十一、結語

補充篇--經典程式(Internet資源)

摘要:

文字介面的控制檯應用程式開發是深入學習C++、掌握互動系統的實現方法的最簡單的一種手段。然而,Visual C++的C++專用庫卻沒有TC所支援的文字(字元)螢幕控制函式,為此本系列文章從一般控制步驟、控制檯視窗操作、文字(字元)控制、滾動和移動游標、鍵盤和滑鼠等幾個方面討論控制檯視窗介面的程式設計控制方法。

在眾多C++開發工具中,由於Microsoft本身的獨特優勢,選用 Visual C++已越來越被眾多學習者所接受。顯然,現今如果還再把TC作為開發環境的話,不僅沒有必要,而且也不利於向Windows應用程式開發的過渡。然而,Visual C++的C++專用庫卻沒有TC所支援的文字螢幕(控制檯視窗)控制函式(相應的標頭檔案是conio.h)。這必然給C++學習者在文字介面設計和程式設計上帶來諸多不便。要知道,文字介面設計是一種深入學習C++、掌握互動系統的實現方法的最簡單的一種手段,它不像C++的Windows圖形介面應用 程式,涉及知識過多。為此,本系列文章來討論在Visual C++ 6.0開發環境中,如何編寫具有美觀清晰的控制檯視窗介面的C++應用程式。

 

(一) 概述操作

所謂控制檯應用程式,就是指那些需要與傳統DOS作業系統保持某種程式的相容,同時又不需要為使用者提供完善介面的程式。簡單地講,就是指在Windows環境下執行的DOS程式。一旦控制檯應用程式在Windows作業系統中執行後,就會彈出一個視窗。例如下列程式碼:

#include <stdio.h>

int main(int argc,char *argv[])

{

       printf("Hello, Console!\n");

       return 0;

}

單擊小型編譯工具欄中的“Build”按鈕或按F7鍵,系統出現一個對話方塊,詢問是否將此專案的工作資料夾設定原始檔所在的資料夾,單擊[是]按鈕,系統開始編譯。 單擊小型編譯工具欄中的“Execute Program”按鈕或按Ctrl+F5鍵,執行剛才的程式。 程式執行後,彈出下圖的視窗:

  

這就是控制檯視窗,與傳統的DOS螢幕視窗相比最主要的區別有:

(1) 預設的控制檯視窗有系統選單和標題,它是一個記憶體緩衝區視窗,緩衝區大小取決於Windows作業系統的分配;而DOS螢幕是一種物理視窗,不具有Windows視窗特性,其大小取決於ROM BIOS分配的記憶體空間。

(2) 控制檯視窗的文字操作是呼叫低層的Win32 APIs,而DOS螢幕的文字操作是通過呼叫BIOS的16(10h)中斷而實現的。

(3) 預設的控制檯視窗可以接收鍵盤和滑鼠的輸入資訊,裝置驅動由Windows管理,而DOS螢幕視窗接收滑鼠時需要呼叫33h中斷,且滑鼠裝置驅動程式由自己安裝。

 

(二)    控制檯文字視窗的一般控制步驟

在Visual C++ 6.0中,控制檯視窗介面的一般程式設計控制步驟如下:呼叫GetStdHandle獲取當前的標準輸入(STDIN)和標準輸出(STDOUT)裝置控制代碼。函式原型為:

HANDLE GetStdHandle( DWORD nStdHandle );

其中,nStdHandle可以是STD_INPUT_HANDLE(標準輸入裝置控制代碼)、STD_OUTPUT_HANDLE(標準輸出裝置控制代碼)和 STD_ERROR_HANDLE(標準錯誤控制代碼)。

需要說明的是,“控制代碼”是Windows最常用的概念。它通常用來標識Windows資源(如選單、 圖示、視窗等)和裝置等物件。雖然可以把控制代碼理解為是一個指標變數型別,但它不是物件所在的地址指標,而是作為Windows系統內部表的索引值來使用 的。呼叫相關文字介面控制的API函式。這些函式可分為三類。一是用於控制檯視窗操作的函式(包括視窗的緩衝區大小、視窗前景字元和背景顏色、視窗標題、大小和位置等);二是用於控制檯輸入輸出的函式(包括字元屬性操作函式);其他的函式併為最後一類。 呼叫CloseHandle()來關閉輸入輸出控制代碼。 注意,在程式中還必須包含標頭檔案windows.h。下面看一個程式:

#include <windows.h>

#include <stdio.h>

#include <conio.h>

int main(void)

{

       HANDLE hOut;

       CONSOLE_SCREEN_BUFFER_INFO bInfo; // 儲存視窗資訊

       COORD pos = {0, 0};

       // 獲取標準輸出裝置控制代碼

       hOut = GetStdHandle(STD_OUTPUT_HANDLE);

       // 獲取視窗資訊

       GetConsoleScreenBufferInfo(hOut, &bInfo );

       printf("\n\nThe soul selects her own society\n");

       printf("Then shuts the door\n");

       printf("On her devine majority\n");

       printf("Obtrude no more\n\n");

       _getch();

       // 向視窗中填充字元以獲得清屏的效果

       FillConsoleOutputCharacter(hOut,' ', bInfo.dwSize.X * bInfo.dwSize.Y, pos, NULL);

       // 關閉標準輸出裝置控制代碼

       CloseHandle(hOut);

       return 0;

}

 

程式中,COORD和CONSOLE_SCREEN_BUFFER_ INFO是wincon.h定義的控制檯結構體型別,其原型如下:

// 座標結構體

typedef struct _COORD {

SHORT X;

SHORT Y;

} COORD;

// 控制檯視窗資訊結構體

typedef struct _CONSOLE_SCREEN_BUFFER_INFO {

COORD dwSize; // 緩衝區大小

COORD dwCursorPosition; // 當前游標位置

WORD wAttributes; // 字元屬性

SMALL_RECT srWindow; // 當前視窗顯示的大小和位置

COORD dwMaximumWindowSize; // 最大的視窗緩衝區大小

} CONSOLE_SCREEN_BUFFER_INFO ;

還需要說明的是,雖然在C++中,iostream.h定義了cin和cout的標準輸入和輸出流物件。但它們只能實現基本的輸入輸出 操作,對於控制檯視窗介面的控制卻無能為力,而且不能與stdio.h和conio.h友好相處,因為iostream.h和它們是C++兩套不同的輸入 輸出操作方式,使用時要特別注意。

 

(三)   控制檯視窗操作操作

用於控制檯視窗操作的API函式如下:

GetConsoleScreenBufferInfo 獲取控制檯視窗資訊

GetConsoleTitle 獲取控制檯視窗標題

ScrollConsoleScreenBuffer 在緩衝區中移動資料塊

SetConsoleScreenBufferSize 更改指定緩衝區大小

SetConsoleTitle 設定控制檯視窗標題

SetConsoleWindowInfo 設定控制檯視窗資訊

此外,還有視窗字型、顯示模式等控制函式,這裡不再細說。下列舉一個示例,程式如下:

#include <windows.h>

#include <stdio.h>

#include <conio.h>

int main(void)

{

       char strTitle[255];

       CONSOLE_SCREEN_BUFFER_INFO bInfo; // 視窗緩衝區資訊

       COORD size = {80, 25};

       HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 獲取標準輸出裝置控制代碼

       GetConsoleScreenBufferInfo(hOut, &bInfo ); // 獲取視窗緩衝區資訊

       GetConsoleTitle(strTitle, 255); // 獲取視窗標題

       printf("當前視窗標題是:\n%s\n", strTitle);

       _getch();

       SetConsoleTitle("控制檯視窗操作"); // 設定視窗標題

       GetConsoleTitle(strTitle, 255);

       printf("當前視窗標題是:\n%s\n", strTitle);

       _getch();

       SetConsoleScreenBufferSize(hOut,size); // 重新設定緩衝區大小

       _getch();

       SMALL_RECT rc = {0,0, 80-1, 25-1}; // 重置視窗位置和大小

       SetConsoleWindowInfo(hOut,true ,&rc);

       CloseHandle(hOut); // 關閉標準輸出裝置控制代碼

       return 0;

}

需要說明的是,控制檯視窗的原點座標是(0, 0),而最大的座標是緩衝區大小減1,例如當緩衝區大小為80*25時,其最大的座標是(79, 24)。

(四) 文字屬性操作操作

與DOS字元相似,控制檯視窗中的字元也有相應的屬性。這些屬性分為:文字的前景色、背景色和雙位元組字符集(DBCS)屬性三種。事實上,我們最關心是文字顏色,這樣可以構造出美觀的介面。顏色屬性都是一些預定義標識:

FOREGROUND_BLUE 藍色

FOREGROUND_GREEN 綠色

FOREGROUND_RED 紅色

FOREGROUND_INTENSITY 加強

BACKGROUND_BLUE 藍色背景

BACKGROUND_GREEN 綠色背景

BACKGROUND_RED 紅色背景

BACKGROUND_INTENSITY 背景色加強

COMMON_LVB_REVERSE_VIDEO 反色

與文字屬性相關的主要函式有:

BOOL FillConsoleOutputAttribute( // 填充字元屬性

HANDLE hConsoleOutput, // 控制代碼

WORD wAttribute, // 文字屬性

DWORD nLength, // 個數

COORD dwWriteCoord, // 開始位置

LPDWORD lpNumberOfAttrsWritten // 返回填充的個數

);

BOOL SetConsoleTextAttribute( // 設定WriteConsole等函式的字元屬性

HANDLE hConsoleOutput, // 控制代碼

WORD wAttributes // 文字屬性

);

BOOL WriteConsoleOutputAttribute( // 在指定位置處寫屬性

HANDLE hConsoleOutput, // 控制代碼

CONST WORD *lpAttribute, // 屬性

DWORD nLength, // 個數

COORD dwWriteCoord, // 起始位置

LPDWORD lpNumberOfAttrsWritten // 已寫個數

);

另外,獲取當前控制檯視窗的文字屬性是通過呼叫函式GetConsoleScreenBufferInfo後,在CONSOLE_SCREEN_ BUFFER_INFO結構成員wAttributes中得到。

 

(五) 文字輸出

操作文字輸出函式有:

BOOL FillConsoleOutputCharacter( // 填充指定資料的字元

HANDLE hConsoleOutput, // 控制代碼

TCHAR cCharacter, // 字元

DWORD nLength, // 字元個數

COORD dwWriteCoord, // 起始位置

LPDWORD lpNumberOfCharsWritten);// 已寫個數

BOOL WriteConsole( // 在當前游標位置處插入指定數量的字元

HANDLE hConsoleOutput, // 控制代碼

CONST VOID *lpBuffer, // 字串

DWORD nNumberOfCharsToWrite, // 字元個數

LPDWORD lpNumberOfCharsWritten, // 已寫個數

LPVOID lpReserved);// 保留

 

BOOL WriteConsoleOutput( // 向指定區域寫帶屬性的字元

HANDLE hConsoleOutput, // 控制代碼

CONST CHAR_INFO *lpBuffer, // 字元資料區

COORD dwBufferSize, // 資料區大小

COORD dwBufferCoord, // 起始座標

PSMALL_RECT lpWriteRegion );// 要寫的區域

 

BOOL WriteConsoleOutputCharacter( // 在指定位置處插入指定數量的字元

HANDLE hConsoleOutput, // 控制代碼

LPCTSTR lpCharacter, // 字串

DWORD nLength, // 字元個數

COORD dwWriteCoord, // 起始位置

LPDWORD lpNumberOfCharsWritten); // 已寫個數

 

可以看出:WriteConsoleOutput函式功能相當於SetConsoleTextAttribute和WriteConsole 的功能。而WriteConsoleOutputCharacter函式相當於SetConsoleCursorPosition(設定游標位置)和 WriteConsole的功能。不過在具體使用要注意它們的區別。

 

(六) 文字操作示例操作

下面看一個示例程式:

// 在具有陰影效果的視窗中顯示一行字元

#include <windows.h>

HANDLE hOut;

void ShadowWindowLine(char *str);

void DrawBox(bool bSingle, SMALL_RECT rc); // 繪製邊框

int main(void)

{

       hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 獲取標準輸出裝置控制代碼

       SetConsoleOutputCP(437); // 設定內碼表,這裡如果設定成936(簡體中文),那麼程式會怎樣?那樣的話,將畫不出邊框。

       ShadowWindowLine("Display a line of words, and center the window with shadow.");

       CloseHandle(hOut); // 關閉標準輸出裝置控制代碼

       return 0;

}

void ShadowWindowLine(char *str)

{

       SMALL_RECT rc;

       CONSOLE_SCREEN_BUFFER_INFO bInfo; // 視窗緩衝區資訊

       WORD att0,att1,attText;

       int i, chNum = strlen(str);

       GetConsoleScreenBufferInfo( hOut, &bInfo ); // 獲取視窗緩衝區資訊

       // 計算顯示視窗大小和位置

       rc.Left = (bInfo.dwSize.X - chNum)/2 - 2;

       rc.Top = 8; // 原始碼段中此處為bInfo.dwSize.Y/2 - 2,但是如果您的DOS螢幕有垂直滾動條的話,還需要把滾動條下拉才能看到,為了方便就把它改為10

       rc.Right = rc.Left + chNum + 4;

       rc.Bottom = rc.Top + 4;

       att0 = BACKGROUND_INTENSITY; // 陰影屬性

       att1 = FOREGROUND_RED |FOREGROUND_GREEN |FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE;// 文字屬性

       attText = FOREGROUND_RED |FOREGROUND_INTENSITY; // 文字屬性

       // 設定陰影然後填充

       COORD posShadow = {rc.Left+1, rc.Top+1}, posText = {rc.Left, rc.Top};

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

       {

              FillConsoleOutputAttribute(hOut, att0, chNum + 4, posShadow, NULL);

              posShadow.Y++;

       }

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

       {

              FillConsoleOutputAttribute(hOut, att1,chNum + 4, posText, NULL);

              posText.Y++;

       }

       // 寫文字和邊框

       posText.X = rc.Left + 2;

       posText.Y = rc.Top + 2;

       WriteConsoleOutputCharacter(hOut, str, strlen(str), posText, NULL);

       DrawBox(true, rc);

       SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢復原來的屬性

}

void DrawBox(bool bSingle, SMALL_RECT rc) // 函式功能:畫邊框

{

       char chBox[6];

       COORD pos;

       if (bSingle)

       {

              chBox[0] = (char)0xda; // 左上角點

              chBox[1] = (char)0xbf; // 右上角點

              chBox[2] = (char)0xc0; // 左下角點

              chBox[3] = (char)0xd9; // 右下角點

              chBox[4] = (char)0xc4; // 水平

              chBox[5] = (char)0xb3; // 堅直

       } else {

              chBox[0] = (char)0xc9; // 左上角點

              chBox[1] = (char)0xbb; // 右上角點

              chBox[2] = (char)0xc8; // 左下角點

              chBox[3] = (char)0xbc; // 右下角點

              chBox[4] = (char)0xcd; // 水平

              chBox[5] = (char)0xba; // 堅直

       }

       // 畫邊框的上 下邊界

       for(pos.X = rc.Left+1;pos.X<rc.Right-1;pos.X++)

       {    

              pos.Y = rc.Top;

              // 畫上邊界

              WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);

              // 畫左上角

              if(pos.X == rc.Left+1)

              {

                     pos.X--;

                     WriteConsoleOutputCharacter(hOut, &chBox[0],1, pos, NULL);

                     pos.X++;

              }

              // 畫右上角

              if(pos.X == rc.Right-2)

              {

                     pos.X++;

                     WriteConsoleOutputCharacter(hOut, &chBox[1], 1, pos, NULL);

                     pos.X--;

              }

              pos.Y = rc.Bottom;

              // 畫下邊界

              WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);

              // 畫左下角

              if(pos.X == rc.Left+1)

              {

                     pos.X--;

                     WriteConsoleOutputCharacter(hOut, &chBox[2], 1, pos, NULL);

                     pos.X++;

              }

              // 畫右下角

              if(pos.X==rc.Right-2)

              {

                     pos.X++;

                     WriteConsoleOutputCharacter(hOut, &chBox[3], 1, pos, NULL);

                     pos.X--;

              }

       }

       // 畫邊框的左右邊界

       for (pos.Y = rc.Top+1; pos.Y<=rc.Bottom-1; pos.Y++)

       {

              pos.X = rc.Left;

              // 畫左邊界

              WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);

              pos.X = rc.Right-1;

              // 畫右邊界

              WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);

       }

}

程式執行結果如下圖所示:

  

需要說明的是:

①在上述例子中,如果呼叫DrawBox函式時,傳遞的第一個引數不是true而是false,那麼畫出來的邊框將是雙線的。執行結果如下:

 

②如果在上述程式無法編譯通過,您可以這樣修改,即程式中呼叫WriteConsoleOutputCharacter和FillConsoleOutputAttribute函式的時候,最後一個引數不用NULL,而是先定義一個變數:

DWORD written;

然後把 &written作為最後一個引數。

③上述程式在不同的字元程式碼頁面(code page)下顯示的結果是不同的。例如,中文Windows作業系統的預設內碼表是簡體中文(936),在該程式碼頁面下值超過128的單字元在Windows NT/XP是顯示不出來的。下表列出了可以使用的內碼表。

內碼表(Code page)   說明

1258       越南文

1257       波羅的海文

1256       阿拉伯文

1255       希伯來文

1254       土耳其語

1253       希臘文

1252       拉丁文(ANSI)

1251       斯拉夫文

1250       中歐文

950  繁體中文

949  韓文

936  簡體中文

932  日文

874  泰文

850  使用多種語言(MS-DOS拉丁文)

437  MS-DOS美語/英語

 

(七) 滾動和移動操作

ScrollConsoleScreenBuffer是實現文字區滾動和移動的API函式。它可以將指定的一塊文字區域移動到另一個區域,被移空的那塊區域由指定字元填充。函式的原型如下:

BOOL ScrollConsoleScreenBuffer(

HANDLE hConsoleOutput, // 控制代碼

CONST SMALL_RECT* lpScrollRectangle, // 要滾動或移動的區域

CONST SMALL_RECT* lpClipRectangle, // 裁剪區域

COORD dwDestinationOrigin, // 新的位置

CONST CHAR_INFO* lpFill // 填充字元

);

利用這個API函式還可以實現刪除指定行的操作。下面來舉一個例子,程式如下:

#include <windows.h>

#include <stdio.h>

#include <conio.h>

HANDLE hOut;

void DeleteLine(int row); // 刪除一行

void MoveText(int x, int y, SMALL_RECT rc); // 移動文字塊區域

void ClearScreen(void); // 清屏

int main(void)

{

       hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 獲取標準輸出裝置控制代碼

       WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE;// 背景是藍色,文字顏色是黃色

       SetConsoleTextAttribute(hOut, att);

       ClearScreen();

       printf("\n\nThe soul selects her own society\n");

       printf("Then shuts the door;\n");

       printf("On her devine majority;\n");

       printf("Obtrude no more.\n\n");

       COORD endPos = {0, 15};

       SetConsoleCursorPosition(hOut, endPos); // 設定游標位置

       SMALL_RECT rc = {0, 2, 40, 5};

       _getch();

       MoveText(10, 5, rc);

       _getch();

       DeleteLine(5);

       CloseHandle(hOut); // 關閉標準輸出裝置控制代碼

       return 0;

}

void DeleteLine(int row)

{

       SMALL_RECT rcScroll, rcClip;

       COORD crDest = {0, row - 1};

       CHAR_INFO chFill;

       CONSOLE_SCREEN_BUFFER_INFO bInfo;

       GetConsoleScreenBufferInfo( hOut, &bInfo );

       rcScroll.Left = 0;

       rcScroll.Top = row;

       rcScroll.Right = bInfo.dwSize.X - 1;

       rcScroll.Bottom = bInfo.dwSize.Y - 1;

       rcClip = rcScroll;

       chFill.Attributes = bInfo.wAttributes;

       chFill.Char.AsciiChar = ' ';

       ScrollConsoleScreenBuffer(hOut, &rcScroll, &rcClip, crDest, &chFill);

}

void MoveText(int x, int y, SMALL_RECT rc)

{

       COORD crDest = {x, y};

       CHAR_INFO chFill;

       CONSOLE_SCREEN_BUFFER_INFO bInfo;

       GetConsoleScreenBufferInfo( hOut, &bInfo );

       chFill.Attributes = bInfo.wAttributes;

       chFill.Char.AsciiChar = ' ';

       ScrollConsoleScreenBuffer(hOut, &rc, NULL, crDest, &chFill);

}

void ClearScreen(void)

{

       CONSOLE_SCREEN_BUFFER_INFO bInfo;

       GetConsoleScreenBufferInfo( hOut, &bInfo );

       COORD home = {0, 0};

       WORD att = bInfo.wAttributes;

       unsigned long size = bInfo.dwSize.X * bInfo.dwSize.Y;

       FillConsoleOutputAttribute(hOut, att, size, home, NULL);

       FillConsoleOutputCharacter(hOut, ' ', size, home, NULL);

}

程式中,實現刪除行的操作DeleteLine的基本原理是:首先將裁剪區域和移動區域都設定成指定行row(包括該行)以下的控制檯視窗區域,然後將移動的位置指定為(0, row-1)。這樣,超出裁剪區域的內容被裁剪掉,從而達到刪除行的目的。

需要說明的是,若裁剪區域引數為NULL,則裁剪區域為整個控制檯視窗。

 

(八) 游標操作操作

控制檯視窗中的游標反映了文字插入的當前位置,通過SetConsoleCursorPosition函式可以改變這個“當前”位置,這樣就能控制字元(串)輸出。事實上,游標本身的大小和顯示或隱藏也可以通過相應的API函式進行設定。例如:

BOOL SetConsoleCursorInfo( // 設定游標資訊

HANDLE hConsoleOutput, // 控制代碼

CONST CONSOLE_CURSOR_INFO *lpConsoleCursorInfo // 游標資訊

);

BOOL GetConsoleCursorInfo( // 獲取游標資訊

HANDLE hConsoleOutput, // 控制代碼

PCONSOLE_CURSOR_INFO lpConsoleCursorInfo // 返回游標資訊

);

這兩個函式都與CONSOLE_CURSOR_INFO結構體型別有關,其定義如下:

typedef struct _CONSOLE_CURSOR_INFO {

DWORD dwSize; // 游標百分比大小

BOOL bVisible; // 是否可見

} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

需要說明的是,dwSize值反映了游標的大小,它的值範圍為1-100;當為1時,游標最小,僅是一條最靠下的水平細線,當為100,游標最大,為一個字元大小的方塊。

(九) 讀取鍵盤資訊操作

  鍵盤事件通常有字元事件和按鍵事件,這些事件所附帶的資訊構成了鍵盤資訊。它是通過API函式ReadConsoleInput來獲取的,其原型如下:

BOOL ReadConsoleInput(

HANDLE hConsoleInput, // 輸入裝置控制代碼

PINPUT_RECORD lpBuffer, // 返回資料記錄

DWORD nLength, // 要讀取的記錄數

LPDWORD lpNumberOfEventsRead // 返回已讀取的記錄數

);

其中,INPUT_RECORD定義如下:

typedef struct _INPUT_RECORD {

WORD EventType; // 事件型別

union {

KEY_EVENT_RECORD KeyEvent;

MOUSE_EVENT_RECORD MouseEvent;

WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;

MENU_EVENT_RECORD MenuEvent;

FOCUS_EVENT_RECORD FocusEvent;

} Event;

} INPUT_RECORD;

與鍵盤事件相關的記錄結構KEY_EVENT_RECORD定義如下:

typedef struct _KEY_EVENT_RECORD {

BOOL bKeyDown; // TRUE表示鍵按下,FALSE表示鍵釋放

WORD wRepeatCount; // 按鍵次數

WORD wVirtualKeyCode; // 虛擬鍵程式碼

WORD wVirtualScanCode; // 虛擬鍵掃描碼

union {

WCHAR UnicodeChar; // 寬字元

CHAR AsciiChar; // ASCII字元

} uChar; // 字元

DWORD dwControlKeyState; // 控制鍵狀態

} KEY_EVENT_RECORD;

我們知道,鍵盤上每一個有意義的鍵都對應著一個唯一的掃描碼,雖然掃描碼可以作為鍵的標識,但它依賴於具體裝置的。因此,在應用程式中,使用的往往是與具體裝置無關的虛擬鍵程式碼。這種虛擬鍵程式碼是與裝置無關的鍵盤編碼。在Visual C++中,最常用的虛擬鍵程式碼已被定義在Winuser.h中,例如:VK_SHIFT表示SHIFT鍵,VK_F1表示功能鍵F1等。上述結構定義中,dwControlKeyState用來表示控制鍵狀態,它可以是CAPSLOCK_ON(CAPS LOCK燈亮)、ENHANCED_KEY(按下擴充套件鍵)、LEFT_ALT_PRESSED(按下左ALT鍵)、 LEFT_CTRL_PRESSED(按下左CTRL鍵)、NUMLOCK_ON (NUM LOCK燈亮)、RIGHT_ALT_PRESSED(按下右ALT鍵)、RIGHT_CTRL_PRESSED(按下右CTRL鍵)、 SCROLLLOCK_ON(SCROLL LOCK燈亮)和SHIFT_PRESSED(按下SHIFT鍵)中的一個或多個值的組合。下面的程式是將使用者按鍵的字元輸入到一個控制檯視窗的某個區域中,並當按下NUM LOCK、CAPS LOCK和SCROLL LOCK鍵時,在控制檯視窗的最後一行顯示這些鍵的狀態。

#include <windows.h>

HANDLE hOut;

HANDLE hIn;

void DrawBox(bool bSingle, SMALL_RECT rc); // 這個自定義函式在第六章用過

void ClearScreen(void);

void CharWindow(char ch, SMALL_RECT rc); // 將ch輸入到指定的視窗中

void ControlStatus(DWORD state); // 在最後一行顯示控制鍵的狀態

void DeleteTopLine(SMALL_RECT rc); // 刪除指定視窗中最上面的行並滾動

int main(void)

{

       hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 獲取標準輸出裝置控制代碼

       hIn = GetStdHandle(STD_INPUT_HANDLE); // 獲取標準輸入裝置控制代碼

       WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE ; // 背景是藍色,文字顏色是黃色

       SetConsoleTextAttribute(hOut, att);

       ClearScreen(); // 清屏

       INPUT_RECORD keyRec;

       DWORD state = 0, res;

       char ch;

       SMALL_RECT rc = {20, 2, 40, 12};

       DrawBox(false, rc);

       COORD pos = {rc.Left+1, rc.Top+1};

       SetConsoleCursorPosition(hOut, pos); // 設定游標位置

       for(;;) // 迴圈

       {

              ReadConsoleInput(hIn, &keyRec, 1, &res);

              if (state != keyRec.Event.KeyEvent.dwControlKeyState)

              {

                     state = keyRec.Event.KeyEvent.dwControlKeyState;

                     ControlStatus(state);

              }

              if (keyRec.EventType == KEY_EVENT)

              {

                     if (keyRec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE)

                            break;

                     // 按ESC鍵退出迴圈

                     if (keyRec.Event.KeyEvent.bKeyDown)

                     {

                            ch = keyRec.Event.KeyEvent.uChar.AsciiChar;

                            CharWindow(ch, rc);

                     }

              }

       }

       pos.X = 0; pos.Y = 0;

       SetConsoleCursorPosition(hOut, pos); // 設定游標位置

       CloseHandle(hOut); // 關閉標準輸出裝置控制代碼

       CloseHandle(hIn); // 關閉標準輸入裝置控制代碼

       return 0;

}

 

void CharWindow(char ch, SMALL_RECT rc) // 將ch輸入到指定的視窗中

{

       static COORD chPos = {rc.Left+1, rc.Top+1};

       SetConsoleCursorPosition(hOut, chPos); // 設定游標位置

       if ((ch<0x20)||(ch>0x7e)) // 如果是不可列印的字元,具體檢視ASCII碼錶

              return;

       WriteConsoleOutputCharacter(hOut, &ch, 1, chPos, NULL);

       if (chPos.X >= (rc.Right-2))

       {

              chPos.X = rc.Left;

              chPos.Y++;

       }

       if (chPos.Y>(rc.Bottom-1))

       {

              DeleteTopLine(rc);

              chPos.Y = rc.Bottom-1;

       }

       chPos.X++;

       SetConsoleCursorPosition(hOut, chPos); // 設定游標位置

}

 

void ControlStatus(DWORD state) // 在第一行顯示控制鍵的狀態

{

       CONSOLE_SCREEN_BUFFER_INFO bInfo;

       GetConsoleScreenBufferInfo( hOut, &bInfo );

       COORD home = {0, 24}; // 原來此處為bInfo.dwSize.Y-1,但為了更便於觀察,我把這裡稍微修改了一下

       WORD att0 = BACKGROUND_INTENSITY ;

       WORD att1 = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_RED;

       FillConsoleOutputAttribute(hOut, att0, bInfo.dwSize.X, home, NULL);

       FillConsoleOutputCharacter(hOut, ' ', bInfo.dwSize.X, home, NULL);

       SetConsoleTextAttribute(hOut, att1);

       COORD staPos = {bInfo.dwSize.X-16,24}; // 原來此處為bInfo.dwSize.Y-1

       SetConsoleCursorPosition(hOut, staPos);

       if (state & NUMLOCK_ON)

              WriteConsole(hOut, "NUM", 3, NULL, NULL);

       staPos.X += 4;

       SetConsoleCursorPosition(hOut, staPos);

       if (state & CAPSLOCK_ON)

              WriteConsole(hOut, "CAPS", 4, NULL, NULL);

       staPos.X += 5;

       SetConsoleCursorPosition(hOut, staPos);

       if (state & SCROLLLOCK_ON)

              WriteConsole(hOut, "SCROLL", 6, NULL, NULL);

       SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢復原來的屬性

       SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition); // 恢復原來的游標位置

}

void DeleteTopLine(SMALL_RECT rc)

{

       COORD crDest;

       CHAR_INFO chFill;

       SMALL_RECT rcClip = rc;

       rcClip.Left++;

       rcClip.Right -= 2;

       rcClip.Top++;

       rcClip.Bottom--;

       crDest.X = rcClip.Left;

       crDest.Y = rcClip.Top - 1;

       CONSOLE_SCREEN_BUFFER_INFO bInfo;

       GetConsoleScreenBufferInfo( hOut, &bInfo );

       chFill.Attributes = bInfo.wAttributes;

       chFill.Char.AsciiChar = ' ';

       ScrollConsoleScreenBuffer(hOut, &rcClip, &rcClip, crDest, &chFill);

}

void ClearScreen(void)

{

       CONSOLE_SCREEN_BUFFER_INFO bInfo;

       GetConsoleScreenBufferInfo( hOut, &bInfo );

       COORD home = {0, 0};

       WORD att = bInfo.wAttributes;

       unsigned long size = bInfo.dwSize.X * bInfo.dwSize.Y;

       FillConsoleOutputAttribute(hOut, att, size, home, NULL);

       FillConsoleOutputCharacter(hOut, ' ', size, home, NULL);

}

// 函式功能:畫邊框

void DrawBox(bool bSingle, SMALL_RECT rc)

{

       char chBox[6];

       COORD pos;

       if (bSingle)

       {

              chBox[0] = (char)0xda; // 左上角點

              chBox[1] = (char)0xbf; // 右上角點

              chBox[2] = (char)0xc0; // 左下角點

              chBox[3] = (char)0xd9; // 右下角點

              chBox[4] = (char)0xc4; // 水平

              chBox[5] = (char)0xb3; // 堅直

       }

       else

       {

              chBox[0] = (char)0xc9; // 左上角點

              chBox[1] = (char)0xbb; // 右上角點

              chBox[2] = (char)0xc8; // 左下角點

              chBox[3] = (char)0xbc; // 右下角點

              chBox[4] = (char)0xcd; // 水平

              chBox[5] = (char)0xba; // 堅直

       }

       // 畫邊框的上 下邊界

       for(pos.X = rc.Left+1;pos.X<rc.Right-1;pos.X++)

       {    

              pos.Y = rc.Top;

              // 畫上邊界

              WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);

              // 畫左上角

              if(pos.X == rc.Left+1)

              {

                     pos.X--;

                     WriteConsoleOutputCharacter(hOut, &chBox[0],1, pos, NULL);

                     pos.X++;

              }

              // 畫右上角

              if(pos.X == rc.Right-2)

              {

                     pos.X++;

                     WriteConsoleOutputCharacter(hOut, &chBox[1], 1, pos, NULL);

                     pos.X--;

              }

              pos.Y = rc.Bottom;

              // 畫下邊界

              WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);

              // 畫左下角

              if(pos.X == rc.Left+1)

              {

                     pos.X--;

                     WriteConsoleOutputCharacter(hOut, &chBox[2], 1, pos, NULL);

                     pos.X++;

              }

              // 畫右下角

              if(pos.X==rc.Right-2)

              {

                     pos.X++;

                     WriteConsoleOutputCharacter(hOut, &chBox[3], 1, pos, NULL);

                     pos.X--;

              }

 

       }

       // 畫邊框的左右邊界

       for (pos.Y = rc.Top+1; pos.Y<=rc.Bottom-1; pos.Y++)

       {

              pos.X = rc.Left;

              // 畫左邊界

              WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);

              pos.X = rc.Right-1;

              // 畫右邊界

              WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);

       }

}

當你輸入畫面中句子時,執行結果如下圖:

 

 

(十) 讀取滑鼠資訊操作

與讀取鍵盤資訊方法相似,滑鼠資訊也是通過ReadConsoleInput來獲取的,其MOUSE_EVENT_RECORD具有下列定義:

typedef struct _MOUSE_EVENT_RECORD {

COORD dwMousePosition; // 當前滑鼠位置

DWORD dwButtonState; // 滑鼠按鈕狀態

DWORD dwControlKeyState; // 鍵盤控制鍵狀態

DWORD dwEventFlags; // 事件狀態

} MOUSE_EVENT_RECORD;

其中,dwButtonState反映了使用者按下滑鼠按鈕的情況,它可以是:

FROM_LEFT_1ST_BUTTON_PRESSED(最 左邊按鈕)、RIGHTMOST_BUTTON_PRESSED(最右邊按鈕)、FROM_LEFT_2ND_BUTTON_PRESSED(左起第二個 按鈕)、FROM_LEFT_3RD_BUTTON_PRESSED(左起第三個按鈕)和FROM_LEFT_4TH_BUTTON_PRESSED (左起第四個按鈕)。而dwEventFlags表示滑鼠 的事件,如DOUBLE_CLICK(雙擊)、MOUSE_MOVED(移動)和 MOUSE_WHEELED(滾輪滾動,只適用於Windows 2000/XP)。dwControlKeyState的含義同前。

下面舉一個例子。這個例子能把滑鼠的當前位置顯示在控制檯視窗的最後一行上,若單擊滑鼠左鍵,則在當前位置處寫一個字元‘A’,若雙擊滑鼠任一按鈕,則程式終止。具體程式碼如下:

#include <windows.h>

#include <stdio.h>

#include <string.h>

HANDLE hOut;

HANDLE hIn;

void ClearScreen(void);

void DispMousePos(COORD pos); // 在第24行顯示滑鼠位置

int main()

{

       hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 獲取標準輸出裝置控制代碼

       hIn = GetStdHandle(STD_INPUT_HANDLE); // 獲取標準輸入裝置控制代碼

       WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE ;

       // 背景是藍色,文字顏色是黃色

       SetConsoleTextAttribute(hOut, att);

       ClearScreen(); // 清屏

       INPUT_RECORD mouseRec;

       DWORD state = 0, res;

       COORD pos = {0, 0};

       for(;;) // 迴圈

       {

              ReadConsoleInput(hIn, &mouseRec, 1, &res);

              if (mouseRec.EventType == MOUSE_EVENT)

              {

                     if (mouseRec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK)

                            break; // 雙擊滑鼠退出迴圈       

                     pos = mouseRec.Event.MouseEvent.dwMousePosition;

                     DispMousePos(pos);

                     if (mouseRec.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)

                            FillConsoleOutputCharacter(hOut, 'A', 1, pos, NULL);

              }

       }

       pos.X = pos.Y = 0;

       SetConsoleCursorPosition(hOut, pos); // 設定游標位置

       CloseHandle(hOut); // 關閉標準輸出裝置控制代碼

       CloseHandle(hIn); // 關閉標準輸入裝置控制代碼

}

void DispMousePos(COORD pos) // 在第24行顯示滑鼠位置

{

       CONSOLE_SCREEN_BUFFER_INFO bInfo;

       GetConsoleScreenBufferInfo( hOut, &bInfo );

       COORD home = {0, 24};

       WORD att0 = BACKGROUND_INTENSITY ;

       FillConsoleOutputAttribute(hOut, att0, bInfo.dwSize.X, home, NULL);

       FillConsoleOutputCharacter(hOut, ' ', bInfo.dwSize.X, home, NULL);

       char s[20];

       sprintf(s,"X = %2lu, Y = %2lu",pos.X, pos.Y);

       SetConsoleTextAttribute(hOut, att0);

       SetConsoleCursorPosition(hOut, home);

       WriteConsole(hOut, s, strlen(s), NULL, NULL);

       SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢復原來的屬性

       SetConsoleCursorPosition(hOut, bInfo.dwCursorPosition); // 恢復原來的游標位置

}

void ClearScreen(void)

{

       CONSOLE_SCREEN_BUFFER_INFO bInfo;

       GetConsoleScreenBufferInfo( hOut, &bInfo );

       COORD home = {0, 0};

       unsigned long size = bInfo.dwSize.X * bInfo.dwSize.Y;

       FillConsoleOutputAttribute(hOut, bInfo.wAttributes, size, home, NULL);

       FillConsoleOutputCharacter(hOut, ' ', size, home, NULL);

}

如果你嘗試在螢幕上寫一個“Hello!”,將看到如下執行結果:

  

 

(十一) 結語

綜上所述,利用控制檯視窗的Widows API函式可以設計簡潔美觀的文字介面,使得用Visual C++ 6.0開發環境深入學習C++以及文字介面設計成為一件比較容易的事件。當然文字介面的設計還需要一定的方法和技巧,限於篇幅,這裡不再闡述。

補充篇--經典控制檯程式

下面是我在網上找到的幾個經典程式碼,供大家學習!

①    輸出各種綵帶。來源:百度文庫《在控制檯視窗中輸出綵帶(含傾斜綵帶)》

原始碼:

#include <windows.h>

#include <stdio.h>

void shuiping();

void chuizhi();

void zuoqingxie();

void youqingxie();

void jiantou();

void SetColor(unsigned short ForeColor,unsigned short BackGroundColor);

int main()

{

       int a;

       SMALL_RECT rc = {0,0,20,10};

       HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

       SetConsoleOutputCP(936);

       SetColor(14,3);

       printf("0:水平綵帶,\n1:垂直綵帶,\n2:右傾斜綵帶,\n3:左傾斜綵帶,\n4:箭頭狀綵帶,\n5:水紋狀綵帶,\n其他輸入退出\n");

       scanf("%d",&a);

       while(a==0||a==1||a==2||a==3||a==4||a==5)

       {

              if(a==0)//實現水平綵帶輸出

              {

                     shuiping();

                     SetColor(14,3); //重新整理緩衝區,使字跡可見

              }    

              else if(a==1)//實現垂直綵帶輸出

              {

                     chuizhi();

                     SetColor(14,3);

              }

              else if(a==2)//實現右傾斜綵帶輸出

              {

                     youqingxie();

                     SetColor(14,3);

              }

              else if(a==3)//實現左傾斜綵帶輸出

              {

                     zuoqingxie();