CodeBlock下的人機交互界面設計
?
人機交互界面指的是計算機系統(tǒng)與用戶之間的接口。通過該接口,一方面,計算機系統(tǒng)向用戶輸出系統(tǒng)的運行狀態(tài)、運行控制和運行結果等方面信息;另一方面,用戶根據(jù)輸出信息向系統(tǒng)輸入相應的指令和數(shù)據(jù)等信息。
?
3.4.1 控制臺窗口和屏幕緩沖區(qū)
控制臺窗口是個二維平面空間,其坐標系統(tǒng)的原點(0, 0)設在窗口左上角,即窗口第一行第一列字符單元的位置。橫軸(X軸)的正向沿原點向右,與窗口的第一行重合,每刻度為一個字符寬度;縱軸(Y軸)的正向沿原點向下,與窗口的第一列重合,每刻度為一個字符高度。窗口中每個字符單元對應一個二維坐標。比如,第5行第32列字符單元的坐標為(31, 4)。如圖3.8所示。
圖3.8 控制臺窗口和屏幕緩沖區(qū)關系示意圖
?
屏幕緩沖區(qū)是個二維數(shù)組,邏輯上可看作一個二維平面空間。數(shù)組第一個元素的下標[0][0]對應此平面空間坐標系統(tǒng)的原點(0, 0),數(shù)組第1維的下標對應坐標系統(tǒng)的縱坐標(Y坐標),第2維下標對應坐標系統(tǒng)的橫坐標(X坐標)。屏幕緩沖區(qū)存放著M行N列字符單元的信息,M和N的大小由系統(tǒng)設置,并可以進行修改。
每個字符單元信息用一個CHAR_INFO結構類型的數(shù)據(jù)來表示,結構成員Char存放字符的碼值(Unicode碼或ASCII碼,取決于系統(tǒng)所采用的字符集),結構成員Attributes存放字符的屬性(字符顯示所用的前景色和背景色)。
操作系統(tǒng)以一定的頻率從屏幕緩沖區(qū)讀取字符單元信息,并顯示在控制臺窗口中。應用程序的輸出信息實際上輸出到了屏幕緩沖區(qū),由此改變了控制臺窗口所顯示的內(nèi)容。初始狀態(tài)下,屏幕緩沖區(qū)坐標系統(tǒng)與控制臺窗口坐標系統(tǒng)重合,窗口中第m行第n列字符的碼值和顏色值存放在屏幕緩沖區(qū)二維數(shù)組中下標為[m-1][n-1]的元素中。利用控制臺函數(shù)可以改變這兩個坐標系統(tǒng)的對應關系,實現(xiàn)特殊的顯示效果。圖3.8表示了控制臺窗口和屏幕緩沖區(qū)的相互關系。
一個控制臺可擁有多個屏幕緩沖區(qū),但只有處于激活狀態(tài)的屏幕緩沖區(qū)內(nèi)容顯示在控制臺窗口中。操作系統(tǒng)在為進程創(chuàng)建控制臺的同時會創(chuàng)建一個屏幕緩沖區(qū)。
進程可調(diào)用函數(shù)CreateConsoleScreenBuffer為其控制臺創(chuàng)建另外的屏幕緩沖區(qū)。
調(diào)用函數(shù)SetConsoleActiveScreenBuffer可以將某個已有的屏幕緩沖區(qū)置為激活狀態(tài),使其內(nèi)容顯示在屏幕窗口中。
不管是否處于激活狀態(tài),屏幕緩沖區(qū)都可以通過句柄來進行讀寫操作,只不過激活狀態(tài)下屏幕緩沖區(qū)的內(nèi)容可以看到,非激活狀態(tài)下看不到而已。
屏幕緩沖區(qū)相關的多個屬性可以獨立進行設置。激活的屏幕緩沖區(qū)屬性值的變化能在控制臺窗口中產(chǎn)生奇妙的外觀效果。屏幕緩沖區(qū)相關的屬性包括:
l?屏幕緩沖區(qū)大小,以字符行和列為單位;
l?文本屬性(文本信息顯示的前景色和背景色);
l?窗口大小和定位(控制臺屏幕緩沖區(qū)在控制臺窗口中顯示時所處的矩形區(qū)域);
l?光標位置、外觀和是否可見;
l?輸出模式(控制字符的輸出處理和行末換行處理)。
屏幕緩沖區(qū)在創(chuàng)建時,它所包含的字符內(nèi)容初始化為空格,光標設為可見并定位在緩沖區(qū)原點(0, 0),而窗口的原點(左上角)與緩沖區(qū)原點置為重合。控制臺屏幕緩沖區(qū)的大小、窗口的大小、文本屬性和光標的外觀取決于用戶或系統(tǒng)的缺省設置。
為獲取控制臺屏幕緩沖區(qū)各種相關屬性的當前值,可分別調(diào)用函數(shù):
GetConsoleScreenBufferInfo;
GetConsoleCursorInfo;
GetConsoleMode。
屏幕緩沖區(qū)光標信息用CONSOLE_CURSOR_INFO結構類型的數(shù)據(jù)表示,成員bVisible表示光標是否可見,成員dwSize表示光標外觀大小,取值范圍為1~100。光標可見時,dwSize的取值從100變?yōu)?,光標外觀大小從充滿整個字符單元變?yōu)槌霈F(xiàn)在單元底部的一條水平線。調(diào)用函數(shù)GetConsoleCursorInfo和SetConsoleCursorInfo分別可以獲得和設置光標屬性值。
由高級控制臺I/O函數(shù)(如getchar,putchar,printf,scanf等)輸出的字符將輸出在光標當前位置,同時光標移動到下一個字符輸出位置。
調(diào)用函數(shù):
GetConsoleScreenBufferInfo和SetConsoleCursorPosition,
分別可以獲得和設置光標在屏幕緩沖區(qū)坐標系統(tǒng)中的當前位置,由此可以控制高級I/O函數(shù)輸出或回顯字符的位置。
字符屬性分為兩類:顏色屬性和DBCS(Double-Byte Character Set,雙字節(jié)字符集)屬性。表3.14中的符號常量在wincon.h頭文件中進行定義。
表3.14 字符屬性符號常量表
屬性
含義
FOREGROUND_BLUE
文本顏色包含藍色
FOREGROUND_GREEN
文本顏色包含綠色
FOREGROUND_RED
文本顏色包含紅色
FOREGROUND_INTENSITY
文本顏色加亮
BACKGROUND_BLUE
背景含藍色
BACKGROUND_GREEN
背景含綠色
BACKGROUND_RED
背景含紅色
BACKGROUND_INTENSITY
背景加亮
COMMON_LVB_LEADING_BYTE
首字節(jié)
COMMON_LVB_TRAILING_BYTE
末字節(jié)
COMMON_LVB_GRID_HORIZONTAL
首行
COMMON_LVB_GRID_LVERTICAL
左列
COMMON_LVB_GRID_RVERTICAL
右列
COMMON_LVB_REVERSE_VIDEO
翻轉前景及背景屬性
COMMON_LVB_UNDERSCORE
下劃線
?
前綴為FOREGROUND的常量值指定文本顏色(文本的前景色)。前綴為BACKGROUND的常量值指定用于填充字符單元背景的顏色。其他常量值用于DBCS屬性。
應用程序可以將前景色和背景色常量值組合起來,獲得不同顏色。例如,下面顏色組合的效果為藍色背景上的亮青色文本。
FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE
配色問題可以由實驗的輸出試驗確定!
如果不指定背景顏色值,那么背景為黑色,而不指定前景顏色值,文本為黑色。例如,下面顏色組合將產(chǎn)生白色背景上的黑色文本效果。
BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED
每個屏幕緩沖區(qū)字符單元儲存了在“畫”該單元的文本(前景)和背景時所使用的顏色屬性值。應用程序可以分別設置每個字符單元的顏色值,并將顏色值存儲在每個單元CHAR_INFO結構類型數(shù)據(jù)的Attributes成員中。
?
3.4.2 在屏幕上指定位置輸出信息
有多種方法在屏幕指定位置輸出帶屬性的字符串信息,這里介紹其中四種基本方法。
(1) 用標準輸出函數(shù)(putchar, printf, puts等)輸出字符串信息;
//設置光標位置
SetConsoleCursorPosition(output_handle, new_pos);
//輸出字符串string
printf(“%s”, string);
?
//在字符串輸出位置填充指定的文本屬性
FillConsoleOutputAttribute(output_handle, new_attributes, strlen(string), new_pos, NULL);
其中output_handle是屏幕緩沖區(qū)句柄,new_pos為COORD類型的變量,存放指定的光標位置坐標,new_attributes為WORD類型的變量,存放指定的文本屬性值,string是字符數(shù)組,存放被輸出的字符串。
?
(2) 用函數(shù)WriteConsole輸出字符串信息;
//設置光標位置
SetConsoleCursorPosition?(output_handle, new_pos);
//設置文本屬性
SetConsoleTextAttribute(output_handle, new_attributes);
//輸出字符串
WriteConsole(output_handle, string, strlen(string), NULL, NULL);
變量的含義同上。
?
(3) 用函數(shù)WriteConsoleOutputCharacter輸出字符串信息;
//在指定位置填充與所輸出字符串等長的文本屬性值
FillConsoleOutputAttribute(output_handle, new_attributes, strlen(string), new_pos, NULL);
//在該指定位置輸出字符串
WriteConsoleOutputCharacter(output_handle, string, strlen(string), new_pos, NULL);
?
(4) 用函數(shù)WriteConsoleOutput輸出字符串信息;
CHAR_INFO * lpBuffer;
COORD?pos = {0, 0};
COORD?size = { strlen(string), 1};
SMALL_RECT area?=?{new_pos.X, new_pos.Y, new_pos.X+strlen(string)-1, new_pos.Y};
lpBuffer = (CHAR_INFO *)malloc(size.X * size.Y * sizeof(CHAR_INFO));
for(i=0; i<strlen(string); i++) {
????lpBuffer->Char.AsciiChar?= string[i];
????lpBuffer->Attributes?= new_attributes;
}
WriteConsoleOutput(output_handle, lpBuffer, size, pos, &area);
free(&area);
?
演示并解釋例3.1 menu_ex3_1
?
這種方法使用起來相對復雜一些?;舅枷胧菍⑤敵鲂畔斪饕粋€矩形字符信息塊,設置矩形塊的大小size,矩形塊在窗口中的輸出位置area,將矩形塊內(nèi)字符信息存放在一個動態(tài)存儲緩沖區(qū)lpBuffer內(nèi),字符信息包含了字符的碼值和顏色屬性,最后調(diào)用函數(shù)WriteConsoleOutput將lpBuffer中字符塊信息寫到控制臺屏幕緩沖區(qū)指定位置。
?
3.4.3 彈出窗口的設計
屏幕窗口是個有限的信息顯示區(qū)域。在文本字符界面下,控制臺窗口的大小通常設為80個字符的寬度和25行字符的高度,即每屏可以顯示2000個字符。
彈出窗口設計的基本思路是:
1、確定輸出信息的屏幕位置和大??;
2、將新窗口彈出后所要覆蓋的屏幕區(qū)域字符信息讀入到一塊內(nèi)存緩沖區(qū);
3、在新窗口內(nèi)輸出信息,模擬“彈出”效果。
4、彈出窗口內(nèi)的操作完成后,把保存在內(nèi)存緩沖區(qū)的字符信息寫到其原來所在的屏幕位置,彈出窗口消失,屏幕恢復為窗口彈出之前的外觀。
?
演示并解釋例3.1 menu_ex3_2
?
多層彈出窗口自學
按照這一思路,可以實現(xiàn)多層彈出窗口。窗口的多層彈出和逐層關閉,給屏幕信息的維護帶來了復雜性。為了便于處理,我們用鏈表來模擬堆棧,實現(xiàn)彈出窗口的棧式管理。
彈出窗口棧式管理用到以下結構類型。
????typedef struct layer_node {
????????char LayerNo; ????????????//彈出窗口層數(shù)
????????SMALL_RECT rcArea; ????//彈出窗口區(qū)域坐標
????????CHAR_INFO *pContent; ??//彈出窗口區(qū)域字符單元原信息存儲緩沖區(qū)
????????char *pScrAtt; ???????????//彈出窗口區(qū)域字符單元原屬性值存儲緩沖區(qū)
????????struct layer_node *next;?????//下一結點的地址
????} LAYER_NODE;
利用這種結構類型的數(shù)據(jù)可以模擬出如圖3.10所示的堆棧,對彈出窗口信息進行管理。
圖3.10 彈出窗口信息堆棧
?
LAYER_NODE結構的5個成員分別表示了彈出窗口相關信息。LayerNo表示當前彈出窗口的層數(shù);rcArea表示當前彈出窗口矩形區(qū)域的位置和大小;pContent指向的動態(tài)存儲區(qū)存放被彈出窗口所覆蓋區(qū)域的原字符單元信息,用于當前彈出窗口關閉后恢復原屏幕窗口信息;pScrAtt指向的動態(tài)存儲區(qū)存放內(nèi)容的用途與輸入處理相關,將在下面的輸入處理中進行介紹;next存放下層彈出窗口相關信息的地址。
堆棧棧頂由LAYER_NODE結構指針TopLayer來指示,初值為NULL。系統(tǒng)界面初始化完成之后,屏幕窗口看作第一層彈出窗口,窗口相關信息用LAYER_NODE結構類型的動態(tài)存儲區(qū)存放后入棧,結構指針TopLayer指向棧頂;以后每彈出一層窗口,就執(zhí)行一次入棧操作。關閉彈出窗口時,用TopLayer指向的LAYER_NODE結構類型數(shù)據(jù)恢復被覆蓋的屏幕區(qū)域,將TopLayer指向下一層結點,釋放用過的動態(tài)存儲區(qū),完成出棧操作。
?
3.4.4 鍵盤和鼠標輸入信息的獲取
ReadConsoleInput函數(shù)原型為:
BOOL WINAPI ReadConsoleInput(HANDLE hConsoleInput, PINPUT_RECORD
lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead);
功能:用來從控制臺輸入緩沖區(qū)讀取輸入數(shù)據(jù),并將讀出數(shù)據(jù)從輸入緩沖區(qū)刪除掉。
函數(shù)ReadConsoleInput被調(diào)用時,如果緩沖區(qū)中沒有輸入數(shù)據(jù),函數(shù)將等待下去,直到讀到至少一條記錄后返回。讀到的記錄存放在參數(shù)lpBuffer所指向的內(nèi)存單元。
記錄用INPUT_RECORD結構類型的數(shù)據(jù)來表示;
成員EventType表示事件的類型;
成員Event是聯(lián)合類型,存放事件的具體內(nèi)容。
編程時,需要對EventType的值為KEY_EVENT(鍵盤輸入)或MOUSE_EVENT(鼠標輸入)的兩類事件進行處理。
當EventType的值為KEY_EVENT時,Event的聯(lián)合成員KeyEvent存放了按鍵相關信息。KeyEvent是KEY_EVENT_RECORD結構類型,其成員bKeyDown表明鍵是被按下(TRUE)還是被釋放(FALSE),成員wRepeatCount表明按鍵重復的次數(shù),成員wVirtualKeyCode存放按鍵的虛擬鍵碼,成員wVirtualScanCode存放按鍵的虛擬掃描碼,成員uChar存放按鍵的ASCII碼或Unicode碼(取決于系統(tǒng)所采用的字符集),成員dwControlKeyState表示有哪些控制鍵被同時按下。我們每按一次鍵會產(chǎn)生兩個事件記錄,一個記錄表示鍵被按下,另一條記錄表示鍵被釋放。常用鍵的各種碼值參見附錄。
當EventType的值為MOUSE_EVENT時,Event的聯(lián)合成員MouseEvent存放了鼠標操作相關信息。MouseEvent是MOUSE_EVENT_RECORD結構類型,其成員dwMousePosition存放了鼠標操作時的坐標位置,表明鼠標處于窗口中的某行和某列的字符單元位置上,成員dwButtonState表明鼠標哪些按鈕被按下,取值可為下面符號常量之一或多個符號常量的組合值:
FROM_LEFT_1ST_BUTTON_PRESSED值為1,表示按下了鼠標最左邊按鈕;
RIGHTMOST_BUTTON_PRESSED值為2,表示按下了鼠標最右邊按鈕;
FROM_LEFT_2ND_BUTTON_PRESSED值為4,表示按下了鼠標左起第二個按鈕;
FROM_LEFT_3RD_BUTTON_PRESSED值為8,表示按下了鼠標左起第三個按鈕;
FROM_LEFT_4TH_BUTTON_PRESSED值為16,表示按下了鼠標左起第四個按鈕。
成員dwControlKeyState表示在鼠標事件發(fā)生時有哪些控制鍵被同時按下,成員dwEventFlags表示鼠標事件的具體類型,取值為以下符號常量:
MOUSE_MOVED值為1,表示鼠標移動事件;
DOUBLE_CLICK值為2,表示鼠標雙擊事件;
MOUSE_WHEELED值為4,表示鼠標滾輪滾動事件。
?
3.4.5 輸入處理(略)
?(1) 鍵盤輸入處理
按照表3.15進行處理,對其他按鍵不予響應。
表3.15 主界面下的鍵盤輸入處理設計
按鍵
系統(tǒng)響應
F1
執(zhí)行幫助菜單下的幫助主題子菜單對應功能模塊
Alt+X
執(zhí)行文件菜單下的退出系統(tǒng)子菜單對應功能模塊
Alt+F
清除當前選中菜單項標記,標記文件菜單項并彈出文件菜單的子菜單
Alt+M
清除當前選中菜單項標記,標記數(shù)據(jù)維護菜單項并彈出數(shù)據(jù)維護菜單的子菜單
Alt+Q
清除當前選中菜單項標記,標記數(shù)據(jù)查詢菜單項并彈出數(shù)據(jù)查詢菜單的子菜單
Alt+S
清除當前選中菜單項標記,標記數(shù)據(jù)統(tǒng)計菜單項并彈出數(shù)據(jù)統(tǒng)計菜單的子菜單
Alt+H
清除當前選中菜單項標記,標記幫助菜單項并彈出幫助菜單的子菜單
向左←
清除當前選中菜單項標記,標記左側菜單項
向右→
清除當前選中菜單項標記,標記右側菜單項
向下↓
彈出當前菜單項的子菜單
f或F
清除當前選中菜單項標記,標記文件菜單項并彈出文件菜單的子菜單
m或M
清除當前選中菜單項標記,標記數(shù)據(jù)維護菜單項并彈出數(shù)據(jù)維護菜單的子菜單
q或Q
清除當前選中菜單項標記,標記數(shù)據(jù)查詢菜單項并彈出數(shù)據(jù)查詢菜單的子菜單
s或S
清除當前選中菜單項標記,標記數(shù)據(jù)統(tǒng)計菜單項并彈出數(shù)據(jù)統(tǒng)計菜單的子菜單
h或H
清除當前選中菜單項標記,標記幫助菜單項并彈出幫助菜單的子菜單
回車
彈出當前菜單項的子菜單
?
鍵盤輸入處理時,要考慮輸入處理的優(yōu)先級,應先響應快捷鍵,再響應組合鍵,最后響應單鍵。以Alt組合鍵為例,判斷組合鍵的方法為:
ReadConsoleInput(hIn, &inRec, 1, &res);????//從輸入緩沖區(qū)讀取一條輸入記錄
if (inRec.EventType == KEY_EVENT && inRec.Event.KeyEvent.bKeyDown) {
//輸入事件類別為KEY_EVENT,且事件由鍵被按下所觸發(fā)
????vkc = inRec.Event.KeyEvent.wVirtualKeyCode; ???//提取虛擬鍵碼
????asc = inRec.Event.KeyEvent.uChar.AsciiChar; ????//提取ASCII碼
????if (inRec.Event.KeyEvent.dwControlKeyState?????//如果左或右Alt鍵被按下
??????& (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) {
????????…… ?//進一步判斷與Alt組合的另一個鍵,并做響應處理
????}
????…… ?//非Alt組合鍵的響應處理
}
常用控制鍵所對應的符號常量在wincon.h頭文件中進行了定義,如表3.16所示。
表3.16 控制鍵對應符號常量表
鍵名
符號常量
值
鍵名
符號常量
值
右Alt
RIGHT_ALT_PRESSED
1
左Alt
LEFT_ALT_PRESSED
2
右Ctrl
RIGHT_CTRL_PRESSED
4
左Ctrl
LEFT_CTRL_PRESSED
8
左右Shift
SHIFT_PRESSED
16
數(shù)字鎖定
NUMLOCK_ON
32
屏幕鎖定
SCROLLLOCK_ON
64
大寫鎖定
CAPSLOCK_ON
128
?
(2) 鼠標輸入處理
熱區(qū)本是網(wǎng)頁設計中用到的一個概念,是指網(wǎng)頁上建有鏈接的區(qū)域。我們將熱區(qū)這個概念借用到程序設計中人機交互界面的設計上來,用來指界面上需要對鼠標事件(包括鼠標移動、滾輪轉動、雙擊和任意一到多個鍵的按下)產(chǎn)生反應的區(qū)域。
基于這一思想,可用一個字符(8個二進制位)來表示窗口中某個字符單元的屬性。如圖3.12所示,這8個二進制位分為三段:0~1比特為A1,2~5比特為A2,6~7比特為A3。A1的取值范圍為0~3,用來表示字符單元的“高度”,即該字符單元上彈出窗口的層數(shù),0表示字符單元處于系統(tǒng)主界面層,沒有彈出窗口覆蓋該字符單元,這樣彈出窗口的層數(shù)最多可到3層;A2取值范圍為0~15,用來表示字符單元的熱區(qū)編號,0表示字符單元不屬于熱區(qū),這樣同一層上的熱區(qū)最多可為15個;A3取值范圍為0~3,在A2取值不為0時用來表示字符單元的熱區(qū)類型,0代表按鈕類型,1代表輸入框類型,2代表下拉選框類型,3保留備用。不同類型的熱區(qū)被鼠標擊中時,系統(tǒng)可以分別進行處理。本課程設計中,字符單元的屬性用8個二進制位來存放,剛好夠用。在其他應用程序開發(fā)中,如果彈出菜單超過3層,或某層窗口中熱區(qū)超過15個,或熱區(qū)類型超過4類,可以考慮用16個二進制位來存放字符單元屬性。
7
6
5
4
3
2
1
0
?
?
?
?
?
?
?
?
A3
A2
A1
圖3.12 字符單元屬性的表示
?
前面在介紹彈出窗口設計時,彈出窗口的棧式管理用到以下結構類型:
????typedef struct layer_node {
????????char LayerNo; ????????????//彈出窗口層數(shù)
????????SMALL_RECT rcArea; ????//彈出窗口區(qū)域坐標
????????CHAR_INFO *pContent; ??//彈出窗口區(qū)域字符單元原信息存儲緩沖區(qū)
????????char *pScrAtt; ???????????//彈出窗口區(qū)域字符單元原屬性值存儲緩沖區(qū)
????????struct layer_node *next;?????//下一結點的地址
????} LAYER_NODE;
其中,結構成員pScrAtt用來指向一個動態(tài)存儲區(qū),該存儲區(qū)存放被彈出窗口所覆蓋字符單元的原先屬性值,在彈出窗口關閉時用于恢復窗口彈出前屏幕字符單元的屬性。
系統(tǒng)界面初始化完成之后,屏幕顯示系統(tǒng)的主界面(如圖3.9所示)。用一個字符數(shù)組ScrAtt存放屏幕上所有字符單元的屬性值,字符單元的坐標X和Y與存放其屬性值的數(shù)組元素下標n的關系為:
n =?Y × 屏幕緩沖區(qū)的寬度 + X
主界面中只有5個主菜單項顯示區(qū)域為熱區(qū),依次編號1~5,熱區(qū)類型為按鈕型。此后,如果有窗口彈出(彈出菜單也是彈出窗口),則將窗口所覆蓋區(qū)域字符單元的信息和屬性分別保存起來,執(zhí)行彈出窗口信息入棧操作,然后在彈出窗口區(qū)域輸出提示信息,將彈出窗口字符單元的屬性值寫入數(shù)組ScrAtt對應元素以設置熱區(qū)。鼠標輸入處理時,取鼠標所在字符單元的屬性值,根據(jù)字符單元的層數(shù)、熱區(qū)編號和熱區(qū)類型,結合鼠標事件類型做出相應處理。當彈出窗口關閉時,用所保存的彈出窗口區(qū)域字符單元原先的屬性值修改數(shù)組ScrAtt對應元素值,恢復窗口彈出前屏幕字符單元的屬性,最后執(zhí)行彈出窗口信息出棧操作。
?
3.4.6 菜單操作與系統(tǒng)功能函數(shù)的調(diào)用
按照概要設計,系統(tǒng)功能分為五個模塊,各模塊所包含的子模塊共有22個,分別用22個函數(shù)實現(xiàn)相應功能。其中,數(shù)據(jù)加載函數(shù)和界面初始化函數(shù)只在系統(tǒng)啟動時執(zhí)行一次,以后不再執(zhí)行。其余20個函數(shù)可以通過菜單操作或快捷鍵執(zhí)行相應功能。
例3.3?在圖3.9所示的主界面下,實現(xiàn)菜單操作與系統(tǒng)功能函數(shù)的調(diào)用。本例中給出了函數(shù)SysRun和函數(shù)ExeFunction的定義,分別用于菜單操作和系統(tǒng)功能函數(shù)的調(diào)用。例子中調(diào)用了例3.1和例3.2中的函數(shù),而其余函數(shù)的定義沒有給出。
#include "dorm.h"
?
void SysRun(?)
{
????INPUT_RECORD inRec;
????DWORD res;
????COORD pos = {0, 0};
????BOOL bRet = TRUE;
????int i, loc, num;
????int cNo, cAtt; ?????//cNo:字符單元層號, cAtt:字符單元屬性
????char vkc, asc; ?????//vkc:虛擬鍵代碼, asc:字符的ASCII碼值
?
????while (bRet) { ?// 循環(huán)
????????ReadConsoleInput(hIn, &inRec, 1, &res);
????????if (inRec.EventType == MOUSE_EVENT) {
????????????pos = inRec.Event.MouseEvent.dwMousePosition;?/*?pos 是坐標 */
????????????cNo = ScrAtt[pos.Y * ScrCol + pos.X] & 3;
????????????cAtt = ScrAtt[pos.Y * ScrCol + pos.X] >> 2;
????????????if (cNo == 0) {
????????????????if (cAtt > 0 && cAtt != SelMenu && TopLayer->LayerNo > 0) {
????????????????????PopOff();
????????????????????SelSMenu = 0;
????????????????????PopMenu(cAtt);
????????????????}
????????????}
????????????else if (cAtt > 0) {
????????????????TagSMenu(cAtt);
????????????}
?
????????????if (inRec.Event.MouseEvent.dwButtonState
????????????????== FROM_LEFT_1ST_BUTTON_PRESSED) {
????????????????if (cNo == 0) {
????????????????????if (cAtt > 0) {
????????????????????????PopMenu(cAtt);
????????????????????}
????????????????????else if (TopLayer->LayerNo > 0) {
????????????????????????PopOff();
????????????????????????SelSMenu = 0;
????????????????????}
????????????????}
????????????????else {
????????????????????if (cAtt > 0) {
????????????????????????PopOff();
????????????????????????SelSMenu = 0;
????????????????????????bRet = ExeFunction(SelMenu, cAtt);
????????????????????}
????????????????}
????????????}
????????????else if (inRec.Event.MouseEvent.dwButtonState
?????????????????????== RIGHTMOST_BUTTON_PRESSED) {
????????????????if (cNo == 0) {
????????????????????PopOff();
????????????????????SelSMenu = 0;
????????????????}
????????????}
????????}
????????else if (inRec.EventType == KEY_EVENT
?????????????????&& inRec.Event.KeyEvent.bKeyDown) {
????????????vkc = inRec.Event.KeyEvent.wVirtualKeyCode;
????????????asc = inRec.Event.KeyEvent.uChar.AsciiChar;
????????????//系統(tǒng)快捷鍵的處理
????????????if (vkc == 112) { ???//F1鍵
????????????????if (TopLayer->LayerNo != 0) {
????????????????????PopOff();
????????????????????SelSMenu = 0;
????????????????}
????????????????bRet = ExeFunction(5, 1); ?//F1幫助主題
????????????}
????????????else if (inRec.Event.KeyEvent.dwControlKeyState
?????????????????????& (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) {
????????????????switch (vkc) { ?//組合鍵Alt+字母
????????????????????case 88: if (TopLayer->LayerNo != 0) {
?????????????????????????????????PopOff();
?????????????????????????????????SelSMenu = 0;
?????????????????????????????}
?????????????????????????????bRet = ExeFunction(1,4); break; ?//Alt+X 退出
????????????????????case 70: PopMenu(1); break; ?//Alt+F
????????????????????case 77: PopMenu(2); break; ?//Alt+M
????????????????????case 81: PopMenu(3); break; ?//Alt+Q
????????????????????case 83: PopMenu(4); break; ?//Alt+S
????????????????????case 72: PopMenu(5); break; ?//Alt+H
????????????????}
????????????}
????????????else if (asc == 0) { ?//方向鍵的處理
????????????????if (TopLayer->LayerNo == 0) { ?//未彈出子菜單時
????????????????????switch (vkc) { ?//方向鍵(左、右、下)的處理
????????????????????????case 37: SelMenu--;
?????????????????????????????????if (SelMenu == 0) {
?????????????????????????????????????SelMenu = 5;
?????????????????????????????????}
?????????????????????????????????TagMenu(SelMenu); break;
????????????????????????case 39: SelMenu++;
?????????????????????????????????if (SelMenu == 6) {
?????????????????????????????????????SelMenu = 1;
?????????????????????????????????}
?????????????????????????????????TagMenu(SelMenu); break;
????????????????????????case 40: PopMenu(SelMenu);
?????????????????????????????????TagSMenu(1); break;
????????????????????}
????????????????}
????????????????else { ?//已彈出子菜單時
????????????????????for (loc=0,i=1; i<SelMenu; i++) {
????????????????????????loc += cSMenu[i-1];
????????????????????} ?//找到子菜單第一項在子菜單數(shù)組中的位置(下標)
????????????????????switch (vkc) { ?//方向鍵(左、右、上、下)的處理
????????????????????????case 37: SelMenu--;
?????????????????????????????????if (SelMenu < 1) {
?????????????????????????????????????SelMenu = 5;
?????????????????????????????????}
?????????????????????????????????TagMenu(SelMenu); PopOff();
?????????????????????????????????PopMenu(SelMenu); TagSMenu(1);
?????????????????????????????????break;
????????????????????????case 38: num = SelSMenu - 1;
?????????????????????????????????if (num < 1) {
?????????????????????????????????????num = cSMenu[SelMenu-1];
?????????????????????????????????}
?????????????????????????????????if (strlen(SMenu[loc+num-1]) == 0) {
?????????????????????????????????????num--;
?????????????????????????????????}
?????????????????????????????????TagSMenu(num); break;
????????????????????????case 39: SelMenu++;
?????????????????????????????????if (SelMenu > 5) {
?????????????????????????????????????SelMenu = 1;
?????????????????????????????????}
?????????????????????????????????TagMenu(SelMenu); PopOff();
?????????????????????????????????PopMenu(SelMenu); TagSMenu(1);
?????????????????????????????????break;
????????????????????????case 40: num = SelSMenu + 1;
?????????????????????????????????if (num > cSMenu[SelMenu-1]) {
?????????????????????????????????????num = 1;
?????????????????????????????????}
?????????????????????????????????if (strlen(SMenu[loc+num-1]) == 0) {
?????????????????????????????????????num++;
?????????????????????????????????}
?????????????????????????????????TagSMenu(num); break;
????????????????????}
????????????????}
????????????}
????????????else if ((asc-vkc == 0) || (asc-vkc == 32)){ ?//按下普通鍵
????????????????if (TopLayer->LayerNo == 0) { ?//未彈出子菜單時
????????????????????switch (vkc) {
????????????????????????case 70: PopMenu(1); break; ?//f或F
????????????????????????case 77: PopMenu(2); break; ?//m或M
????????????????????????case 81: PopMenu(3); break; ?//q或Q
????????????????????????case 83: PopMenu(4); break; ?//s或S
????????????????????????case 72: PopMenu(5); break; ?//h或H
????????????????????????case 13: PopMenu(SelMenu); ??//回車
?????????????????????????????????TagSMenu(1); break;
????????????????????}
????????????????}
????????????????else { ?//已彈出子菜單時的鍵盤輸入處理
????????????????????if (vkc == 27) { ?//按下ESC鍵時, 關閉子菜單
????????????????????????PopOff();
????????????????????????SelSMenu = 0;
????????????????????}
????????????????????else if(vkc == 13) { //|| vkc == 32
????????????????????????num = SelSMenu;
????????????????????????PopOff();
????????????????????????SelSMenu = 0;
????????????????????????bRet = ExeFunction(SelMenu, num);
????????????????????}
????????????????????else {
????????????????????????for (loc=0,i=1; i<SelMenu; i++) {
????????????????????????????loc += cSMenu[i-1];
????????????????????????}
????????????????????????for (i=loc; i<loc+cSMenu[SelMenu-1]; i++) {
????????????????????????????if (strlen(SMenu[i])>0 && vkc==SMenu[i][1]) {
????????????????????????????????PopOff();
????????????????????????????????SelSMenu = 0;
????????????????????????????????bRet = ExeFunction(SelMenu, i-loc+1);
????????????????????????????}
????????????????????????}
????????????????????}
????????????????}
????????????}
????????}
????}
}
?
BOOL ExeFunction(int m, int s)
{
????BOOL bRet = TRUE;
????BOOL (*pFunction[cSMenu[0]+cSMenu[1]+cSMenu[2]+cSMenu[3]+cSMenu[4]])(void);
????int i, loc;
?
????//pFunction
????pFunction[0] = DataSave;
????pFunction[1] = DataBackup;
????pFunction[2] = DataRestore;
????pFunction[3] = SySexit;
????pFunction[4] = MaintainSex;
????pFunction[5] = MaintainType;
????pFunction[6] = NULL;
????pFunction[7] = MaintainDorm;
????pFunction[8] = MaintainStu;
????pFunction[9] = MaintainCharge;
????pFunction[10] = QuerySex;
????pFunction[11] = QueryType;
????pFunction[12] = NULL;
????pFunction[13] = QueryDorm;
????pFunction[14] = QueryStu;
????pFunction[15] = QueryCharge;
????pFunction[16] = StatIn;
????pFunction[17] = StatType;
????pFunction[18] = StatCharge;
????pFunction[19] = StatUncharge;
????pFunction[20] = HelpTopic;
????pFunction[21] = NULL;
????pFunction[22] = AboutDorm;
?
????for (i=1,loc=0; i<m; i++) {
????????loc += cSMenu[i-1];
????}
????loc += s - 1;
?
????if (pFunction[loc] != NULL) {
????????bRet = (*pFunction[loc])();
????}
?
????return bRet;
}
?
演示并解釋例3.1 menu_ex3_2