C語言控制台圖形


一:設置句柄與窗口信息

 

在Windows操作系統下用C語言編寫控制台的窗口界面首先要獲取當前標准輸入和標准輸出設備的句柄。通過調用函數GetStdHandle可以獲取當前標准輸入以及輸出設備的句柄。函數原型為:

[cpp] view plain copy  
HANDLE GetStdHandle(DWORD nStdHandle);  
/* 
其中,nStdHandle可以是 
STD_INPUT_HANDLE    標准輸入設備句柄 
STD_OUTPUT_HANDLE   標准輸出設備句柄 
STD_ERROR_HANDLE    標准錯誤設備句柄 
*/  
       
       需要說明的是,“句柄”是Windows最常用的一個概念。它通常用來標識Windows資源(如菜單、 圖標、窗口等)和設備等對象。雖然可以把句柄理解為是一個指針變量類型,但它不是對象所在的地址指針,而是作為Windows系統內部表的索引值來使用 的。調用相關文本界面控制的API函數。這些函數可分為三類。一是用於控制台窗口操作的函數(包括窗口的緩沖區大小、窗口前景字符和背景顏色、窗口標題、大小和位置等);二是用於控制台輸入輸出的函數(包括字符屬性操作函數);其他的函數並為最后一類。通過調用CloseHandle函數來關閉輸入輸出句柄。

 

       示例程序:

[cpp] view plain copy  
#include <stdio.h>  
#include <windows.h>  
#include <conio.h>  
  
int main(int argc,char *argv[])  
{  
    HANDLE handle_out;                              //定義一個句柄  
    CONSOLE_SCREEN_BUFFER_INFO screen_info;         //定義窗口緩沖區信息結構體  
    COORD pos = {0, 0};                             //定義一個坐標結構體  
    handle_out = GetStdHandle(STD_OUTPUT_HANDLE);   //獲得標准輸出設備句柄  
    GetConsoleScreenBufferInfo(handle_out, &screen_info);   //獲取窗口信息  
    _getch();   //輸入一個字符,不會顯示到屏幕上  
    /* 
    向整個緩沖區填充字符'A' 
    其中填充的開始處為pos,坐標為{0, 0},也就是屏幕最左上角的字符處 
    填充個數為screen_info.dwSize.X(緩沖區寬度,也就是橫坐標最大值加1) * screen_info.dwSize.Y(緩沖區高度,也就是縱坐標最大值加1) 
    因此可以達到向整個緩沖區填充字符'A'的效果 
    */  
    FillConsoleOutputCharacter(handle_out, 'A', screen_info.dwSize.X * screen_info.dwSize.Y, pos, NULL);  
    CloseHandle(handle_out);    //關閉標准輸出設備句柄  
    return 0;  
}  
[cpp] view plain copy  
//程序中,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函數,如下:

[cpp] view plain copy  
//獲取控制台窗口信息  
GetConsoleScreenBufferInfo();  
  
//獲取控制台窗口標題  
GetConsoleTitle();  
  
//更改指定緩沖區大小  
SetConsoleScreenBufferSize();  
  
//設置控制台窗口標題  
SetConsoleTitle();  
  
//設置控制台窗口信息  
SetConsoleWindowInfo();  


       下面的示例程序用於說明此類函數的使用:

[cpp] view plain copy  
#include <stdio.h>  
#include <stdlib.h>  
#include <Windows.h>  
#include <conio.h>  
#define N 255  
  
int main()  
{  
    HANDLE handle_out;  //定義一個句柄  
    CONSOLE_SCREEN_BUFFER_INFO scbi;    //定義一個窗口緩沖區信息結構體  
    COORD size = {80, 25};      //定義一個坐標結構體  
    char strtitle[N];  
    handle_out = GetStdHandle(STD_OUTPUT_HANDLE);   //獲得標准輸出設備句柄  
    GetConsoleScreenBufferInfo(handle_out, &scbi);  //獲得窗口緩沖區信息  
    GetConsoleTitle(strtitle, N);   //獲得當前窗口標題  
    printf("當前窗口標題為:%s\n", strtitle);  
    _getch();  
    SetConsoleTitle("控制台窗口操作");     //設置窗口標題為“控制台窗口操作”  
    GetConsoleTitle(strtitle, N);           //獲得當前窗口標題  
    printf("當前窗口標題為:%s\n", strtitle);  
    _getch();  
    SetConsoleScreenBufferSize(handle_out, size);   // 重新設置緩沖區大小  
    _getch();  
    SMALL_RECT rc = {0, 0, 80-1, 25-1};     // 重置窗口位置和大小  
    SetConsoleWindowInfo(handle_out, 1, &rc);  
    CloseHandle(handle_out);    //關閉標准輸出設備句柄  
    return 0;  
}  

       其中,SetConsoleScreenBufferSize函數指定新的控制台屏幕緩沖區的大小,以字符列和行為單位。指定的寬度和高度不能小於控制台屏幕緩沖區窗口的寬度和高度。指定的大小也不能小於系統允許的最小大小。這個最低取決於控制台當前的字體大小 (由用戶選定)。


