人机交互界面设计

时间:2024.1.14

武 汉 工 程 大 学

计算机科学与工程学院

《人机交互》实验报告

人机交互界面设计

人机交互界面设计

计算机科学与工程学院

《人机交互》实验报告 2

人机交互界面设计

计算机科学与工程学院

《人机交互》实验报告 3

人机交互界面设计

计算机科学与工程学院

《人机交互》实验报告 4

人机交互界面设计


第二篇:CodeBlock下的人机交互界面设计


CodeBlock下的人机交互界面设计

人机交互界面指的是计算机系统与用户之间的接口。通过该接口,一方面,计算机系统向用户输出系统的运行状态、运行控制和运行结果等方面信息;另一方面,用户根据输出信息向系统输入相应的指令和数据等信息。

3.4.1 控制台窗口和屏幕缓冲区

控制台窗口是个二维平面空间,其坐标系统的原点(0, 0)设在窗口左上角,即窗口第一行第一列字符单元的位置。横轴(X轴)的正向沿原点向右,与窗口的第一行重合,每刻度为一个字符宽度;纵轴(Y轴)的正向沿原点向下,与窗口的第一列重合,每刻度为一个字符高度。窗口中每个字符单元对应一个二维坐标。比如,第5行第32列字符单元的坐标为(31, 4)。如图3.8所示。

CodeBlock下的人机交互界面设计

窗口标题栏

图3.8 控制台窗口和屏幕缓冲区关系示意图

屏幕缓冲区是个二维数组,逻辑上可看作一个二维平面空间。数组第一个元素的下标

[0][0]对应此平面空间坐标系统的原点(0, 0),数组第1维的下标对应坐标系统的纵坐标(Y坐标),第2维下标对应坐标系统的横坐标(X坐标)。屏幕缓冲区存放着M行N列字符单元的信息,M和N的大小由系统设置,并可以进行修改。每个字符单元信息用一个CHAR_INFO结构类型的数据来表示,结构成员Char存放字符的码值(Unicode码或ASCII码,取决于系统所采用的字符集),结构成员Attributes存放字符的属性(字符显示所用的前景色和背景色)。操作系统以一定的频率从屏幕缓冲区读取字符单元信息,并显示在控制台窗口中。应用程序的输出信息实际上输出到了屏幕缓冲区,由此改变了控制台窗口所显示的内容。初始状态下,屏幕缓冲区坐标系统与控制台窗口坐标系统重合,窗口中第m行第n列字符的码值和颜色值存放在屏幕缓冲区二维数组中下标为[m-1][n-1]的元素中。利用控制台函数可以改变这两个坐标系统的对应关系,实现特殊的显示效果。图3.8表示了控制台窗口和屏幕缓冲区的相互关系。

1

一个控制台可拥有多个屏幕缓冲区,但只有处于激活状态的屏幕缓冲区内容显示在控制台窗口中。操作系统在为进程创建控制台的同时会创建一个屏幕缓冲区。进程可调用函数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 字符属性符号常量表

CodeBlock下的人机交互界面设计

2

前缀为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) 用标准输出函数(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);

3

CodeBlock下的人机交互界面设计

//输出字符串

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<strlen(string); i++) {

lpBuffer->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); //设置窗口标题

4

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; i<size.X; i++) {

(BuffMenuBar+i)->Attributes = 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);

5

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; i<num; i++) {

PosA += strlen(Menu[i-1]) + 4;

}

PosB = PosA + strlen(Menu[num-1]);

}

GetConsoleScreenBufferInfo( hOut, &bInfo );

size.X = bInfo.dwSize.X;

size.Y = 1;

for (i=0; i<PosA; i++) {

(BuffMenuBar+i)->Attributes = 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; i<PosB; i++) {

(BuffMenuBar+i)->Attributes = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;

}

for (i=PosB; i<bInfo.dwSize.X; i++) {

(BuffMenuBar+i)->Attributes = BACKGROUND_BLUE | BACKGROUND_GREEN 6

| 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所示。

CodeBlock下的人机交互界面设计

图3.9 学生住宿信息管理系统主界面

7

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所示的堆栈,对弹出窗口信息进行管理。 8

CodeBlock下的人机交互界面设计

图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); // 设置黄色前景和蓝色背景

9

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;

10

}

SelMenu = num;

TagMenu(SelMenu);

LocSMenu(SelMenu, &rcPop);

for (i=1; i<SelMenu; i++) {

loc += cSMenu[i-1];

}

labels.ppLabel = SMenu + loc;

labels.num = cSMenu[SelMenu-1];

COORD aLoc[labels.num];

