codeblock下人机交互界面
设计
领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计
.
CodeBlock下的人机交互界面设计
人机交互界面指的是计算机系统与用户之间的接口。通过该接口,一方面,计算机系统向用户输出系统的运行状态、运行控制和运行结果等方面信息;另一方面,用户根据输出信息向系统输入相应的指令和数据等信息。
3.4.1 控制台窗口和屏幕缓冲区
控制台窗口是个二维平面空间,其坐标系统的原点(0, 0)设在窗口左上角,即窗口第一行第一列字符单元的位置。横轴(X轴)的正向沿原点向右,与窗口的第一行重合,每刻度为一个字符宽度;纵轴(Y轴)的正向沿原点向下,与窗口的第一列重合,每刻度为一个字符高度。窗口中每个字符单元对应一个二维坐标。比如,第5行第32列字符单元的坐标为(31, 4)。如图3.8所示。
窗口标题栏
第32列 内存映像
(0,0) X轴 BBBB„„„„„„(0,0) (0,1) (0,2) (0,N-1) 第5行 BBBB „„„„„„(1,0) (1,1) (1,2) (1,N-1)
(31,4) BBBB „„„„„„(2,0) (2,1) (2,2) (2,N-1)M行 „„„„„„„„„„„„„„„„„„„„„„„„„„„
BBBB „„(M-1,0) (M-1,1) (M-1,2) (M-1,N-1)控制台窗口区
Y轴
N列
显示器屏幕区
激活的屏幕缓冲区
非激活的屏幕缓冲区
图3.8 控制台窗口和屏幕缓冲区关系示意图
屏幕缓冲区是个二维数组,逻辑上可看作一个二维平面空间。数组第一个元素的下标[0][0]对应此平面空间坐标系统的原点(0, 0),数组第1维的下标对应坐标系统的纵坐标(Y坐标),第2维下标对应坐标系统的横坐标(X坐标)。屏幕缓冲区存放着M行N列字符单元的信息,M和N的大小由系统设置,并可以进行修改。每个字符单元信息用一个CHAR_INFO结构类型的数据来
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
示,结构成员Char存放字符的码值(Unicode码或ASCII码,取决于系统所采用的字符集),结构成员Attributes存放字符的属性(字符显示所用的前景色和背景色)。操作系统以一定的频率从屏幕缓冲区读取字符单元信息,并显示在控制台窗口中。应用程序的输出信息实际上输出到了屏幕缓冲区,由此改变了控制台窗口所显示的内容。初始状态下,屏幕缓冲区坐标系统与控制台窗口坐标系统重合,窗口中第m行第n列字符的码值和颜色值存放在屏幕缓冲区二维数组中下标为[m-1][n-1]的元素中。利用控制台函数可以改变这两个坐标系统的对应关系,实现特殊的显示效果。图3.8表示了控制台窗口和屏幕缓冲区的相互关系。
.
.
一个控制台可拥有多个屏幕缓冲区,但只有处于激活状态的屏幕缓冲区内容显示在控制台窗口中。操作系统在为进程创建控制台的同时会创建一个屏幕缓冲区。进程可调用函数CreateConsoleScreenBuffer为其控制台创建另外的屏幕缓冲区。调用函数SetConsoleActiveScreenBuffer可以将某个已有的屏幕缓冲区置为激活状态,使其内容显示在屏幕窗口中。不管是否处于激活状态,屏幕缓冲区都可以通过句柄来进行读写操作,只不过激活状态下屏幕缓冲区的内容可以看到,非激活状态下看不到而已。
屏幕缓冲区相关的多个属性可以独立进行设置。激活的屏幕缓冲区属性值的变化能在控制台窗口中产生奇妙的外观效果。屏幕缓冲区相关的属性包括:
, 屏幕缓冲区大小,以字符行和列为单位;
, 文本属性(文本信息显示的前景色和背景色);
, 窗口大小和定位(控制台屏幕缓冲区在控制台窗口中显示时所处的矩形区域);
, 光标位置、外观和是否可见;
, 输出模式(控制字符的输出处理和行末换行处理)。
屏幕缓冲区在创建时,它所包含的字符内容初始化为空格,光标设为可见并定位在缓冲区原点(0, 0),而窗口的原点(左上角)与缓冲区原点置为重合。控制台屏幕缓冲区的大小、窗口的大小、文本属性和光标的外观取决于用户或系统的缺省设置。
为获取控制台屏幕缓冲区各种相关属性的当前值,可分别调用函数:
GetConsoleScreenBufferInfo;
GetConsoleCursorInfo;
GetConsoleMode。
屏幕缓冲区光标信息用CONSOLE_CURSOR_INFO结构类型的数据表示,成员bVisible表示光标是否可见,成员dwSize表示光标外观大小,取值范围为1~100。光标可见时,dwSize的取值从100变为1,光标外观大小从充满整个字符单元变为出现在单元底部的一条水平线。调用函数GetConsoleCursorInfo和SetConsoleCursorInfo分别可以获得和设置光标属性值。
由高级控制台I/O函数(如getchar,putchar,printf,scanf等)输出的字符将输出在光标当前位置,同时光标移动到下一个字符输出位置。
调用函数:
GetConsoleScreenBufferInfo和SetConsoleCursorPosition,
分别可以获得和设置光标在屏幕缓冲区坐标系统中的当前位置,由此可以控制高级I/O函数输出或回显字符的位置。
字符属性分为两类:颜色属性和DBCS(Double-Byte Character Set,双字节字符集)属性。表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
末字节 COMMON_LVB_TRAILING_BYTE
.
.
首行 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
如果不指定背景颜色值,那么背景为黑色,而不指定前景颜色值,文本为黑色。例如,下面颜色组合将产生白色背景上的黑色文本效果。
BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED
每个屏幕缓冲区字符单元储存了在“画”该单元的文本(前景)和背景时所使用的颜色属性值。应用程序可以分别设置每个字符单元的颜色值,并将颜色值存储在每个单元CHAR_INFO结构类型数据的Attributes成员中。
屏幕缓冲区的当前文本属性对随后使用高级I/O函数输出或回显的字符产生作用。调用函数GetConsoleScreenBufferInfo可得到当前屏幕缓冲区的文本属性值,而调用函数SetConsoleTextAttribute可设置字符属性值。改变屏幕缓冲区文本属性不会对当前已经输出字符的颜色产生影响。同时文本属性不会对低级控制台I/O函数(如函数WriteConsoleOutput或WriteConsoleOutputCharacter)输出的字符产生影响,低级控制台I/O函数输出字符的颜色取决于输出位置上的字符属性值。
3.4.2 在屏幕上指定位置输出信息
有多种方法在屏幕指定位置输出带属性的字符串信息,这里介绍其中四种基本方法。
(1) 用
标准
excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载
输出函数(putchar, printf, puts等)输出字符串信息;
//设置光标位置
SetConsoleCursorPosition(output_handle, new_pos);
//输出字符串string
printf(“%s”, string);
//在字符串输出位置填充指定的文本属性
FillConsoleOutputAttribute(output_handle, new_attributes, strlen(string), new_pos, NULL);
其中output_handle是屏幕缓冲区句柄,new_pos为COORD类型的变量,存放指定的光标位置坐标,new_attributes为WORD类型的变量,存放指定的文本属性值,string是字符数组,存放被输出的字符串。
(2) 用函数WriteConsole输出字符串信息;
//设置光标位置
SetConsoleCursorPosition (output_handle, new_pos);
//设置文本属性
SetConsoleTextAttribute(output_handle, new_attributes);
.
.
//输出字符串
WriteConsole(output_handle, string, strlen(string), NULL, NULL);
变量的含义同上。
(3) 用函数WriteConsoleOutputCharacter输出字符串信息;
//在指定位置填充与所输出字符串等长的文本属性值
FillConsoleOutputAttribute(output_handle, new_attributes, strlen(string), new_pos, NULL);
//在该指定位置输出字符串
WriteConsoleOutputCharacter(output_handle, string, strlen(string), new_pos, NULL);
(4) 用函数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
Char.AsciiChar = string[i];
lpBuffer->Attributes = new_attributes;
}
WriteConsoleOutput(output_handle, lpBuffer, size, pos, &area);
free(area);
这种方法使用起来相对复杂一些。基本思想是将输出信息当作一个矩形字符信息块,设
置矩形块的大小size,矩形块在窗口中的输出位置area,将矩形块内字符信息存放在一个动
态存储缓冲区lpBuffer内,字符信息包含了字符的码值和颜色属性,最后调用函数
WriteConsoleOutput将lpBuffer中字符块信息写到控制台屏幕缓冲区指定位置。
前两种方法所调用的函数printf和WriteConsole属于高级I/O函数,而后两种方法所
调用的函数WriteConsoleOutputCharacter和WriteConsoleOutput则是低级I/O函数。这四种
基本方法的区别在于字符串输出位置的设置、字符串颜色属性的设置和字符串内容的输出三
个方面。实际应用中,这些方法结合起来使用,可以获得特别的输出效果。
例3.1 文本菜单界面的初始化。
#include "dorm.h"
int main()
{
COORD size = {ScrCol, ScrRow}; //窗口缓冲区大小
WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY
| BACKGROUND_BLUE;
hOut = GetStdHandle(STD_OUTPUT_HANDLE); //获取标准输出设备句柄
hIn = GetStdHandle(STD_INPUT_HANDLE); //获取标准输入设备句柄
SetConsoleTitle(SysName); //设置窗口标题
.
.
SetConsoleScreenBufferSize(hOut, size); //设置窗口缓冲区大小80*25
SetConsoleTextAttribute(hOut, att); //设置黄色前景和蓝色背景
ClearScreen(); //清屏
ShowMenu();
return 0;
}
void ShowMenu()
{
CONSOLE_SCREEN_BUFFER_INFO bInfo;
CONSOLE_CURSOR_INFO lpCur;
COORD size;
COORD pos = {0, 0};
int i, j;
char ch;
GetConsoleScreenBufferInfo( hOut, &bInfo );
size.X = bInfo.dwSize.X;
size.Y = 1;
SetConsoleCursorPosition(hOut, pos);
for (i=0; i < 5; i++) {
printf(" %s ", Menu[i]);
}
GetConsoleCursorInfo(hOut, &lpCur); //隐藏光标
lpCur.bVisible = FALSE;
SetConsoleCursorInfo(hOut, &lpCur);
BuffMenuBar = (CHAR_INFO *)malloc(size.X * size.Y * sizeof(CHAR_INFO));
SMALL_RECT rcMenu ={0, 0, size.X-1, 0} ;
ReadConsoleOutput(hOut, BuffMenuBar, size, pos, &rcMenu);
for (i=0; iAttributes = BACKGROUND_BLUE | BACKGROUND_GREEN
| BACKGROUND_RED;
ch = (char)((BuffMenuBar+i)->Char.AsciiChar);
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
(BuffMenuBar+i)->Attributes |= FOREGROUND_RED;
}
}
WriteConsoleOutput(hOut, BuffMenuBar, size, pos, &rcMenu); .
.
TagMenu(1);
}
void TagMenu(int num)
{
CONSOLE_SCREEN_BUFFER_INFO bInfo;
COORD size;
COORD pos = {0, 0};
int PosA = 2, PosB;
char ch;
int i;
if (num == 0) {
PosA = 0;
PosB = 0;
}
else {
for (i=1; iAttributes = BACKGROUND_BLUE | BACKGROUND_GREEN
| BACKGROUND_RED;
ch = (BuffMenuBar+i)->Char.AsciiChar;
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
(BuffMenuBar+i)->Attributes |= FOREGROUND_RED;
}
}
for (i=PosA; iAttributes = FOREGROUND_BLUE | FOREGROUND_GREEN
| FOREGROUND_RED;
}
for (i=PosB; iAttributes = BACKGROUND_BLUE | BACKGROUND_GREEN
.
.
| BACKGROUND_RED;
ch = (char)((BuffMenuBar+i)->Char.AsciiChar);
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
(BuffMenuBar+i)->Attributes |= FOREGROUND_RED;
}
}
SMALL_RECT rcMenu = {0, 0, size.X-1, 0};
WriteConsoleOutput(hOut, BuffMenuBar, size, pos, &rcMenu); }
void ClearScreen(void)
{
CONSOLE_SCREEN_BUFFER_INFO bInfo;
COORD home = {0, 0};
unsigned long size;
GetConsoleScreenBufferInfo( hOut, &bInfo );
size = bInfo.dwSize.X * bInfo.dwSize.Y;
FillConsoleOutputAttribute(hOut, bInfo.wAttributes, size, home, NULL);
FillConsoleOutputCharacter(hOut, ' ', size, home, NULL); }
程序运行结果如图3.9所示。
图3.9 学生住宿信息管理系统主界面
.
.
3.4.3 弹出窗口的设计
屏幕窗口是个有限的信息显示区域。在文本字符界面下,控制台窗口的大小通常设为80个字符的宽度和25行字符的高度,即每屏可以显示2000个字符。但是,为了使显示信息清晰美观、重点突出,并且体现信息之间的相关性,信息应该分批逐屏显示,每次在窗口最显眼的区域显示一类主题信息和相关辅助信息。
程序在运行过程中,会输出大量信息,包括运行状态、运行提示和运行结果等信息,某些信息还需要反复多次输出;同时程序需要用户输入数据和控制信息,用户的输入信息也需在屏幕上回显。初级编程者喜欢采用这样的信息输出方法:用标准输入输出函数进行输出,光标随着字符信息的输出而移动,逐行输出,满屏后引起屏幕滚动,新输出的信息在屏幕底部显示,屏幕上端的信息被“挤”出屏幕。这种方法在编程实现上很简单、很省事,但人机交互效果不好,输出信息的整体性和层次性差,用户在操作时犹如“走迷宫”。
弹出窗口的输出方式克服了上述方法的缺点,信息输出位置可以控制在屏幕中央或者与之关联的任何地方,信息的层次感很强。由于保留了窗口弹出之前的屏幕信息,关闭弹出窗口后,用户能非常清楚地回到上一步操作的地方,进行下一步操作。
弹出窗口设计的基本思路是,首先确定输出信息的屏幕位置和大小,接着将新窗口弹出后所要覆盖的屏幕区域字符信息读入到一块内存缓冲区,然后在新窗口内输出信息,模拟“弹出”效果。弹出窗口内的操作完成后,把保存在内存缓冲区的字符信息写到其原来所在的屏幕位置,弹出窗口消失,屏幕恢复为窗口弹出之前的外观。
按照这一思路,可以实现多层弹出窗口。窗口的多层弹出和逐层关闭,给屏幕信息的维护带来了复杂性。为了便于处理,我们用链表来模拟堆栈,实现弹出窗口的栈式管理。
弹出窗口栈式管理用到以下结构类型。
typedef struct layer_node {
char LayerNo; //弹出窗口层数
SMALL_RECT rcArea; //弹出窗口区域坐标
CHAR_INFO *pContent; //弹出窗口区域字符单元原信息存储缓冲区
char *pScrAtt; //弹出窗口区域字符单元原属性值存储缓冲区
struct layer_node *next; //下一结点的地址
} LAYER_NODE;
利用这种结构类型的数据可以模拟出如图3.10所示的堆栈,对弹出窗口信息进行管理。 .
.
LayerNo:弹出窗口的层数 TopLayer
rcArea:弹出窗口位置和大小
pContent:原字符信息存储区地址
pScrAtt:原单元属性值存储区地址
next:下层结点
图3.10 弹出窗口信息堆栈
LAYER_NODE结构的5个成员分别表示了弹出窗口相关信息。LayerNo表示当前弹出窗口的层数;rcArea表示当前弹出窗口矩形区域的位置和大小;pContent指向的动态存储区存放被弹出窗口所覆盖区域的原字符单元信息,用于当前弹出窗口关闭后恢复原屏幕窗口信息;pScrAtt指向的动态存储区存放内容的用途与输入处理相关,将在下面的输入处理中进行介绍;next存放下层弹出窗口相关信息的地址。
堆栈栈顶由LAYER_NODE结构指针TopLayer来指示,初值为NULL。系统界面初始化完成之后,屏幕窗口看作第一层弹出窗口,窗口相关信息用LAYER_NODE结构类型的动态存储区存放后入栈,结构指针TopLayer指向栈顶;以后每弹出一层窗口,就执行一次入栈操作。关闭弹出窗口时,用TopLayer指向的LAYER_NODE结构类型数据恢复被覆盖的屏幕区域,将TopLayer指向下一层结点,释放用过的动态存储区,完成出栈操作。
例3.2 弹出菜单的实现。以例3.1为基础,在主函数main中加入下面粗体部分的代码,另外加上下面其他函数的定义,程序运行后可看到菜单弹出和关闭的效果。 #include "dorm.h"
int main()
{
COORD size = {ScrCol, ScrRow}; //窗口缓冲区大小
WORD att = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY
| BACKGROUND_BLUE;
hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
hIn = GetStdHandle(STD_INPUT_HANDLE); // 获取标准输入设备句柄
SetConsoleTitle(SysName); //设置窗口标题
SetConsoleScreenBufferSize(hOut, size); //设置窗口缓冲区大小80*25
SetConsoleTextAttribute(hOut, att); // 设置黄色前景和蓝色背景
.
.
ClearScreen(); // 清屏
ScrAtt = (char *)calloc(ScrCol * ScrRow, sizeof(char));
TopLayer = (LAYER_NODE *)malloc(sizeof(LAYER_NODE));
TopLayer->LayerNo = 0;
TopLayer->rcArea.Left = 0;
TopLayer->rcArea.Top = 0;
TopLayer->rcArea.Right = ScrCol - 1;
TopLayer->rcArea.Bottom = ScrRow - 1;
TopLayer->pContent = NULL;
TopLayer->pScrAtt = ScrAtt;
TopLayer->next = NULL;
ShowMenu();
ShowState(); //显示状态栏
SelMenu = 2;
TagMenu(SelMenu);
PopMenu(SelMenu);
getch();
PopOff();
getch();
return 0;
}
void PopMenu(int num)
{
LABEL_BUNDLE labels;
HOT_AREA areas;
SMALL_RECT rcPop;
COORD pos;
WORD att;
char *pCh;
int i, j, loc = 0;
if (num != SelMenu) { //如果弹出子菜单不属于选中的主菜单
if (TopLayer->LayerNo != 0) { //将此前已弹出的菜单关闭
PopOff();
SelSMenu = 0;
}
}
else if (TopLayer->LayerNo != 0) { //若已弹出该子菜单,则返回
return;
.
.
}
SelMenu = num;
TagMenu(SelMenu);
LocSMenu(SelMenu, &rcPop);
for (i=1; iRight - pRc->Left + 1;
size.Y = pRc->Bottom - pRc->Top + 1;
nextLayer = (LAYER_NODE *)malloc(sizeof(LAYER_NODE));
nextLayer->next = TopLayer;
nextLayer->LayerNo = TopLayer->LayerNo + 1;
nextLayer->rcArea = *pRc;
nextLayer->pContent = (CHAR_INFO *)malloc(size.X*size.Y*sizeof(CHAR_INFO));
nextLayer->pScrAtt = (char *)malloc(size.X*size.Y*sizeof(char));
pCh = nextLayer->pScrAtt;
ReadConsoleOutput(hOut, nextLayer->pContent, size, pos, pRc);
for (i=pRc->Top; i<=pRc->Bottom; i++) {
for (j=pRc->Left; j<=pRc->Right; j++) {
*pCh = ScrAtt[i*ScrCol+j];
pCh++;
}
}
TopLayer = nextLayer;
//设置弹出窗口区域的字符属性
pos.X = pRc->Left;
.
.
pos.Y = pRc->Top;
for (i=pRc->Top; i<=pRc->Bottom; i++) {
FillConsoleOutputAttribute(hOut, att, size.X, pos, NULL);
pos.Y++;
}
for (i=0; inum; i++) {
pCh = pLabel->ppLabel[i];
if (strlen(pCh) != 0) {
WriteConsoleOutputCharacter(hOut, pCh, strlen(pCh),
pLabel->pLoc[i], NULL);
}
}
for (i=pRc->Top; i<=pRc->Bottom; i++) {
for (j=pRc->Left; j<=pRc->Right; j++) {
ScrAtt[i*ScrCol+j] = TopLayer->LayerNo;
}
}
for (i=0; inum; i++) {
row = pHotArea->pArea[i].Top;
for (j=pHotArea->pArea[i].Left; j<=pHotArea->pArea[i].Right; j++) {
ScrAtt[row*ScrCol+j] |= (pHotArea->pSort[i] << 6)
| (pHotArea->pTag[i] << 2);
}
}
DrawBox(pRc);
}
void PopOff(void)
{
LAYER_NODE *nextLayer;
COORD size;
COORD pos = {0, 0};
char *pCh;
int i, j;
if ((TopLayer->next==NULL) || (TopLayer->pContent==NULL)) {
return;
}
nextLayer = TopLayer->next;
//恢复弹出窗口区域原外观
.
.
size.X = TopLayer->rcArea.Right - TopLayer->rcArea.Left + 1;
size.Y = TopLayer->rcArea.Bottom - TopLayer->rcArea.Top + 1;
WriteConsoleOutput(hOut, TopLayer->pContent, size, pos, &(TopLayer->rcArea));
//恢复字符单元原属性
pCh = TopLayer->pScrAtt;
for (i=TopLayer->rcArea.Top; i<=TopLayer->rcArea.Bottom; i++) {
for (j=TopLayer->rcArea.Left; j<=TopLayer->rcArea.Right; j++) {
ScrAtt[i*ScrCol+j] = *pCh;
pCh++;
}
}
free(TopLayer->pContent); //释放动态存储区
free(TopLayer->pScrAtt);
free(TopLayer);
TopLayer = nextLayer;
SelSMenu = 0;
}
void DrawBox(SMALL_RECT *pRc)
{
char chBox[] = {'+','-','|'}; //画框用的字符
COORD pos = {pRc->Left, pRc->Top};
WriteConsoleOutputCharacter(hOut, &chBox[0], 1, pos, NULL); //画左上角
for (pos.X = pRc->Left + 1; pos.X < pRc->Right; pos.X++) { //画上边
WriteConsoleOutputCharacter(hOut, &chBox[1], 1, pos, NULL);
}
pos.X = pRc->Right;
WriteConsoleOutputCharacter(hOut, &chBox[0], 1, pos, NULL); //画右上角
for (pos.Y = pRc->Top+1; pos.Y < pRc->Bottom; pos.Y++) { //画左边和右边
pos.X = pRc->Left;
WriteConsoleOutputCharacter(hOut, &chBox[2], 1, pos, NULL);
pos.X = pRc->Right;
WriteConsoleOutputCharacter(hOut, &chBox[2], 1, pos, NULL);
}
pos.X = pRc->Left;
pos.Y = pRc->Bottom;
WriteConsoleOutputCharacter(hOut, &chBox[0], 1, pos, NULL); //画左下角
for (pos.X = pRc->Left + 1; pos.X < pRc->Right; pos.X++) { //画下边
WriteConsoleOutputCharacter(hOut, &chBox[1], 1, pos, NULL);
}
pos.X = pRc->Right;
.
.
WriteConsoleOutputCharacter(hOut, &chBox[0], 1, pos, NULL); //画右下角 }
void TagSMenu(int num)
{
SMALL_RECT rcPop;
COORD pos;
WORD att; //文本属性
int width;
LocSMenu(SelMenu, &rcPop);
if ((num<1) || (num == SelSMenu) || (num>rcPop.Bottom-rcPop.Top-1)) {
return;
}
pos.X = rcPop.Left + 2;
width = rcPop.Right - rcPop.Left - 3;
if (SelSMenu != 0) {
pos.Y = rcPop.Top + SelSMenu;
att = BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED; //白底黑字
FillConsoleOutputAttribute(hOut, att, width, pos, NULL);
pos.X += 1;
att |= FOREGROUND_RED;
FillConsoleOutputAttribute(hOut, att, 1, pos, NULL);
}
pos.X = rcPop.Left + 2;
pos.Y = rcPop.Top + num;
att = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; //黑底白字
FillConsoleOutputAttribute(hOut, att, width, pos, NULL);
SelSMenu = num;
}
void LocSMenu(int num, SMALL_RECT *rc) {
int i, len, loc = 0;
rc->Top = 1;
rc->Left = 1;
for (i=1; iLeft += strlen(Menu[i-1]) + 4;
loc += cSMenu[i-1];
}
rc->Right = strlen(SMenu[loc]);
for (i=1; iRight < len) {
rc->Right = len;
}
}
rc->Right += rc->Left + 3;
rc->Bottom = rc->Top + cSMenu[num-1] + 1;
if (rc->Right >= ScrCol) {
len = rc->Right - ScrCol + 1;
rc->Left -= len;
rc->Right = ScrCol - 1;
}
}
程序运行结果如图3.11所示。
图3.11 弹出菜单效果图
3.4.4 键盘和鼠标输入信息的获取
良好的人机交互界面具有以下特征:能同时支持键盘输入和鼠标输入,对系统常用功能的调用设有快捷键,通过键盘操作能够调用到系统的每一个功能,充分利用鼠标移动速度快和操作直观的特点提高信息输入效率等。
函数ReadConsoleInput用来从控制台输入缓冲区读取输入数据,并将读出数据从输入缓冲区删除掉。该函数原型为:
BOOL WINAPI ReadConsoleInput(HANDLE hConsoleInput, PINPUT_RECORD lpBuffer,
DWORD nLength, LPDWORD lpNumberOfEventsRead);
.
.
函数参数在附录中已进行说明,这里介绍该函数的用法。
函数ReadConsoleInput被调用时,如果缓冲区中没有输入数据,函数将等待下去,直到读到至少一条
记录
混凝土 养护记录下载土方回填监理旁站记录免费下载集备记录下载集备记录下载集备记录下载
后返回。读到的记录存放在参数lpBuffer所指向的内存单元。记录用INPUT_RECORD结构类型的数据来表示,成员EventType表示事件的类型,成员Event是联合类型,存放事件的具体内容。编程时,需要对EventType的值为KEY_EVENT(键盘输入)或MOUSE_EVENT(鼠标输入)的两类事件进行处理。
当EventType的值为KEY_EVENT时,Event的联合成员KeyEvent存放了按键相关信息。KeyEvent是KEY_EVENT_RECORD结构类型,其成员bKeyDown表明键是被按下(TRUE)还是被释放(FALSE),成员wRepeatCount表明按键重复的次数,成员wVirtualKeyCode存放按键的虚拟键码,成员wVirtualScanCode存放按键的虚拟扫描码,成员uChar存放按键的ASCII码或Unicode码(取决于系统所采用的字符集),成员dwControlKeyState表示有哪些控制键被同时按下。我们每按一次键会产生两个事件记录,一个
记录表
体温记录表下载消防控制室值班记录表下载体温记录表 下载幼儿园关于防溺水的家访记录表绝缘阻值测试记录表下载
示键被按下,另一条记录表示键被释放。常用键的各种码值参见附录。
当EventType的值为MOUSE_EVENT时,Event的联合成员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表示在鼠标事件发生时有哪些控制键被同时按下,成员dwEventFlags表示鼠标事件的具体类型,取值为以下符号常量:
MOUSE_MOVED值为1,表示鼠标移动事件;
DOUBLE_CLICK值为2,表示鼠标双击事件;
值为4,表示鼠标MOUSE_WHEELED滚轮滚动事件。
3.4.5 输入处理
程序在运行过程中需要用户输入数据或控制信息,数据用于程序功能实现时的运算处理,控制信息用于选择和执行程序功能。程序在什么地方需要用户输入哪些信息,信息以何种方式输入,以及接收到各种不同信息后程序如何进行处理,这些是详细设计时所要解决的输入处理问题。
比如,在数据加载和主界面初始化完成后,系统显示如图3.9所示的主界面,等待用户输入。我们对此时系统的输入处理做一个详细设计,分为键盘输入处理和鼠标输入处理两个部分。
(1) 键盘输入处理
按照表3.15进行处理,对其他按键不予响应。
表3.15 主界面下的键盘输入处理设计
按键 系统响应
执行帮助菜单下的帮助主题子菜单对应功能模块 F1
执行文件菜单下的退出系统子菜单对应功能模块 Alt+X
.
.
清除当前选中菜单项标记,标记文件菜单项并弹出文件菜单的子菜单 Alt+F
清除当前选中菜单项标记,标记数据维护菜单项并弹出数据维护菜单的子菜单 Alt+M
清除当前选中菜单项标记,标记数据查询菜单项并弹出数据查询菜单的子菜单 Alt+Q
清除当前选中菜单项标记,标记数据统计菜单项并弹出数据统计菜单的子菜单 Alt+S
清除当前选中菜单项标记,标记帮助菜单项并弹出帮助菜单的子菜单 Alt+H
向左? 清除当前选中菜单项标记,标记左侧菜单项
向右? 清除当前选中菜单项标记,标记右侧菜单项
向下? 弹出当前菜单项的子菜单
清除当前选中菜单项标记,标记文件菜单项并弹出文件菜单的子菜单 f或F
清除当前选中菜单项标记,标记数据维护菜单项并弹出数据维护菜单的子菜单 m或M
清除当前选中菜单项标记,标记数据查询菜单项并弹出数据查询菜单的子菜单 q或Q
清除当前选中菜单项标记,标记数据统计菜单项并弹出数据统计菜单的子菜单 s或S
清除当前选中菜单项标记,标记帮助菜单项并弹出帮助菜单的子菜单 h或H
回车 弹出当前菜单项的子菜单
键盘输入处理时,要考虑输入处理的优先级,应先响应快捷键,再响应组合键,最后响
应单键。以Alt组合键为例,判断组合键的方法为:
ReadConsoleInput(hIn, &inRec, 1, &res); //从输入缓冲区读取一条输入记录 if (inRec.EventType == KEY_EVENT && inRec.Event.KeyEvent.bKeyDown) {
//输入事件类别为KEY_EVENT,且事件由键被按下所触发
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 NUMLOCK_ON 32 屏幕锁定 大写锁定 SCROLLLOCK_ON 64 CAPSLOCK_ON 128
(2) 鼠标输入处理
主界面下,当用鼠标左键点击主菜单项字符单元时,系统清除当前选中菜单项标记,然
后标记鼠标所点击菜单项并弹出该菜单的子菜单。鼠标左键点击其他区域,或其他鼠标事件,
系统均不予响应。
鼠标输入处理时,系统响应除了与鼠标事件类型有关外,还要根据鼠标所处位置决定具
体的响应动作。通常的做法是,获取鼠标事件发生时鼠标在窗口中的坐标,根据鼠标的横坐
.
.
标X和纵坐标Y来判断如何进行处理。这种判断需要进行十分繁琐的关系运算和逻辑运算,缺乏技术含量。下面介绍一种简单灵活的判断方法,可明显减少编码工作量。
热区本是网页设计中用到的一个概念,是指网页上建有链接的区域。我们将热区这个概念借用到程序设计中人机交互界面的设计上来,用来指界面上需要对鼠标事件(包括鼠标移动、滚轮转动、双击和任意一到多个键的按下)产生反应的区域。
基于这一思想,可用一个字符(8个二进制位)来表示窗口中某个字符单元的属性。如图3.12所示,这8个二进制位分为三段:0~1比特为A1,2~5比特为A2,6~7比特为A3。A1的取值范围为0~3,用来表示字符单元的“高度”,即该字符单元上弹出窗口的层数,0表示字符单元处于系统主界面层,没有弹出窗口覆盖该字符单元,这样弹出窗口的层数最多可到3层;A2取值范围为0~15,用来表示字符单元的热区编号,0表示字符单元不属于热区,这样同一层上的热区最多可为15个;A3取值范围为0~3,在A2取值不为0时用来表示字符单元的热区类型,0代表按钮类型,1代表输入框类型,2代表下拉选框类型,3保留备用。不同类型的热区被鼠标击中时,系统可以分别进行处理。本课程设计中,字符单元的属性用8个二进制位来存放,刚好够用。在其他应用程序开发中,如果弹出菜单超过3层,或某层窗口中热区超过15个,或热区类型超过4类,可以考虑用16个二进制位来存放字符单元属性。
7 6 5 4 3 2 1 0
A3 A2 A1
图3.12 字符单元属性的表示
前面在介绍弹出窗口设计时,弹出窗口的栈式管理用到以下结构类型:
typedef struct layer_node {
char LayerNo; //弹出窗口层数
SMALL_RECT rcArea; //弹出窗口区域坐标
CHAR_INFO *pContent; //弹出窗口区域字符单元原信息存储缓冲区
char *pScrAtt; //弹出窗口区域字符单元原属性值存储缓冲区
struct layer_node *next; //下一结点的地址
} LAYER_NODE;
其中,结构成员pScrAtt用来指向一个动态存储区,该存储区存放被弹出窗口所覆盖字符单元的原先属性值,在弹出窗口关闭时用于恢复窗口弹出前屏幕字符单元的属性。
系统界面初始化完成之后,屏幕显示系统的主界面(如图3.9所示)。用一个字符数组ScrAtt存放屏幕上所有字符单元的属性值,字符单元的坐标X和Y与存放其属性值的数组元素下标n的关系为:
n , Y × 屏幕缓冲区的宽度 , X
主界面中只有5个主菜单项显示区域为热区,依次编号1~5,热区类型为按钮型。此后,如果有窗口弹出(弹出菜单也是弹出窗口),则将窗口所覆盖区域字符单元的信息和属性分别保存起来,执行弹出窗口信息入栈操作,然后在弹出窗口区域输出提示信息,将弹出窗口字符单元的属性值写入数组ScrAtt对应元素以设置热区。鼠标输入处理时,取鼠标所在字符单元的属性值,根据字符单元的层数、热区编号和热区类型,结合鼠标事件类型做出相应处理。当弹出窗口关闭时,用所保存的弹出窗口区域字符单元原先的属性值修改数组ScrAtt对应元素值,恢复窗口弹出前屏幕字符单元的属性,最后执行弹出窗口信息出栈操作。
3.4.6 菜单操作与系统功能函数的调用
.
.
按照概要设计,系统功能分为五个模块,各模块所包含的子模块共有22个,分别用22个函数实现相应功能。其中,数据加载函数和界面初始化函数只在系统启动时执行一次,以后不再执行。其余20个函数可以通过菜单操作或快捷键执行相应功能。
例3.3 在图3.9所示的主界面下,实现菜单操作与系统功能函数的调用。本例中给出了函数SysRun和函数ExeFunction的定义,分别用于菜单操作和系统功能函数的调用。例子中调用了例3.1和例3.2中的函数,而其余函数的定义没有给出。
#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) { // 循环
ReadConsoleInput(hIn, &inRec, 1, &res);
if (inRec.EventType == MOUSE_EVENT) {
pos = inRec.Event.MouseEvent.dwMousePosition;
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;
//系统快捷键的处理
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 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; i0 && 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
本文档为【codeblock下人机交互界面设计】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑,
图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。