三:文本屬性


在這里介紹一個設置文本屬性的函數,原型如下

[cpp] view plain copy  
BOOL SetConsoleTextAttribute(   // 設置WriteConsole等函數的字符屬性  
HANDLE hConsoleOutput,          // 句柄  
WORD wAttributes                // 文本屬性  
);  


       順便提一下文本屬性,其實就是顏色屬性,有背景色和前景色(就是字符的顏色)兩類,每一類只提供三原色(紅,綠,藍)和加強色(灰色,可與其他顏色搭配使用,使顏色變亮,后面會提到)。最后還有一個反色(不太清楚這個到底怎么用,很奇葩的東西)。示例程序如下:

[cpp] view plain copy  
#include <stdio.h>  
#include <stdlib.h>  
#include <windows.h>  
#include <conio.h>  
/* 
基本文本屬性 
FOREGROUND_BLUE 藍色 
FOREGROUND_GREEN 綠色 
FOREGROUND_RED 紅色 
FOREGROUND_INTENSITY 加強 
BACKGROUND_BLUE 藍色背景 
BACKGROUND_GREEN 綠色背景 
BACKGROUND_RED 紅色背景 
BACKGROUND_INTENSITY 背景色加強 
COMMON_LVB_REVERSE_VIDEO 反色 
*/  
  
const WORD FORE_BLUE   = FOREGROUND_BLUE;           //藍色文本屬性  
const WORD FORE_GREEN  = FOREGROUND_GREEN;          //綠色文本屬性  
const WORD FORE_RED    = FOREGROUND_RED;            //紅色文本屬性  
const WORD FORE_PURPLE = FORE_BLUE | FORE_RED;      //紫色文本屬性  
const WORD FORE_CYAN   = FORE_BLUE | FORE_GREEN;    //青色文本屬性  
const WORD FORE_YELLOW = FORE_RED | FORE_GREEN;     //黃色文本屬性  
const WORD FORE_GRAY   = FOREGROUND_INTENSITY;      //灰色文本屬性  
const WORD BACK_BLUE   = BACKGROUND_BLUE;           //藍色背景屬性  
const WORD BACK_GREEN  = BACKGROUND_GREEN;          //綠色背景屬性  
const WORD BACK_RED    = BACKGROUND_RED;            //綠色背景屬性  
const WORD BACK_PURPLE = BACK_BLUE | BACK_RED;      //紫色背景屬性  
const WORD BACK_CYAN   = BACK_BLUE | BACK_GREEN;    //青色背景屬性  
const WORD BACK_YELLOW = BACK_RED | BACK_GREEN;     //黃色背景屬性  
const WORD BACK_GRAY   = BACKGROUND_INTENSITY;      //灰色背景屬性  
  
int main()  
{  
    HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);    //獲得標准輸出設備句柄  
    CONSOLE_SCREEN_BUFFER_INFO csbi;                        //定義窗口緩沖區信息結構體  
    GetConsoleScreenBufferInfo(handle_out, &csbi);          //獲得窗口緩沖區信息  
    SetConsoleTextAttribute(handle_out, FORE_BLUE);  
    printf("藍色字符\n");  
    SetConsoleTextAttribute(handle_out, FORE_RED);  
    printf("紅色字符\n");  
    SetConsoleTextAttribute(handle_out, FORE_GREEN);  
    printf("綠色字符\n");  
    SetConsoleTextAttribute(handle_out, FORE_PURPLE);  
    printf("紫色字符\n");  
    SetConsoleTextAttribute(handle_out, FORE_CYAN);  
    printf("青色字符\n");  
    SetConsoleTextAttribute(handle_out, FORE_YELLOW);  
    printf("黃色字符\n");  
    SetConsoleTextAttribute(handle_out, FORE_GRAY);  
    printf("灰色字符\n");  
    SetConsoleTextAttribute(handle_out, FORE_GREEN | FORE_BLUE | FORE_RED);  
    printf("白色字符\n");  
    SetConsoleTextAttribute(handle_out, BACK_BLUE);  
    printf("藍色背景\n");  
    SetConsoleTextAttribute(handle_out, BACK_RED);  
    printf("紅色背景\n");  
    SetConsoleTextAttribute(handle_out, BACK_GREEN);  
    printf("綠色背景\n");  
    SetConsoleTextAttribute(handle_out, BACK_PURPLE);  
    printf("紫色背景\n");  
    SetConsoleTextAttribute(handle_out, BACK_CYAN);  
    printf("青色背景\n");  
    SetConsoleTextAttribute(handle_out, BACK_YELLOW);  
    printf("黃色背景\n");  
    SetConsoleTextAttribute(handle_out, BACK_GRAY);  
    printf("灰色背景\n");  
    SetConsoleTextAttribute(handle_out, BACK_BLUE | BACK_RED | BACK_GREEN);  
    printf("白色背景\n");  
    SetConsoleTextAttribute(handle_out, BACK_GREEN | FORE_RED); //示例:綠色背景紅色字符  
    printf("綠色背景與紅色字符的混合\n");  
    SetConsoleTextAttribute(handle_out, FOREGROUND_INTENSITY | FORE_RED);   //示例:亮紅色字符  
    printf("亮色的生成,與加強色融合\n");  
    return 0;  
}  

       上述示例程序最好用C++來中編譯,因為有C語言的編譯器或者IDE不支持上述的定義常量的方式。需要從這個示例中了解的是三原色的混合是用C語言位運算中的按位或 | 運算符,背景顏色與字符顏色的同時定義也是使用這個運算符融合。另外,將任意顏色與對應的加強色(灰色,有前景和背景兩種,需要對應)融合后會成為對應顏色的高亮版,比如紅色字符與前景加強色的融合會結合成亮紅色。

 

       至於反色,大家可以試試,當我設置了文本屬性為反色后,輸入字符都不顯示了,但是下標還在移動,我估計反色將白色字符變成了黑色字符,與黑色背景一樣,所以沒有顯示出來。至於反色與其他的組合以及其他的顏色組合,還需要大家一起探索、、、

 

