11.1.1 从“控制台”说起
“命令行交互界面”(简称CUI,也有人称为CLI)。
CUI需要我们记忆并在控制台输入命令文本内容,而GUI则以图形的方式呈现、组织各类命令,比如Windows的“开始”菜单,用户只需通过简单的键盘或鼠标操作,就可以发起命令。
GUI操作系统重,用户可以同时处理多个任务,然而,一个总喜欢等待你“按下任意键”的CUI屏幕,就是史前操作系统展现各用户的全部,难以实现多任务。
CUI和GUI的形同之处
我们写过的许多与用户交互的代码,其过程基本类似于图11-1
图11 -1 典型的CUI主循环
11.1.2 GUI下的输入处理
基于控制台的程序,输入用cin, 输出用cout,非常简单。
GUI的输入,并非通过一个函数直接读取用户的一个按键操作或鼠标动作(也包括触摸屏的输入)的结果,而是操作系统,帮我们捕获这些输入操作,然后再有它转发到程序。
在GUI的世界里,往往有多个程序同时运行,哪怕就只有一个程序在运行,屏幕也可能有多个“图形元素”等待用户输入,所以必须有一套机制,以方便操作系统将用户的输入准确地转发到特定的那个图形元素。
这套机制最基础的要求是:每个图元元素都有一个“编号”(可以理解为“地址”)。综上所述,输入过程如图11-2所示
图11-2 消息传递与分发
统一术语:
以Windows操作系统为例,操作系统发送给各个应用程序的信息(包括用户输入),称为“消息(Message/Msg)”;各个拥有标号的“图形元素”,称为“窗口(Windows/Wnd)”;而编号也不是普通编号,它叫“句柄(Handle)”
句柄的含义:句柄就是每个窗口在操作系统中唯一的编号。
控制台程序中,当程序运行到“cin >> i”之类的语句时,程序就卡在那里等待用户输入了。
但在GUI环境下,一个带窗口的程序运行起来之后,用户抓过鼠标就在窗口上挪动,并乱点一气……,这些操作都被认为是合理的,于是就将这些消息一次发送给程序了;如果程序不处理这些消息(哪怕是收到后就直接丢弃),操作系统就会会怀疑这个程序挂掉了。
所以编写一个GUI应用,最重要的事情,就是抓紧搞一个“死循环”来接收这些消息。以Windows操作系统为例,这个“死循环”的C代码类似:
//Windows API编程示例的伪代码
MSG msg; //消息
while(::GetMessage(&msg)
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
三个全局函数,都是Windows操作系统提供的编程接口(API)函数。
GetMessage源源不断地获取属于当前进程的消息,
然后通过TranslateMessage做必要的转换,
再调用DispatchMessage将它们正确地分派给本进程内的窗口。
但,代码里没有看到窗口句柄,怎么知道各个消息属于哪个窗口呢?猜一下就知道了,MSG结构里有一个成员,就是这个消息所属窗口的句柄。
再往后的工作,就是每个窗口都会有一个术语为"WndProc(窗口过程)"的函数,它会收到操作系统发来的消息,然后搞一个“switch / case”结构来区分消息是什么,再做处理。
图 11-3 GUI下的消息循环
这个过程被称为消息循环。简单地理解,就是应用程序将源源不断地接收到消息,然后判断这消息是什么,根据这个消息触发并执行一段事件(回调函数),然后再收到新消息……
一个从零开始的GUI应用代码,需要从“GetMessage(...)”开始写起,但多数C++ GUI库,都将这个过程封装起来,所以编程重心变成是为每一类窗口(如图11-3中的窗口1和窗口2)写它处理消息的过程;但一大块的"switch / case"是令人厌烦的结构,所以GUI库提供了一些手段,帮助我们绕过"switch / case"。
11.1.3 GUI下的输出处理
既然叫“图形用户界面”,自然要在屏幕上画各种信息。屏幕再大,面积总是有限的,外加“多任务”,每个进程都抢这在屏幕上涂鸦,结果很容易想到:后面画的内容,会将前面的内容覆盖掉。
电脑上打开QQ登录框,把它摆在屏幕中央;然后打开浏览器,拖过去盖住QQ登录框,此时,我们看到的那个QQ登录框还在吗?
那个QQ登录框真的不在了。QQ程序肯定还在内存里,QQ登录框的窗口句柄也还在操作系统的“账本”里,但此时操作系统无需尝试网屏幕上“画”那个对话框;
假设我们将浏览器一点一点地挪开,此时QQ程序会收到一个消息:“你快重画窗口,不过就这一小块有效。”
如此,是否可以推出GUI程序和CUI程序在界面显示上最大的不同?
CUI程序是主动的,在你想要的时间,地点,想说一句话是,直接cout一下,比如:
char C;
//此时,此处,需要一句提示
cout << "请输入一个字母:";
cin >> C;
//此时,此处,又需要一句话
cout << "您输入的字母是:" << C << "。" << endl;
//想想,觉得需要再补一句
cout << "我是不是很聪明?" << endl;
GUI程序在这方面却是被动的,它必须等操作系统发消息通知它:“嘿,你可以在屏幕上输出点什么了。” 操作系统通知一次,就得响应一次。
假设GUI程序使用DrawText(int x, int y, char const* text)函数可以在屏幕上指定位置上输出一句话,但程序只是主动调用一次,比如:
DrawText(100, 100, "Hello World");
屏幕上确实有可能出现那句话,但此时再把浏览器拖过啦,“盖住”那行话,然后移开……那行话消失不见了,浏览器就像一个橡皮擦,把那行话擦掉了。
其间原因在于:我们写的程序也接收到了操作系统“快重画吧”的消息,却不理会这个消息,没有再次调用DrawText()操作。
那么,是不是屏幕上显示的所有内容,都需要我们写代码自行绘画呢?当然不是,像常规窗口,对话框,按钮,列表框和菜单等标准袁术,它们在不同的应用中长得样子都相似,可见其画法都一样。因为它们统一由操作系统(或其GUI支持库)负责在屏幕上画出来,除非我们想搞定制,比如画一个不规则多边形的按钮。