第一篇 看透 Windows
第二章 GUI的世界(二) 在何时何处画
上一章我们讲到,在Windows的世界里,我们所看到的一切,都是“画”出来的。并且,我们大胆地在Windows的桌面画了一个红叉。画红叉似乎很爽,不过那个程序更像是一个恶作剧。只是用来证明我们可以画。这一章,我们将要学习如何“正经地”画。
每一个“正经地”的Windows应用程序,往往都是相似的,它们只在合适的时机,合适的位置上画需要的东西。我们首先来看看合适的位置是哪里。
2.1 在自己的窗口上画
我们说过,在Windows世界里,有无数的窗口(请复习第一章).现在,让我们也来制造一个窗口——通过CB,这是一件再容易不过的事了。
2.1.1 准备工作:创建一个窗口
同时在学习《白话 C++》的学员注意了,我们在《白话 C++》里,经常创建“控制台工程”,就是那个“黑黑的”的窗口。但在《白话 Windows编程》,我们更多的是以如下步骤创建一个正常的Windows应用程序工程。提醒您不要顺手就来一个控制台工程,否则,你会永远找不到“窗口”。
运行 C++ Builder 6,菜单:File | New | Appication。 CB6为我们新建一个空白的Windows程序工程(Project)。并且自动生成了一个表单(Form)。我们知道,当程序运行之后,这个表单就被称为窗口(Window)。(如果你对这些及后面一些CB相关操作不熟,请复习《白话C++》的第二章)。
(对于CB5,在菜单File里,可以直接到的New Application。)
这个工程很重要,马上保存工程!按一下Ctrl + Shift + S 。建议在磁盘上建立一个专门的文件夹来保存这个工程。比如,建立:bhwin/ls2/prj1/ 这样层次的文件夹。至于CPP文件名和工程名字,我都只是取默认的Unit1.cpp和Project1.bpr。
2.1.2 观察一个窗口的蛛丝马迹!
2.1.2.1 第一个问题
没错,这一小节里,我们将像一个神探一样去观察一个窗口,并且提出一个个问题。要撕开一匹布,最难的在于撕开最开始的一个口子,下面的一个个问题,就是我们撕裂Windows面纱的第一道口子。希望你能有一付好眼力。
保存之后,我们还是什么代码也不必写,直接按F9,编译,运行。在我的电脑上,显示了一个窗口。就是设计时的那个表单Form1。为了不占用太大版面,我把窗口拉得很小。
(图一:一个“光秃秃”的窗口)
这是一个光秃秃的窗口……这真的是一个“光秃秃”的窗口吗?仔细看这个窗口,你能发现什么?有个同学特深沉,他说看出这窗口上隐含着一幅3维立体图,并且他建议大家最好用“斗鸡眼”的方式来观察………拷!真是太夸张了,有这样的学生,我觉得大家不要叫我老师,还是叫我“大师”吧。
事实上,这个窗口并非空无一物,它有一个标题栏,标题是:Form1,在右方,还有三个小按钮,分别表示“最小化”、“最大化”、“关闭”……听到这里,你们千万不要准备退学啊!虽然这些你们早就知道,可是我们的第一个问题也出来了:
你有没有想过,标题栏,及窗口边框等,它们也是画出来的!可是,它们是由谁画出来的呢?
第一个问题:窗口默认的图形元素,是谁在为我们画的呢?
2.1.2.2 第二个问题
退出 Form1,我们在该表单上放一个Button(按钮)。
首先,在CB的“Standard”页(也就是第一页),找到按钮控件(TButton):
(Standard页上 TButton 控件)
鼠标选中图示中有个“OK”的按钮,然后在当前表单上放置一个按钮,尽量放在表单的中间。
(在表单上放置一个按钮)
双击Button1,出现代码编辑窗口,输入以下代码(黑体部分):
void __fastcall TForm1::Button1Click(TObject *Sender)
{
//设置当前客户区的画笔(Pen)
Canvas->Pen->Color = clRed; //画笔的颜色为红色(Red)
Canvas->Pen->Width = 10; //画笔的粗细
//开始画叉啦,需要画两笔,
//这是第一笔:左上到右下
Canvas->MoveTo(0,0); //把画笔移到(Move to)坐标0,0处,即 客户区的原点
Canvas->LineTo(ClientWidth,ClientHeight); //从当前位置画一条线到客户区的右下角。
//第二笔:右上到左下:
Canvas->MoveTo(ClientWidth,0);
Canvas->LineTo(0,ClientHeight);
}
//---------------------------------------------------------------------------
同样是画一个叉,和第一章在屏幕上相比,这里的代码省去了不少。这是因为当我们在一个自己生成的窗口上作画时,很多东西都由CB默认提供了。比如:“画布(Canvas)”,还有当前客户区的长宽:ClientWidth,ClientHeight。(Client即客户的意思)。MoveTo是“移动到…”,LineTo是“画线到…”这两个函数在上一章我们用过了。
(学员会问了:老师你怎么知道变量Canvas,ClientWidth,ClientHeight;又怎么知道函数MoveTo,LineTo?嗯,这些东东你们很快也会知道它们的出处,但现在不是解释这些的时候。白话 C++的课程不是到了结构了吗?很快后面要学习“类”的知识,只有在学习了“类”的基础上,我们才可能去解释这一切。)
言归正传,我们保存工程,按F9,编译,执行。然后按一下Button1,结果如下:
(图二:在客户区上画叉)
按完Button1后,千万什么也不要动,不过我猜想,你很可能已经动了什么了,没关系,再按一次Button1就是,保证你看到的图和上面的图一个样。
然后我们还是来仔细观察这个窗口!看到什么了吗?红叉“经过”了按钮,可是竟然没有“画过”按钮的表面。这是为什么?
再者,我们是从窗口的坐标原点(0,0)起笔,你注意了吗?这个坐标原点,是在窗口的左上角,而不是我们中学学笛卡尔坐标系的左下角。另外,这个绘图区域,并不位于标题栏之内,而是紧挨在标题栏之下。这就说明了,在默认情况下,我们是无法在标题上画画的。
第二个问题:窗口在绘画时,Windows是如何决定它要在哪里画,不在哪里画?
2.1.2.3 第三个问题
继续我们的福尔摩斯之旅!
再按一次Button1,保证那个红叉的完好。然后,我来拖一个别的窗口(比如你聊天用的QQ窗口什么的),小心地“擦过”带叉窗口的右下角……看:
(我拖着WinAMP的窗口,“擦过”Form1)
擦过之后:
(擦过之后,红叉短了一腿)
我们发现,另外一个窗口,居然像个“橡皮擦”一样,擦去了我在Form1这个窗口上的画的红叉的一部分。
再做一个试验,还是先按一个Button1画出红叉,然后小心翼翼地用鼠标在窗口边缘拉大窗口,你会发现此时红叉不受损伤。但是如果我们把窗口先缩小,然后再拉回原来大小,这时会发现,红叉又有一部分消失了。
第三个问题: 在窗口上画的内容,为什么会被“擦除”?
2.2 在需要的时候画
所有三个问题,我都没准备回答。当你学完课程,这些问题必将不再是问题。
如果程序还在运行着,关闭它,回到CB。
在Form1表单上,选中那个Button1,然后删除它(没错,我们不要它了)。
鼠标单击一下Form1,用以确保选中Form1本身,然后在控件属性窗口,选Event(事件)页;最后找到OnPaint这一行,如图:
(找到OnPaint事件)
在OnPaint右边的编辑框内,双击鼠标,或者按下Ctrl + 回车键。进入OnPaint事件响应函数的代码编辑处。
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender) {
} //--------------------------------------------------------------------------- |
(Form1 的 OnPaint 事件响应函数)
(小心,这时候,你千万不要——没错,是不要——存盘,不然,CB会因为这个事件响应函数是空的,而自动删除它。)
然后,我们把前面Button1的OnClick函数里的代码,拷贝到FormPaint函数体内:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender) { //设置当前客户区的画笔(Pen) Canvas->Pen->Color = clRed; //画笔的颜色为红色(Red) Canvas->Pen->Width = 10; //画笔的粗细
//开始画叉啦,需要画两笔, //这是第一笔:左上到右下 Canvas->MoveTo(0,0); //把画笔移到(Move to)坐标0,0处,即 客户区的原点 Canvas->LineTo(ClientWidth,ClientHeight); //从当前位置画一条线到客户区的右下角。
//第二笔:右上到左下: Canvas->MoveTo(ClientWidth,0); Canvas->LineTo(0,ClientHeight); } //--------------------------------------------------------------------------- |
(代码Copy自前面Button1OnClick)
这时再存盘。
确保你把代码都拷对了吗?如果你是网页上拷来的,那有时会夹带一个汉字的空格,这样就会编译不过去。不管如何,按下F9键,跑跑吧。
(又是一个红叉)
又是一个红叉,但这回,你再用别的窗口去擦除,你会发现这个红叉擦不掉了。
不过,当你调整该窗口的大小……发现什么?咦世界有些乱套:
(将窗口拉小再拉大,红叉乱掉了)
此时要想恢复红叉,最直接的方法是把该窗口最小化,再恢复。你试试,并找找其它方法。
或许还有很多疑问,但关于“红叉”,我们的课程完了,下面要做的,算是一个扩展。
2.3 Hello Windows
Canvas 不仅可以用来画画,也可以用来写字。严格地讲,在Windows这个一切都是画出来的世界里,写字何尝不是涂鸦?当然,Windows不会发神经到让我一笔一划“画”出字来。在Windows的安装目录下,有一个Fonts(字体)目录,里面放了很多字体。所谓的字体,就是事先建立如何画出某个字来的信息。有了字体,我们可以很轻松的在窗口上写字。
去掉前面FormPaint函数体内的代码,换成以下几句:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender) { Canvas->Font->Color = clGreen; //画布的字体的颜色=绿色 Canvas->Font->Size = 24; //画布的字体的大小=24 (你现在看的课程,字体大小是9)
Canvas->TextOut(10,10,"你好,Windows世界!"); } //--------------------------------------------------------------------------- |
(更改 FormPaint里的代码,让我们向Windows世界问个好。)
按F9后,运行结果如图:
(呵呵,Windows,老朋友了。)
(也许,有些人对Windows没什么感情?如果你愿意,你可以把这句话改为“安红,俺想你!”,我知道你做得到。)
这节课就到这里吧。记住,更精采,更实用的Windows编程世界,在后面等着我们。