for (i=0; i<labels.num; i++) {

aLoc[i].X = rcPop.Left + 2;

aLoc[i].Y = rcPop.Top + i + 1;

}

labels.pLoc = aLoc;

areas.num = labels.num;

SMALL_RECT aArea[areas.num];

char aSort[areas.num];

char aTag[areas.num];

for (i=0; i<areas.num; i++) {

aArea[i].Left = rcPop.Left + 2;

aArea[i].Top = rcPop.Top + i + 1;

aArea[i].Right = rcPop.Right - 2;

aArea[i].Bottom = aArea[i].Top;

aSort[i] = 0;

aTag[i] = i + 1;

}

areas.pArea = aArea;

areas.pSort = aSort;

areas.pTag = aTag;

att = BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED; //白底黑字 PopUp(&rcPop, att, &labels, &areas);

pos.X = rcPop.Left + 2;

for (pos.Y=rcPop.Top+1; pos.Y<rcPop.Bottom; pos.Y++) {

pCh = SMenu[loc+pos.Y-rcPop.Top-1];

if (strlen(pCh)==0) {

FillConsoleOutputCharacter(hOut, '-', rcPop.Right-rcPop.Left-3, pos, NULL); for (j=rcPop.Left+2; j<rcPop.Right-1; j++) {

ScrAtt[pos.Y*ScrCol+j] &= 3;

11

}

}

}

pos.X = rcPop.Left + 3;

att = FOREGROUND_RED | BACKGROUND_BLUE

| BACKGROUND_GREEN | BACKGROUND_RED;

for (pos.Y=rcPop.Top+1; pos.Y<rcPop.Bottom; pos.Y++) {

if (strlen(SMenu[loc+pos.Y-rcPop.Top-1])==0) {

continue;

}

FillConsoleOutputAttribute(hOut, att, 1, pos, NULL);

}

}

void PopUp(SMALL_RECT *pRc, WORD att, LABEL_BUNDLE *pLabel, HOT_AREA *pHotArea) {

LAYER_NODE *nextLayer;

COORD size;

COORD pos = {0, 0};

char *pCh;

int i, j, row;

size.X = pRc->Right - 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;

12

pos.Y = pRc->Top;

for (i=pRc->Top; i<=pRc->Bottom; i++) {

FillConsoleOutputAttribute(hOut, att, size.X, pos, NULL); pos.Y++;

}

for (i=0; i<pLabel->num; 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; i<pHotArea->num; 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;

//恢复弹出窗口区域原外观

13

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;

14

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; i<num; i++) {

rc->Left += strlen(Menu[i-1]) + 4;

loc += cSMenu[i-1];

}

rc->Right = strlen(SMenu[loc]);

for (i=1; i<cSMenu[num-1]; i++) {

15

len = strlen(SMenu[loc+i]);

if (rc->Right < 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所示。

CodeBlock下的人机交互界面设计

图3.11 弹出菜单效果图

3.4.4 键盘和鼠标输入信息的获取

良好的人机交互界面具有以下特征:能同时支持键盘输入和鼠标输入,对系统常用功能的调用设有快捷键,通过键盘操作能够调用到系统的每一个功能,充分利用鼠标移动速度快和操作直观的特点提高信息输入效率等。

函数ReadConsoleInput用来从控制台输入缓冲区读取输入数据,并将读出数据从输入缓冲区删除掉。该函数原型为:

BOOL WINAPI ReadConsoleInput(HANDLE hConsoleInput, PINPUT_RECORD lpBuffer,

DWORD nLength, LPDWORD lpNumberOfEventsRead);

16

函数参数在附录中已进行说明,这里介绍该函数的用法。

函数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,表示鼠标双击事件;

MOUSE_WHEELED值为4,表示鼠标滚轮滚动事件。

3.4.5 输入处理

程序在运行过程中需要用户输入数据或控制信息,数据用于程序功能实现时的运算处理,控制信息用于选择和执行程序功能。程序在什么地方需要用户输入哪些信息,信息以何种方式输入,以及接收到各种不同信息后程序如何进行处理,这些是详细设计时所要解决的输入处理问题。

比如,在数据加载和主界面初始化完成后,系统显示如图3.9所示的主界面,等待用户输入。我们对此时系统的输入处理做一个详细设计,分为键盘输入处理和鼠标输入处理两个部分。

(1) 键盘输入处理

按照表3.15进行处理,对其他按键不予响应。

表3.15 主界面下的键盘输入处理设计

CodeBlock下的人机交互界面设计

17

键盘输入处理时,要考虑输入处理的优先级,应先响应快捷键,再响应组合键,最后响应单键。以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头文件中进行了定义,如表

CodeBlock下的人机交互界面设计

3.16所示。

表3.16 控制键对应符号常量表

(2) 鼠标输入处理

主界面下,当用鼠标左键点击主菜单项字符单元时,系统清除当前选中菜单项标记,然后标记鼠标所点击菜单项并弹出该菜单的子菜单。鼠标左键点击其他区域,或其他鼠标事件,系统均不予响应。

鼠标输入处理时,系统响应除了与鼠标事件类型有关外,还要根据鼠标所处位置决定具体的响应动作。通常的做法是,获取鼠标事件发生时鼠标在窗口中的坐标,根据鼠标的横坐

18

CodeBlock下的人机交互界面设计

标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

CodeBlock下的人机交互界面设计

图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 菜单操作与系统功能函数的调用

19

按照概要设计,系统功能分为五个模块,各模块所包含的子模块共有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;

20

}

}

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

}