四:文本屬性

 

文本顏色屬性已經學會了,那么下面就學習幾個比較常用的文本輸出函數,如下:

[cpp] view plain copy  
BOOL FillConsoleOutputAttribute(    // 填充字符屬性  
HANDLE hConsoleOutput,              // 句柄  
WORD wAttribute,                    // 文本屬性  
DWORD nLength,                      // 個數  
COORD dwWriteCoord,                 // 開始位置  
LPDWORD lpNumberOfAttrsWritten      // 返回填充的個數  
);  
BOOL FillConsoleOutputCharacter(    // 填充指定數據的字符  
HANDLE hConsoleOutput,              // 句柄  
TCHAR cCharacter,                   // 字符  
DWORD nLength,                      // 字符個數  
COORD dwWriteCoord,                 // 起始位置  
LPDWORD lpNumberOfCharsWritten      // 已寫個數  
);  
BOOL WriteConsoleOutputCharacter(   // 在指定位置處寫指定數量的字符  
HANDLE hConsoleOutput,              // 句柄  
LPCTSTR lpCharacter,                // 字符串  
DWORD nLength,                      // 字符個數  
COORD dwWriteCoord,                 // 起始位置  
LPDWORD lpNumberOfCharsWritten      // 已寫個數  
);  


       另外再介紹一個表示區域的結構體,如下:

[cpp] view plain copy  
typedef struct _SMALL_RECT  //表示矩形區域的結構體  
{  
  SHORT Left;       //左邊界  
  SHORT Top;        //上邊界  
  SHORT Right;      //右邊界  
  SHORT Bottom;     //下邊界  
} SMALL_RECT;  
/* 
微軟官方的說法是 
Left    區域的左上頂點的X坐標 
Top     區域的左上頂點的Y坐標 
Right   區域的右下頂點的X坐標 
Bottom  區域的右下頂點的Y坐標 
*/  


       通過以上的文本輸出函數,我們來做一個簡單的在一個具有陰影效果的窗口顯示字符串的示例程序,如下:

[cpp] view plain copy  
#include <stdio.h>  
#include <stdlib.h>  
#include <Windows.h>  
#include <conio.h>  
  
int main()  
{  
    char *str = "Hello World!";     //定義輸出信息  
    int len = strlen(str), i;  
    WORD shadow = BACKGROUND_INTENSITY;     //陰影屬性  
    WORD text = BACKGROUND_GREEN | BACKGROUND_INTENSITY;    //文本屬性  
    HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);    //獲得標准輸出設備句柄  
    CONSOLE_SCREEN_BUFFER_INFO csbi;    //定義窗口緩沖區信息結構體  
    GetConsoleScreenBufferInfo(handle_out, &csbi);  //獲得窗口緩沖區信息  
    SMALL_RECT rc;      //定義一個文本框輸出區域  
    COORD posText;      //定義文本框的起始坐標  
    COORD posShadow;    //定義陰影框的起始坐標  
    //確定區域的邊界  
    rc.Top = 8;     //上邊界  
    rc.Bottom = rc.Top + 4;     //下邊界  
    rc.Left = (csbi.dwSize.X - len) / 2 - 2;    //左邊界,為了讓輸出的字符串居中  
    rc.Right = rc.Left + len + 4;   //右邊界  
    //確定文本框起始坐標  
    posText.X = rc.Left;  
    posText.Y = rc.Top;  
    //確定陰影框的起始坐標  
    posShadow.X = posText.X + 1;  
    posShadow.Y = posText.Y + 1;  
    for (i=0; i<5; ++i)     //先輸出陰影框  
    {  
        FillConsoleOutputAttribute(handle_out, shadow, len + 4, posShadow, NULL);  
        posShadow.Y++;  
    }  
    for (i=0; i<5; ++i)     //在輸出文本框,其中與陰影框重合的部分會被覆蓋掉  
    {  
        FillConsoleOutputAttribute(handle_out, text, len + 4, posText, NULL);  
        posText.Y++;  
    }  
    //設置文本輸出處的坐標  
    posText.X = rc.Left + 2;  
    posText.Y = rc.Top + 2;  
    WriteConsoleOutputCharacter(handle_out, str, len, posText, NULL);   //輸出字符串  
    SetConsoleTextAttribute(handle_out, csbi.wAttributes);   // 恢復原來的屬性  
    CloseHandle(handle_out);  
    return 0;  
}  

 

       以上樣例在Code::Blocks 13.12中編譯通過。


