七、滾動和移動
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++以及文本介面設計成為一件比較容易的事件。當然文本介面的設計還需要一定的方法和技巧,限於篇幅,這裡不再闡述
文章出處:http://www.diybl.com/course/3_program/c++/cppsl/2008829/138698_2.html