21

}

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];

} //找到子菜单第一项在子菜单数组中的位置(下标) 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); 22

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); 23

}

}

}

}

}

}

}

}

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;

24

if (pFunction[loc] != NULL) { bRet = (*pFunction[loc])(); }

return bRet;

}

25

更多相关推荐:
交互设计笔试题总结

1平面设计工作的流程1)进行设计需求分析;2)提供设计构思;3)收集整理资料;4)选择相关软件进行制作;5)根据领导或客户的意见进行修改;6)成稿。2用户体验(UE)是一种纯主观的,在用户使用产品过程中建立起来…

头像web版交互设计总结

NO头像web版交互设计总结头像web版交互设计总结jadl20xx422互联网资源1项目背景QQ会员头像是一个会员比较喜欢的功能为用户提供了大量优质精美的头像但内容一直由官方提供虽然保证了质量但在数量上更新速...

交互设计使用体验报告

交互设计使用体验报告随着社会的发展快节奏的工作已经成为一种趋向很多办公族和御宅族每天必做的事就是叫外卖淘宝在这方面看出了商机推手机叫外卖服务淘点点可能很多人从来都没听说过淘点点这是一款足以改变我吗交外卖习惯的革...

硕士交互设计开题报告

南京航空航天大学攻读硕士学位研究生课题论证报姓名庄多多学号SQ09055044003专业设计艺术学研究方向人机工程学指导教师陈炳发20xx年12月28日告

交互设计开题报告

研究生学位论文开题报告姓学名刘青号Z1308306学位类别硕士一级学科艺术学二级学科艺术设计研究方向数字媒体艺术导师职称盛瑨副教授南京艺术学院研究生处制20xx年月日填123456789

关于交互设计的调查分析报告1

关于交互设计的调查分析报告本学期我们学习了创意元素与产品设计其中的交互设计引起了我的兴趣于是在此对交互设计进行了调查分析一历史渊源BillMoggridge在20世纪80年代后期提出了交互设计的概念初始名为So...

交互设计实习报告

实习报告一实习说明1实习时间20xx年10月20日至20xx年1月5日2实习内容关于手机微波客户端的交互分析3实习性质毕业实习二实习内容介绍微博即微博客MicroBlog的简称是一个基于用户关系的信息分享传播以...

一名交互设计师必备的知识架构

一名交互设计师必备的知识架构20xx1281010发布者yuanxingbbs查看10607评论4来自jeffreyblog摘要如果你也是一枚刚入门的交互设计师是不是常有这样一种感觉不知从何下手闷头读了一大堆书...

交互设计方法

交互设计的方法长期以来我就有对几年来交互设计的心得进行总结整理的想法回到中国来亲身体会到不少同行主要是交互设计师和视觉设计师对于交互设计的困惑以及其他行业对于交互设计的误解和滥用后来我在小范围内开设了一个关于交...

北京理工大学工业设计考研经验总结

才思教育网址北京理工大学工业设计考研经验总结各位考研的同学们大家好我是才思的一名学员现在已经顺利的考上北京理工大学工业设计专业今天和大家分享一下这个专业的笔记方便大家准备考研希望给大家一定的帮助考试科目设计理论...

腾讯5面的经历 交互设计

腾讯5面的经历交互设计昨天拿到了腾讯的OFFER经过了十几天的折腾总算有个好结果心情放松了下来决定写点经验仅供参考一面试之前我的目标就是上海的交互设计师职位腾讯今年在上海好像没有校招的但是有分部所以我还是想试一...

快题设计总结

文章做快题设计总结转载考研和找工作铅笔2H要买真的最好是中华牌三只以上如果你准备用铅笔画透视图就要多准备几种规格至少要有HB2B钢笔如果你能画一手出色的钢笔画是很让人羡慕的如果直接画没有把握就用铅笔先画好钢笔最...

交互设计总结(25篇)