五:文本移動

控制文本的移動是控制台窗口界面編程的一個很重要的功能,有了這個功能我們可以實現界面的滾動。下面我們介紹一個控制文本移動的函數,如下:

[cpp] view plain copy  
BOOL ScrollConsoleScreenBuffer(             //文本移動函數  
    HANDLE hConsoleOutput,                  //句柄  
    const SMALL_RECT *lpScrollRectangle,    //移動區域  
    const SMALL_RECT *lpClipRectangle,      //裁剪區域,如果為NULL,那么將代表整個屏幕緩沖區  
    COORD dwDestinationOrigin,              //移動到的位置,這個點將成為移動區域的左上頂點  
    const CHAR_INFO *lpFill                 //空出區域的填充字符  
);  


       下面來看一個移動文本的樣例程序,如下:

[cpp] view plain copy  
#include <stdio.h>  
#include <conio.h>  
#include <Windows.h>  
#include <stdlib.h>  
  
int main()  
{  
    HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);    //獲得標准輸出設備句柄  
    CONSOLE_SCREEN_BUFFER_INFO csbi;        //定義窗口緩沖區信息結構體  
    SMALL_RECT scroll;      //定義移動區域  
    COORD pos = {0, 5};     //移動位置  
    CHAR_INFO chFill;       //定義填充字符  
    GetConsoleScreenBufferInfo(handle_out, &csbi);  //獲得窗口緩沖區信息  
    //定義填充字符的各個參數及屬性  
    chFill.Char.AsciiChar = ' ';  
    chFill.Attributes = csbi.wAttributes;  
    //輸出文本  
    printf("00000000000000000000000000000\n");  
    printf("11111111111111111111111111111\n");  
    printf("22222222222222222222222222222\n");  
    printf("33333333333333333333333333333\n");  
    //確定區域  
    scroll.Left = 1;  
    scroll.Top = 1;  
    scroll.Right = 10;  
    scroll.Bottom = 2;  
    ScrollConsoleScreenBuffer(handle_out, &scroll, NULL, pos, &chFill); //移動文本  
    return 0;  
}  

 

       在上面的樣例程序中,裁剪區域是整個控制台窗口的屏幕緩沖區,現在如果我們把裁剪區域設定為與移動區域一樣,也就是說ScrollConsoleScreenBuffer函數的第三個參數也改成&scroll,那么結果會怎么樣呢?

 

 

       為什么會發生這種現象呢?很明顯示因為裁剪區域的設定問題,現在我們把裁剪區域依舊設定成移動區域,但是我們只把移動區域下移一行而不是移動在別的位置,看看會有什么現象發生?

 

 

       現在我們應該可以猜想出結論了,別急,再做一個實驗,現在我們將裁減區域又重新改為整個屏幕緩沖區,看看會有什么樣的現象發生?

 

 

       再來最后一個實驗,我們將裁減區域減小為移動區域的上半部分,繼續執行下移一行的操作,看看最終結果會怎么樣?

 

 

       好了,現在我們通過歸納可以得出幾個結論了,那就是

       一,裁減區域以外的區域不會受文本移動的影響。具體是:

1,裁減區域以外的區域不會被移動過來的區域覆蓋,

2,裁減區域以外的區域被移動到他處之后原區域不發生變化,因此不需要填充字符。

總的歸納來說也就是原來是什么樣子,文本移動后還是什么樣子,不會改變。

       二,裁減區域以內的區域受文本移動的影響。具體是:

1,當裁減區域以內的區域被移動到他處造成該區域為空時會被設定的字符填充,

2,裁減區域以內的區域會被移動過來的區域覆蓋。

總的歸納來說也就是完全受文本移動的影響,移動過來就被覆蓋,被移走就由設定的字符來填充

 

六:光標操作


控制文本的移動是控制台窗口界面編程的一個很重要的功能,有了這個功能我們可以實現界面的滾動。下面我們介紹一個控制文本移動的函數,如下:

[cpp] view plain copy  
BOOL ScrollConsoleScreenBuffer(             //文本移動函數  
    HANDLE hConsoleOutput,                  //句柄  
    const SMALL_RECT *lpScrollRectangle,    //移動區域  
    const SMALL_RECT *lpClipRectangle,      //裁剪區域,如果為NULL,那么將代表整個屏幕緩沖區  
    COORD dwDestinationOrigin,              //移動到的位置,這個點將成為移動區域的左上頂點  
    const CHAR_INFO *lpFill                 //空出區域的填充字符  
);  


       下面來看一個移動文本的樣例程序,如下:

