控制檯窗口界面編程控制

轉自: http://blog.csdn.net/yxnk/archive/2008/05/11/2434685.aspx

 

摘要: 文本界面的控制檯應用程序開發是深入學習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程序。一旦C++控制檯應用程序在Windows 9x/NT/2000操作系統中運行後,就會彈出一個窗口。例如下列過程:

  單擊Visual C++標準工具欄上的“New Text File”按鈕,打開一個新的文檔窗口。 

  選擇File | Save菜單或按快捷鍵Ctrl+S或單擊標準工具欄的Save按鈕,彈出“保存爲”文件對話框。將文件名爲“Hello.cpp” (注意擴展名.cpp不能省略)。 

  在文檔窗口中輸入下列代碼:

#include 
void main()
{
cout<<"Hello, Console!"< }

  單擊小型編譯工具欄中的“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 
#include 
#include 
void main()
{
HANDLE hOut;
hout = GetStdHandle(STD_OUTPUT_HANDLE);
// 獲取標準輸出設備句柄
CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口信息
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();
COORD pos = {0, 0}; 
FillConsoleOutputCharacter(hOut, ' ', bInfo.dwSize.X * bInfo.dwSize.Y, pos, NULL);
// 向窗口中填充字符以獲得清屏的效果
CloseHandle(hOut); // 關閉標準輸出設備句柄
}

程序中,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 
#include 
#include 
void main()
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); 
// 獲取標準輸出設備句柄
CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口緩衝區信息
GetConsoleScreenBufferInfo(hOut, bInfo );
// 獲取窗口緩衝區信息
char strTitle[255];
GetConsoleTitle(strTitle, 255); // 獲取窗口標題
printf("當前窗口標題是:%s/n", strTitle);
_getch();
SetConsoleTitle("控制檯窗口操作"); // 獲取窗口標題
_getch();
COORD size = {80, 25};
SetConsoleScreenBufferSize(hOut,size); // 重新設置緩衝區大小
_getch();
SMALL_RECT rc = {0,0, 80-1, 25-1}; // 重置窗口位置和大小
SetConsoleWindowInfo(hOut,true ,&rc);
CloseHandle(hOut); // 關閉標準輸出設備句柄
}

  需要說明的是,控制檯窗口的原點座標是(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); // 繪製邊框
void main()
{
 hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 獲取標準輸出設備句柄
 SetConsoleOutputCP(437); // 設置代碼頁
 ShadowWindowLine("Display a line of words, and center the window with shadow.");
 CloseHandle(hOut); // 關閉標準輸出設備句柄
}

void ShadowWindowLine(char *str)
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo; // 窗口緩衝區信息
 GetConsoleScreenBufferInfo( hOut, &bInfo ); // 獲取窗口緩衝區信息
 // 計算顯示窗口大小和位置
 int x1, y1, x2, y2, chNum = strlen(str);
 x1 = (bInfo.dwSize.X - chNum)/2 - 2;
 y1 = bInfo.dwSize.Y/2 - 2;
 x2 = x1 + chNum + 4;
 y2 = y1 + 5;
 WORD att1 = BACKGROUND_INTENSITY; // 陰影屬性
 WORD att0 = FOREGROUND_RED |FOREGROUND_GREEN |FOREGROUND_BLUE |  
       FOREGROUND_INTENSITY |
       BACKGROUND_RED | BACKGROUND_BLUE; // 文本屬性

 WORD attText = FOREGROUND_RED |FOREGROUND_INTENSITY; // 文本屬性
 // 設置陰影
 COORD posShadow = {x1+1, y1+1}, posText = {x1, y1};
 for (int i=0; i<5; i++){
  FillConsoleOutputAttribute(hOut, att1, chNum + 4, posShadow, NULL); 
  posShadow.Y++;
 }
 // 填充窗口背景
 for (i=0; i<5; i++){
  FillConsoleOutputAttribute(hOut, att0, chNum + 4, posText, NULL); 
  posText.Y++;
 }
 // 寫文本和邊框
 posText.X = x1 + 2;
 posText.Y = y1 + 2;
 WriteConsoleOutputCharacter(hOut, str, strlen(str), posText, NULL);
 SMALL_RECT rc = {x1, y1, x2-1, y2-1};
 DrawBox(true, rc);
 SetConsoleTextAttribute(hOut, bInfo.wAttributes); // 恢復原來的屬性
}

