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();

                     SetColor(14,3);

              }

              else if(a==4)//實現箭頭狀綵帶輸出

              {

                     jiantou();

                     SetColor(14,3);

              }

              else if(a==5)//實現水紋狀綵帶輸出

              {

                     jiantou();

                     jiantou();

                     SetColor(14,3);

              }

              fflush(stdin);

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

              scanf("%d",&a);

       }

       return 0;

      

}

void SetColor(unsigned short ForeColor,unsigned short BackGroundColor)

{

       HANDLE hCon=GetStdHandle(STD_OUTPUT_HANDLE);

       SetConsoleTextAttribute(hCon,ForeColor+BackGroundColor*0x10);

}

//水平綵帶函數

void shuiping()

{

       int i,j,k;

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

       {

              for(j=0;j<=79;++j)

              {

                     k=i%16;

                     SetColor(k,k);

                     putchar('A');

              }

       }

}

 

//垂直綵帶函數

void chuizhi()

{

       int i,j,k;

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

       {

              for(j=0;j<40;++j)

              {

                     k=j%16;

                     SetColor(k,k);

                     putchar('A');

                     putchar('A');

        }

             

       }

}

 

//右傾斜綵帶函數

void youqingxie()

{

       int i,j,k;

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

       {

              for(j=0;j<40;++j)

              {

                     if(j-i>=0)

                            k=(j-i)%16;

                     else

                            k=(j-i)%16+16;

                     SetColor(k,k);

                     putchar('A');

                     putchar('A');

              }

       }

}

 

//左傾斜綵帶函數

void zuoqingxie()

{

       int i,j,k;

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

       {

              for(j=0;j<40;++j)

              {

                     k=(i+j)%16;

                     SetColor(k,k);

                     putchar('A');

                     putchar('A');

              }

       }

}

//箭頭狀綵帶函數

void jiantou()

{

       int i,j,k;

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

       {

              for(j=0;j<40;++j)

              {

                     k=(i+j)%16;

                     SetColor(k,k);

                     putchar('A');

                     putchar('A');

              }

       }

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

       {

              for(j=0;j<40;++j)

              {

                     if(j-i>=0)

                            k=(j-i)%16;

                     else

                            k=(j-i)%16+16;

                     SetColor(k,k);

                     putchar('A');

                     putchar('A');

              }

       }

}

運行結果展示:

水平綵帶

      豎直綵帶

左傾斜綵帶

      右傾斜綵帶

箭頭狀綵帶

      水波狀綵帶

 


②輸出顏色方陣

出處: 百度知道《在控制檯窗口中輸出顏色方陣》

作者:AlphaBlend

#include <windows.h>

#include <stdio.h>

#include <conio.h>

 

#define getrandom( min, max ) ((rand() % (int)(((max)+1) - (min))) + (min))

 

void Init(void);

void gotoxy(int x, int y);

void regularcolor(void);

void randomcolor(void);

void Cls(HANDLE hConsole);

 

HANDLE hOut;

int forecolor[16];

int backcolor[16];

 

int main(void)

{

       int i;

       int a;

      

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

       {

              forecolor[i] = i;

              backcolor[i] = i << 4;

       }

       hOut = GetStdHandle(STD_OUTPUT_HANDLE);

      

       Init();

       while(1)

       {

              a = getch();

              if (a == 48)

              {

                     Cls(hOut);

                     regularcolor();

                     getch();

              } else if (a == 49) {

                     Cls(hOut);

                     randomcolor();

                     getch();

              }  else {

                     Cls(hOut);

                     break;

              }

              Cls(hOut);

              Init();

       }

      

      

       CloseHandle(hOut);

       return 0;

}

//---------------------------------------------------------------------------

 

void gotoxy(int x, int y)

{

       COORD pos = {x, y};

       SetConsoleCursorPosition(hOut, pos);

}

 

void regularcolor(void)

{

       int i, j, x, y;

       int l = 8, t = 5;

       for (y = 0; y < 16; y++)

       {

              gotoxy(l - 3, y + t);

              SetConsoleTextAttribute(hOut, forecolor[15]|backcolor[0]);

              printf("%d", y);

              for (x = 0; x < 16; x++)

              {

                     gotoxy(x * 4 + l, y + t);

                     SetConsoleTextAttribute(hOut, forecolor[y]|backcolor[x]);

                     printf("ZZZ");

                     if (y == 15)

                     {

                            gotoxy(x * 4 + l, 17 + t);

                            SetConsoleTextAttribute(hOut, forecolor[15]|backcolor[0]);

                            printf("%d", x);

                     }

              }

       }

}

 

void randomcolor(void)

{

       int i, j, x, y;

       int l = 8, t = 5;

       char s[4] = {"012"};

       rand();

       for (y = 0; y < 16; y++)

       {

              for (x = 0; x < 16; x++)

              {

                     s[0] = getrandom(32, 127);

                     s[1] = getrandom(32, 127);

                     s[2] = getrandom(32, 127);

                     gotoxy(x * 4 + l, y + t);

                     SetConsoleTextAttribute(hOut, forecolor[getrandom(0, 15)]|backcolor[getrandom(0, 15)]);

                     printf("%c", s[0]);

                     gotoxy(x * 4 + l + 1, y + t);

                     SetConsoleTextAttribute(hOut, forecolor[getrandom(0, 15)]|backcolor[getrandom(0, 15)]);

                     printf("%c", s[1]);

                     gotoxy(x * 4 + l + 2, y + t);

                     SetConsoleTextAttribute(hOut, forecolor[getrandom(0, 15)]|backcolor[getrandom(0, 15)]);

                     printf("%c", s[2]);

              }

       }

}

 

void Cls(HANDLE hConsole)

{

       COORD coordScreen = {0, 0};

      

       BOOL   bSuccess;

       DWORD  cCharsWritten;

       CONSOLE_SCREEN_BUFFER_INFO csbi;

       DWORD  dwConSize;

      

       SetConsoleTextAttribute(hOut, 0x0f|0);

       bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi);

       dwConSize = csbi.dwSize.X * csbi.dwSize.Y;

      

       bSuccess = FillConsoleOutputCharacter(hConsole, (TCHAR) '   ', dwConSize, coordScreen, &cCharsWritten);

       bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi);

       bSuccess = FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten);

       bSuccess = SetConsoleCursorPosition(hConsole, coordScreen);

}

 

void Init(void)

{

       gotoxy(30, 10);

       printf("0. Regular Color Array");

       gotoxy(30, 11);

       printf("1. Random Color Array");

       gotoxy(30, 12);

       printf("2. Quit");

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章