[cpp] view plain copy  
#include <stdio.h>  
#include <conio.h>  
#include <Windows.h>  
#include <stdlib.h>  
  
int main()  
{  
    HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);    //獲得標准輸出設備句柄  
    CONSOLE_SCREEN_BUFFER_INFO csbi;        //定義窗口緩沖區信息結構體  
    SMALL_RECT scroll;      //定義移動區域  
    COORD pos = {0, 5};     //移動位置  
    CHAR_INFO chFill;       //定義填充字符  
    GetConsoleScreenBufferInfo(handle_out, &csbi);  //獲得窗口緩沖區信息  
    //定義填充字符的各個參數及屬性  
    chFill.Char.AsciiChar = ' ';  
    chFill.Attributes = csbi.wAttributes;  
    //輸出文本  
    printf("00000000000000000000000000000\n");  
    printf("11111111111111111111111111111\n");  
    printf("22222222222222222222222222222\n");  
    printf("33333333333333333333333333333\n");  
    //確定區域  
    scroll.Left = 1;  
    scroll.Top = 1;  
    scroll.Right = 10;  
    scroll.Bottom = 2;  
    ScrollConsoleScreenBuffer(handle_out, &scroll, NULL, pos, &chFill); //移動文本  
    return 0;  
}  

 

       在上面的樣例程序中,裁剪區域是整個控制台窗口的屏幕緩沖區,現在如果我們把裁剪區域設定為與移動區域一樣,也就是說ScrollConsoleScreenBuffer函數的第三個參數也改成&scroll,那么結果會怎么樣呢?

 

 

       為什么會發生這種現象呢?很明顯示因為裁剪區域的設定問題,現在我們把裁剪區域依舊設定成移動區域,但是我們只把移動區域下移一行而不是移動在別的位置,看看會有什么現象發生?

 

 

       現在我們應該可以猜想出結論了,別急,再做一個實驗,現在我們將裁減區域又重新改為整個屏幕緩沖區,看看會有什么樣的現象發生?

 

 

       再來最后一個實驗,我們將裁減區域減小為移動區域的上半部分,繼續執行下移一行的操作,看看最終結果會怎么樣?

 

 

       好了,現在我們通過歸納可以得出幾個結論了,那就是

       一,裁減區域以外的區域不會受文本移動的影響。具體是:

1,裁減區域以外的區域不會被移動過來的區域覆蓋,

2,裁減區域以外的區域被移動到他處之后原區域不發生變化,因此不需要填充字符。

總的歸納來說也就是原來是什么樣子,文本移動后還是什么樣子,不會改變。

       二,裁減區域以內的區域受文本移動的影響。具體是:

1,當裁減區域以內的區域被移動到他處造成該區域為空時會被設定的字符填充,

2,裁減區域以內的區域會被移動過來的區域覆蓋。

總的歸納來說也就是完全受文本移動的影響,移動過來就被覆蓋,被移走就由設定的字符來填充

 

七:鍵盤事件


 輸入事件中的鍵盤事件通常有字符事件和按鍵事件,這些事件的附帶信息構成了鍵盤輸入的信息,而想要讀取這些信息,是要通過API函數ReadConsoleInput來獲取的,函數原型如下:

[cpp] view plain copy  
BOOL ReadConsoleInput(              //讀取輸入信息  
    HANDLE hConsoleInput,           //句柄  
    PINPUT_RECORD lpBuffer,         //輸入事件結構體的指針  
    DWORD nLength,                  //要讀取的記錄數  
    LPDWORD lpNumberOfEventsRead    //用來接受成功讀取記錄數的指針  
);  //如果該函數成功調用,返回非零值  
//輸入事件結構體的指針可以是結構體數組的首地址,這樣就可以一次性讀取多個記錄數。  


       下面介紹幾個和讀取鍵盤輸入事件有關的結構體,各結構體原型如下:

[cpp] view plain copy  
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;  
/* 
其中事件類型EventType的值有5種 
KEY_EVENT                   代表Event包含一個KEY_EVENT_RECODE結構體 
MOUSE_EVENT                 代表Event包含一個MOUSE_EVENT_RECODE結構體 
WINDOW_BUFFER_SIZE_EVENT    代表Event包含一個WINDOW_BUFFER_SIZE_EVENT_RECORD結構體 
MENU_EVENT                  代表Event包含一個MENU_EVENT_RECORD結構體 
FOCUS_EVENT                 代表Event包含一個FOCUS_EVENT_RECORD結構體 
*/  
  