void DrawBox(bool bSingle, SMALL_RECT rc)
{
 char chBox[6];
 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; // 堅直
 }
 COORD pos = {rc.Left, rc.Top};
 WriteConsoleOutputCharacter(hOut, &chBox[0], 1, pos, NULL);

 for (pos.X = rc.Left + 1; pos.X  WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);

 pos.X = rc.Right;
 WriteConsoleOutputCharacter(hOut, &chBox[1], 1, pos, NULL);

 for (pos.Y = rc.Top+1; pos.Y {
  pos.X = rc.Left;
  WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);
  pos.X = rc.Right;
  WriteConsoleOutputCharacter(hOut, &chBox[5], 1, pos, NULL);
 }
 pos.X = rc.Left; pos.Y = rc.Bottom;
 WriteConsoleOutputCharacter(hOut, &chBox[2], 1, pos, NULL);
 
 for (pos.X = rc.Left + 1; pos.X  WriteConsoleOutputCharacter(hOut, &chBox[4], 1, pos, NULL);

 pos.X = rc.Right;
 WriteConsoleOutputCharacter(hOut, &chBox[3], 1, pos, NULL);
}
  
  程序運行結果如下圖所示。
控制檯窗口程序運行結果 
  需要說明的是,上述程序在不同的字符代碼頁面(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 
#include 
#include 
HANDLE hOut;
void DeleteLine(int row); // 刪除一行
void MoveText(int x, int y, SMALL_RECT rc); // 移動文本塊區域
void ClearScreen(void); // 清屏
void main()
{
 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");
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo ); 
 COORD endPos = {0, bInfo.dwSize.Y - 1};
 SetConsoleCursorPosition(hOut, endPos); // 設置光標位置
 SMALL_RECT rc = {0, 2, 40, 5};
 _getch();
 MoveText(10, 5, rc);
 _getch();
 DeleteLine(5);
 CloseHandle(hOut); // 關閉標準輸出設備句柄
}

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 
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); // 刪除指定窗口中最上面的行並滾動
void 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 keyRec;
 DWORD state = 0, res;
 char ch;
 SMALL_RECT rc = {20, 2, 40, 12};
 DrawBox(true, 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); // 關閉標準輸入設備句柄
}

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)) return;
 WriteConsoleOutputCharacter(hOut, &ch, 1, chPos, NULL);
 if (chPos.X>=(rc.Right-1))
 {
  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, 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,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--;
 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);
}

  程序運行結果如下圖所示:
  


十、讀取鼠標信息

  與讀取鍵盤信息方法相似,鼠標信息也是通過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); // 在最後一行顯示鼠標位置
void 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 = 0; pos.Y = 0;
  SetConsoleCursorPosition(hOut, pos); // 設置光標位置
  CloseHandle(hOut); // 關閉標準輸出設備句柄
  CloseHandle(hIn); // 關閉標準輸入設備句柄
 }

void DispMousePos(COORD pos) // 在最後一行顯示鼠標位置
{
 CONSOLE_SCREEN_BUFFER_INFO bInfo;
 GetConsoleScreenBufferInfo( hOut, &bInfo ); 
 COORD home = {0, bInfo.dwSize.Y-1};
 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);
}

  程序運行結果如下:
 

十一、結語

  綜上所述,利用控制檯窗口的Widows API函數可以設計簡潔美觀的文本界面,使得用Visual C++ 6.0開發環境深入學習C++以及文本界面設計成爲一件比較容易的事件。當然文本界面的設計還需要一定的方法和技巧,限於篇幅,這裏不再闡述。
發佈了10 篇原創文章 · 獲贊 1 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章