typedef struct _KEY_EVENT_RECORD    //鍵盤事件結構體   
{  
    BOOL  bKeyDown;             //按鍵狀態,true代表鍵按下,false代表鍵釋放  
    WORD  wRepeatCount;         //按鍵次數  
    WORD  wVirtualKeyCode;      //虛擬鍵  
    WORD  wVirtualScanCode;     //虛擬鍵掃描碼  
    union  
    {  
        WCHAR UnicodeChar;      //解釋成Unicode寬字符  
        CHAR  AsciiChar;        //解釋成ASCII碼字符  
    } uChar;  
    DWORD dwControlKeyState;    //控制鍵狀態  
} KEY_EVENT_RECORD;  
/* 
控制鍵各狀態的值 
ENHANCED_KEY        擴展鍵被按下 
LEFT_ALT_PRESSED    左Alt鍵被按下 
LEFT_CTRL_PRESSED   左Ctrl鍵被按下 
RIGHT_ALT_PRESSED   右Alt鍵被按下 
RIGHT_CTRL_PRESSED  右Ctrl鍵被按下 
NUMLOCK_ON          數字鎖定被打開 
SCROLLLOCK_ON       滾動鎖定被打開 
CAPSLOCK_ON         大寫鎖定被打開 
SHIFT_PRESSED       Shift鍵被按下 
*/  

       當輸入事件為鍵盤事件時,事件類型就為鍵盤事件,為其他事件時,事件類型就為對應的事件。另外,鍵盤上每個有意義的鍵都對應着一個唯一的掃描碼,雖然掃描碼可以作為鍵的標識,但是它是依賴於具體的設備的。因此,在應用程序中,使用的往往是與具體設備無關的虛擬鍵代碼。這種虛擬鍵代碼是一種與具體設備無關的鍵盤編碼。而控制鍵狀態比如大寫鎖定開啟狀態,Ctrl鍵按下狀態等、、、

 

       下面是部分常用虛擬鍵代碼表:

[cpp] view plain copy  
/* 
虛擬鍵代碼      值          鍵名稱 
-----------------------------------------------------             
VK_BACK         0x08        退格鍵 
VK_TAB          0x09        Tab鍵 
VK_RETURN       0x0D        回車鍵 
VK_SHIFT        0x10        Shift鍵 
VK_LSHIFT       0xA0        左Shift鍵 
VK_RSHIFT       0xA1        右Shift鍵 
VK_CONTROL      0x11        Ctrl鍵 
VK_LCONTROL     0xA2        左Ctrl鍵 
VK_RCONTROL     0xA3        右Ctrl鍵 
VK_MENU         0x12        Alt鍵 
VK_LMENU        0xA4        左Alt鍵 
VK_RMENU        0xA5        右Alt鍵 
VK_PAUSE        0x13        Pause鍵 
VK_CAPITAL      0x14        Caps Lock鍵 
VK_NUMLOCK      0x90        Num Lock鍵 
VK_SCROLL       0x91        Scroll Lock鍵 
VK_ESCAPE       0x1B        Esc鍵 
VK_SPACE        0x20        空格鍵 
VK_PRIOR        0x21        Page Up鍵 
VK_NEXT         0x22        Page Down鍵 
VK_END          0x23        End鍵 
VK_HOME         0x24        Home鍵 
VK_LEFT         0x25        左方向鍵 
VK_UP           0x26        上方向鍵 
VK_RIGHT        0x27        右方向鍵 
VK_DOWN         0x28        下方向鍵 
VK_DELETE       0x2E        Delete鍵 
VK_INSERT       0x2D        Insert鍵 
'0'             0x30        0鍵(非小鍵盤) 
'1'             0x31        1鍵(非小鍵盤) 
'2'             0x32        2鍵(非小鍵盤) 
...             ...         ... 
'9'             0x39        9鍵(非小鍵盤) 
'A'             0x41        A鍵 
'B'             0x42        B鍵 
...             ...         ... 
'Z'             0x5A        Z鍵 
VK_SLEEP        0x5F        Sleep鍵 
VK_NUMPAD0      0x60        小鍵盤0鍵 
VK_NUMPAD1      0x61        小鍵盤1鍵 
VK_NUMPAD2      0x62        小鍵盤2鍵 
...             ...         ... 
VK_NUMPAD9      0x69        小鍵盤9鍵 
VK_MULTIPLY     0x6A        小鍵盤乘鍵* 
VK_ADD          0x6B        小鍵盤加鍵+ 
VK_SUBTRACT     0x6D        小鍵盤減鍵- 
VK_DIVIDE       0x6F        小鍵盤除鍵/ 
VK_DECIMAL      0x6E        小鍵盤點鍵. 
VK_F1           0x70        F1鍵 
VK_F2           0x71        F2鍵 
...             ...         ... 
VK_F12          0x7B        F12鍵 
VK_F13          0x7C        F13鍵      注:別問我,我也不知道什么電腦有這么多鍵 
...             ...         ... 
VK_F24          0x87        F24鍵 
VK_OEM_1        0xBA        ;:鍵 
VK_OEM_2        0xBF        /?鍵 
VK_OEM_3        0xC0        ·~鍵 
VK_OEM_4        0xDB        [{鍵 
VK_OEM_5        0xDC        \|鍵 
VK_OEM_6        0xDD        ]}鍵 
VK_OEM_7        0xDE        '"鍵 
VK_OEM_PLUS     0xBB        =+鍵 
VK_OEM_MINUS    0xBD        -_鍵 
VK_OEM_COMMA    0xBC        ,<鍵 
VK_OEM_PERIOD   0xBE        .>鍵  
*/  

       以上是部分常用的微軟虛擬鍵盤碼表,想要知道更詳細的,請參見MSDN。其中各個虛擬鍵的具體使用情況根據各人編譯器或IDE等的不同而有所差異。下面是一個實現按Esc鍵就輸出Esc的樣例程序:

[cpp] view plain copy  
#include <stdio.h>  
#include <stdlib.h>  
#include <windows.h>  
#include <conio.h>  
#define true 1  
#define false 0  
  
int main()  
{  
    HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);      //獲得標准輸入設備句柄  
    INPUT_RECORD keyrec;        //定義輸入事件結構體  
    DWORD res;      //定義返回記錄  
    for (;;)  
    {  
        ReadConsoleInput(handle_in, &keyrec, 1, &res);      //讀取輸入事件  
        if (keyrec.EventType == KEY_EVENT)      //如果當前事件是鍵盤事件  
        {  
            if (keyrec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE) //當前事件的虛擬鍵為Esc鍵  
            {  
                printf("Esc ");  
            }  
        }  
    }  
    return 0;  
}  

       在上面的樣例程序中,當你按下Esc鍵后又馬上釋放,程序會輸出兩次Esc,因為有兩次事件的虛擬鍵代碼都是Esc鍵的代碼,一次是按下,一次是釋放。如果要實現按下鍵后出現反應,釋放不出現反應,那么將上例程序中第18行代碼的條件改成

[cpp] view plain copy  
if (keyrec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE  
    && keyrec.Event.KeyEvent.bKeyDown == true)     //表示當前為鍵按下而不是鍵釋放  
就行了。


       根據控制鍵的狀態我們可以實現不同的狀態輸出不同的值以及組合鍵的實現,下面的樣例程序在大寫鎖定打開時輸入A鍵則輸出大寫字母A,否則輸出小寫字母a。而在Shift鍵被按下的狀態是則輸出Shift+A以及Shift+a。樣例程序如下

[cpp] view plain copy  
#include <stdio.h>  
#include <stdlib.h>  
#include <windows.h>  
#include <conio.h>  
#define true 1  
#define false 0  
  
int main()  
{  
    HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);      //獲得標准輸入設備句柄  
    INPUT_RECORD keyrec;        //定義輸入事件結構體  
    DWORD res;      //定義返回記錄  
    for (;;)  
    {  
        ReadConsoleInput(handle_in, &keyrec, 1, &res);      //讀取輸入事件  
        if (keyrec.EventType == KEY_EVENT)      //如果當前事件是鍵盤事件  
        {  
            if (keyrec.Event.KeyEvent.wVirtualKeyCode == 'A'  
                && keyrec.Event.KeyEvent.bKeyDown == true)   //當按下字母A鍵時  
            {  
                if (keyrec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)    //Shift鍵為按下狀態  
                {  
                    printf("Shift+");  
                }  
                if (keyrec.Event.KeyEvent.dwControlKeyState & CAPSLOCK_ON)  //大寫鎖定為打開狀態  
                {  
                    printf("A ");  
                }  
                else        //大寫鎖定關閉狀態  
                {  
                    printf("a ");  
                }  
            }  
        }  
    }  
    return 0;  
}  

       由上例需要了解到的是,各個控制鍵狀態的的確定並不是使用等於符號==而是按位與&運算符,因為在同一時刻可能有多種控制鍵狀態值,比如各種鎖定都被打開且各種控制鍵也被同時按下。使用&運算符則顯得尤其高明,方便查詢各個控制鍵的狀態而不使之出現沖突。呵呵,不服不行啊,感慨一下,還是要多學習一下別人高明的地方,比如靈活運用位運算符實現各種功能等等······

 

八:鼠標事件


上次講的是鍵盤事件,這次我們介紹鼠標事件。下面先介紹下鼠標事件的結構體以及相關信息。

[cpp] view plain copy  
typedef struct _MOUSE_EVENT_RECORD      //鼠標事件結構體  
{  
    COORD dwMousePosition;      //當前鼠標在控制台窗口緩沖區的位置  
    DWORD dwButtonState;        //鼠標按鍵的狀態  
    DWORD dwControlKeyState;    //控制鍵狀態  
    DWORD dwEventFlags;         //鼠標事件類型  
} MOUSE_EVENT_RECORD;  
/* 
其中鼠標按鍵狀態dwButtonState可能的值有 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
FROM_LEFT_1ST_BUTTON_PRESSED        最左邊的鼠標鍵被按下      一般來說就是鼠標左鍵 
FROM_LEFT_2ND_BUTTON_PRESSED        左起第二個鼠標鍵被按下    一般來說是鼠標中鍵,就是滾輪鍵 
FROM_LEFT_3RD_BUTTON_PRESSED        左起第三個鼠標鍵被按下 
FROM_LEFT_4TH_BUTTON_PRESSED        左起第四個鼠標鍵被按下 
RIGHTMOST_BUTTON_PRESSED            最右邊的鼠標鍵被按下      一般來說是鼠標右鍵 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
控制鍵狀態dwControlKeyState與鍵盤事件的一樣 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
ENHANCED_KEY        擴展鍵被按下  
LEFT_ALT_PRESSED    左Alt鍵被按下  
LEFT_CTRL_PRESSED   左Ctrl鍵被按下  
RIGHT_ALT_PRESSED   右Alt鍵被按下  
RIGHT_CTRL_PRESSED  右Ctrl鍵被按下  
NUMLOCK_ON          數字鎖定被打開  
SCROLLLOCK_ON       滾動鎖定被打開  
CAPSLOCK_ON         大寫鎖定被打開  
SHIFT_PRESSED       Shift鍵被按下 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
鼠標事件類型dwEventFlags有以下幾種 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
DOUBLE_CLICK            雙擊,第一擊只作為普通按鍵事件,第二擊才作為雙擊事件 
MOUSE_HWHEELED          水平鼠標滾輪移動 
MOUSE_MOVED             鼠標移動 
MOUSE_WHEELED           垂直鼠標滾輪移動 
0                       當鼠標有鍵被按下或者釋放 
*/  


       下面給一個樣例程序,實現在控制台窗口緩沖區的最下面一行顯示當前鼠標在緩沖區的坐標,單擊左鍵在當前鼠標位置輸出字母A,單擊右鍵則輸出字母B,雙擊任何鼠標鍵退出的功能。程序如下:

[cpp] view plain copy  
#include <stdio.h>  
#include <windows.h>  
#include <conio.h>  
  
HANDLE handle_in;  
HANDLE handle_out;  
CONSOLE_SCREEN_BUFFER_INFO csbi;        //定義窗口緩沖區信息結構體  
  
void DisplayMousePosition(COORD pos);   //顯示鼠標所在位置  
  
void gotoxy(int x, int y);  //將光標移到坐標為(x,y)的位置  
  
int main()  
{  
    handle_in = GetStdHandle(STD_INPUT_HANDLE);      //獲得標准輸入設備句柄  
    handle_out = GetStdHandle(STD_OUTPUT_HANDLE);    //獲得標准輸出設備句柄  
    INPUT_RECORD mouserec;      //定義輸入事件結構體  
    DWORD res;      //用於存儲讀取記錄  
    COORD pos;      //用於存儲鼠標當前位置  
    COORD size = {80, 25};  //窗口緩沖區大小  
    GetConsoleScreenBufferInfo(handle_out, &csbi);  //獲得窗口緩沖區信息  
    SetConsoleScreenBufferSize(handle_out, size);   //設置窗口緩沖區大小  
    for (;;)  
    {  
        ReadConsoleInput(handle_in, &mouserec, 1, &res);      //讀取輸入事件  
        pos = mouserec.Event.MouseEvent.dwMousePosition;    //獲得當前鼠標位置  
        gotoxy(0, 24);  //在第25行顯示鼠標位置  
        DisplayMousePosition(pos);      //顯示鼠標位置  
        if (mouserec.EventType == MOUSE_EVENT)    //如果當前為鼠標事件  
        {  
            gotoxy(pos.X, pos.Y);  
            //單擊鼠標左鍵,輸出字符A  
            if (mouserec.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)  
            {  
                putchar('A');  
            }  
            //單擊鼠標右鍵,輸出字符B  
            if (mouserec.Event.MouseEvent.dwButtonState == RIGHTMOST_BUTTON_PRESSED)  
            {  
                putchar('B');  
            }  
            //雙擊退出  
            if (mouserec.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK)  
            {  
                break;  
            }  
        }  
    }  
    CloseHandle(handle_out);  
    CloseHandle(handle_in);  
    return 0;  
}  
  
void DisplayMousePosition(COORD pos)  
{  
    COORD dis = {0, 24};        //在第24行顯示鼠標位置  
    WORD att = FOREGROUND_GREEN | FOREGROUND_INTENSITY; //文本屬性  
    GetConsoleScreenBufferInfo(handle_out, &csbi);  //獲得窗口緩沖區信息  
    printf("X = %3d, Y = %3d", (int)pos.X, (int)pos.Y);  
    FillConsoleOutputAttribute(handle_out, att, 16, dis, NULL);  //填充文本屬性  
    return;  
}  
  
void gotoxy(int x, int y)  
{  
    COORD pos = {x, y};  
    SetConsoleCursorPosition(handle_out, pos);  
}  

       附上用本程序寫的Hello world!的圖:

 

       注意:當使用system函數后鼠標事件無法正常發生。


————————————————
版權聲明:本文為CSDN博主「KKK_Kiral」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/liluo_2951121599/article/details/66474233


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM