荔园在线
荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀
[回到开始]
[上一篇][下一篇]
发信人: jek (好好学习天天向上), 信区: Program
标 题: 创世纪的 C++builder(3)
发信站: BBS 荔园晨风站 (Sun Mar 12 02:36:44 2000), 转信
第XX章 具体而微的绘图程式
在本章中我将为你示范如何在C++Builder中撰写一个完整的
绘图程式。藉由这个程式的撰写,你会更加了解C++Builder
的 Canvas
绘图精神,而在撰写这个程式的同时,我们也可将相关的技术
做一个整体的检阅。此绘图程式的执行结果如下:
在此程式中我会以循序渐进的方式一步一步地带领你完成整个
程式,基本上这个程式和C++Builder内附的范例程式有几分
类似,但我必须要说明的是:在
C++Builder中所附的范例程式是直接由原先在Delphi内以
Object Pascal 所撰写的范例程式修改而成,所以有部份程式
的写法大为违背C++
式物件导向精神,在迈入C++Builder 的新世纪之後,我们当
然希望写出的程式是『系出名门,血统纯正』的C++
式的物件导向程式。而这就是我在本章中希望带领你完成的程
式。
XX-01 关於滑鼠事件(Mouse Event)
撰写绘图程式,首先要了解滑鼠事件,在Windows中定义了许
多的滑鼠讯息(Message),而这些滑鼠讯息在BCB中就成为滑
鼠事件了,为了要处理滑鼠事件,我们必须要选写滑鼠事件处
理程式:
在Windows中定义的滑鼠讯息列表
WM_CAPTURECHANGED
WM_LBUTTONDBLCLK
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MBUTTONDBLCLK
WM_MBUTTONDOWN
WM_MBUTTONUP
WM_MOUSEACTIVATE
WM_MOUSEMOVE
WM_NCHITTEST
WM_NCLBUTTONDBLCLK
WM_NCLBUTTONDOWN
WM_NCLBUTTONUP
WM_NCMBUTTONDBLCLK
WM_NCMBUTTONDOWN
WM_NCMBUTTONUP
WM_NCMOUSEMOVE
WM_NCRBUTTONDBLCLK
WM_NCRBUT?
鼠的按键,或是需要利用Shift来取得特殊键的状态,而做一
些额外的程式处理。
XX-02-01 OnMouseDown事件的处理
首先我们先以一个最基本的画线程式来说明OnMouseDown事件
的处理,当使用者按下滑鼠时,我们希望将笔移至事件发生时
的坐标,因此我们可将程式写成如下:
void __fastcall TForm1::FormMouseDown(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Canvas->MoveTo(X,Y);
}
XX-02-03 OnMouseUp事件的处理
同样地,我们可以再为这个Form加上OnMouseUp的事件处理
函式,在收到OnMouseUp事件时,由滑鼠点下的坐标,画一条
直线至现在的坐标。
void __fastcall TForm1::FormMouseUp(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
Canvas->LineTo(X,Y);
}
在写完了以上两个事件处理函式之後,我们就可以在Form上
面作画了,你可以用滑鼠在Form上面拖戈出一条条直线。其
执行结果大致如图XX-01:
图XX-01
XX-02-02 OnMouseMove事件的处理
在加上了OnMouseDown及OnMouseUp处理函式之後,我们只能
画出一条条直线,若是我们想要以滑鼠画出不规则线段时,就
必须再处理OnMouseMove事件,利用OnMouseMove事件,我们
可以追纵到滑鼠移动的位置,简单的OnMouseMove事件处理函
式如下:
void __fastcall TForm1::FormMouseMove(TObject *Sender,
TShiftState Shift, int X,
int Y)
{
Canvas->LineTo(X,Y);
}
此程式的意义即在於将滑鼠所经过的每个点,以线条连接起
来,在加上OnMouseMove 事件处理函式之後,它的执行结果会
变成图XX-02:
图XX-02
XX-02-03 滑鼠的处理的加强
前面的程式对於滑鼠的移动处理有部份考虑的不够周详,因为
它在滑鼠移动时不分青红皂白就将线画在萤慕上,造成萤幕上
的线条混乱,这并不是正规的处理方猎悖⒒惶跤稍阒聊壳八诘愕南摺M?
时更新原点位置至目前所在之点。
滑鼠放开时,将记录滑鼠按下的旗标设为False。
以下就是关於三个滑鼠事件的处理程式码。
// 滑鼠按下的事件处理函式
// 1. 将旗标设为True
// 2. 记录原点位置
void __fastcall TForm1::FormMouseDown(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
m_bDraw = TRUE;
m_nOrgX=X;
m_nOrgY=Y;
}
// 滑鼠移动的事件处理函式
// 1. 判断旗标是否为True。若是则进行以下动作。
// 2. 移动至原点。
// 3. 画一条由原点至目前所在点的线条。
// 4. 更改原点位置。
void __fastcall TForm1::FormMouseMove(TObject *Sender,
TShiftState Shift,
int X, int Y)
{
if (m_bDraw)
{
Canvas->MoveTo(m_nOrgX,m_nOrgY);
Canvas->LineTo(X,Y);
m_nOrgX = X;
m_nOrgY = Y;
}
}
// 滑鼠放开的事件处理函式
// 1. 判断旗标是否为True。若是则进行以下动作。
// 1. 将旗标设为 False。
// 2. 画线并记录原点位置(非必要)。
void __fastcall TForm1::FormMouseUp(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
if (m_bDraw)
{
m_bDraw=FALSE;
Canvas->MoveTo(m_nOrgX,m_nOrgY);
Canvas->LineTo(X,Y);
m_nOrgX = X;
m_nOrgY = Y;
}
}
将滑鼠事件处理函式做以上的修改之後,我们就完成了一个基
本的涂鸦程式的雏形了。喂?
XX-03 绘图物件的定义
至目前为止,我们已经完成了一个简单的涂鸦程式,接下来,
我希望将程式扩充为一个一般的绘图程式,它必须具备基本的
画线、画圆、画方等功能。为了要实作出这些功能,我们必须
先定义我们的绘图物件。
XX-03-01 绘图物件之始CShape
class CShape
{
protected:
TCanvas* m_pCanvas;
TColor m_Color;
int m_nWidth;
public:
CShape(TCanvas* pCanvas) {m_pCanvas = pCanvas;}
virtual ~CShape() {}
virtual void OnMouseMove(int,int)=0;
virtual void OnMouseDown(int,int)=0;
virtual void OnMouseUp(int,int)=0;
};
我们首先定义一个CShape类别,它是所有绘图物件之始,也
因此它定义了一个绘图物件的基本行为。在此绘图程式中我希
望它可以处理三个不同的滑鼠事件并加以处理之,所以我在
CShape中定义了三个相对应的成员函式,而且它们都是纯虚拟
函式,表示所有继承自CShape的类别都必须改写此三个成员
函式。
(关於物件导向的关念请叁阅 <必要的C++ 基础章节> 或是相
关书籍,在此尽作简短的解释)。这三个函式名称称如下:
virtual void OnMouseMove(int,int)=0;
virtual void OnMouseDown(int,int)=0;
virtual void OnMouseUp(int,int)=0;
另外我们再定义一般性的绘图物件都会用到的基本特性,如颜
色及线条宽度,再加上绘图时所需要的 Canvas,如此就组成
了CShape的类别定义:
TCanvas* m_pCanvas; // 绘图所需的Canvas
TColor m_Color; // 颜色
int m_nWidth; // 宽度
至於CShape的解构函式为何也设成virtual呢?这关系到继
承物件的毁灭方法。若是基础类别的解构函式没有定义成虚拟
函式时,会造成特定情况下,子类别的解构函式没有被呼叫到
的情形:
如:
CLine *pLine = new Line;
CShape* pShape=pLIne;
delete pShape;
上述的例子因为CLine为CShape的子类别,因此可以直接将
pShape指标指向pLine,然而在後面delete
pShape时,若是pShape的解构函式不为虚拟函式,会造成pLine
的解构函式不被呼叫到。这是一般C++ 程式设计时很容易犯的
错误。
我们可以将以上的经验法则归纳成一个原则,即是:只要该类
别有可能被继承,就必须将其解构函式设为虚拟函式。如此就
有了以下的定义了:
CShape(TCanvas* pCanvas) {m_pCanvas = pCanvas;}
virtual ~CShape() {}
CShape的建构函式必须传入Canvas以便绘图,而解构函式则
不做任何事,只将其定义为虚拟函式。
XX-03-02 CLine类别定义及实作-画直线的类别
class CLine : public CShape
{
public:
POINT m_ptMove;
POINT m_ptOrigin;
public:
CLine(TCanvas* pCanvas):CShape(pCanvas) {}
virtual ~CLine() {}
virtual void OnMouseMove(int,int);
virtual void OnMouseDown(int,int);
virtual void OnMouseUp(int,int);
};
我们将CLine定义为一个画直线的类别,而我们希望在画直线
时可以在拖弋滑鼠时将原先的线条擦去,并画出新的线,因此
我们必须宣告两个点来记载滑鼠按下的点及上次的点以便擦去
原来的线条。
以下就是CLine对於三个滑鼠事件的处理函式:
// 滑鼠按下的事件处理函式
// 1. 设定原点及上个启始点为目前所在点。
// 2. 移动至目前所在点。
void CLine::OnMouseDown(int x,int y)
{
m_ptOrigin.x = m_ptMove.x = x;
m_ptOrigin.y = m_ptMove.y = y;
m_pCanvas->MoveTo(x,y);
}
// 滑鼠移动事件处理函式
// 1.将画笔模式设为XOR模式,以便擦去上一条线。
// 2.擦去原来的线(以XOR模式再画一次就会擦去了)
// 3.在目前的位置画出一条新线。
// 4.更新坐标并改变画笔模式。
void CLine::OnMouseMove(int x,int y)
{
m_pCanvas->Pen->Mode = pmXor;
m_pCanvas->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
m_pCanvas->LineTo(m_ptMove.x,m_ptMove.y);
m_pCanvas->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
m_pCanvas->LineTo(x,y);
m_ptMove.x = x;
m_ptMove.y = y;
m_pCanvas->Pen->Mode = pmCopy;
}
// 滑鼠放开事件处理函式
// 1.画出原点至目前点的直线。
void CLine::OnMouseUp(int x,int y)
{
m_pCanvas->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
m_pCanvas->LineTo(x,y);
}
这就是画直线类别的定义及实作内容。
XX-03-03 CPolyline类别定义及实作 - 画随意线的类别
class CPolyline : public CShape
{
public:
POINT m_ptOrigin;
public:
CPolyline(TCanvas* pCanvas):CShape(pCanvas) {}
virtual ~CPolyline() {}
virtual void OnMouseMove(int,int);
virtual void OnMouseDown(int,int);
virtual void OnMouseUp(int,int);
};
CPolyline类别其实和我们前面所写的涂鸦程式的行为模式极
为类似,所以我就简单带过好了。
void CPolyline::OnMouseDown(int x,int y)
{
m_ptOrigin.x = x;
m_ptOrigin.y = y;
m_pCanvas->MoveTo(x,y);
}
void CPolyline::OnMouseMove(int x,int y)
{
m_pCanvas->LineTo(x,y);
}
void CPolyline::OnMouseUp(int x,int y)
{
m_pCanvas->LineTo(x,y);
}
XX-03-04 CPolygon类别定义及实作 - 画多边形的类别
class CPolygon : public CPolyline
{
public:
CPolygon(TCanvas* pCanvas):CPolyline(pCanvas){}
virtual ~CPolygon() {}
virtual void OnMouseUp(int,int);
};
CPolygon是CPolyline的子类别,其差别仅在於它会将首尾两
点连接,使其成为一个多边形,因此我们就直接由CPolyline
继承而来,只改写其OnMouseUp成员函式即可。
void CPolygon::OnMouseUp(int x,int y)
{
m_pCanvas->MoveTo(m_ptOrigin.x,m_ptOrigin.y);
m_pCanvas->LineTo(x,y);
}
XX-03-05 CRectangle类别定义及实作 - 画矩形的类别
class CRectangle : public CShape
{
public:
POINT m_ptMove;
POINT m_ptOrigin;
public:
CRectangle(TCanvas* pCanvas):CShape(pCanvas) {}
virtual ~CRectangle() {}
virtual void OnMouseMove(int,int);
virtual void OnMouseDown(int,int);
virtual void OnMouseUp(int,int);
};
画矩形类别其实和画线类别有些类似,它们同样必须记载上次
滑鼠移动的点,并擦掉原来的图形画出新的图形,所以我只针
对其相异的部份加以说明之:
// 滑鼠移动事件处理函式
// 原理和CLine类似,只不过改成画矩形。
void CRectangle::OnMouseMove(int = pmXor;
m_pCanvas-
>Rectangle(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMo
ve.y);
m_ptMove.x = x;
m_ptMove.y = y;
m_pCanvas-
>Rectangle(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMo
ve.y);
m_pCanvas->Pen->Mode = pmCopy;
}
XX-03-06 CRoundRect类别定义及实作 - 画圆矩形的类别
class CRoundRect : public CShape
{
public:
POINT m_ptMove;
POINT m_ptOrigin;
public:
CRoundRect(TCanvas* pCanvas):CShape(pCanvas) {}
virtual ~CRoundRect() {}
virtual void OnMouseMove(int,int);
virtual void OnMouseDown(int,int);
virtual void OnMouseUp(int,int);
};
CRoundRect的实作几乎和Crectangle相同,只不过它们呼叫
不同的API罢了,CRoundRect是以Canvas->RoundRect来画出
图形的。
void CRoundRect::OnMouseMove(int x,int y)
{
m_pCanvas->Pen->Mode = pmXor;
m_pCanvas-
>RoundRect(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMo
ve.y,4,4);
m_ptMove.x = x;
m_ptMove.y = y;
m_pCanvas-
>RoundRect(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMo
ve.y,4,4);
m_pCanvas->Pen->Mode = pmCopy;
}
XX-03-07 CEllipse 类别定义及实作 - 画圆形的类别
画圆形的处理和画矩形也大致相同,因为在Windows中是以包
围矩形来定义一个圆形,因此和CRoundRect相同的,我们只
要改写成画圆函式即可。其馀我就不多说了。
class CEllipse : public CShape
{
public:
POINT m_ptMove;
POINT m_ptOrigin;
public:
CEllipse(TCanvas* pCanvas):CShape(pCanvas) {}
virtual ~CEllipse() {}
virtual void OnMouseMove(int,int);
virtual void OnMouseDown(int,int);
virtual void OnMouseUp(int,int);
};
XX-03-08小结
以上就是此绘图程式中所使用的各个物件的定义,此乃血统纯
正的C++
写法的程式,不像C++Builder官方的范例是由Delphi的范例
修改而来,充满了Object Pascal的味道。
若你对C++ 尚不太熟悉的话,请你一定要细细领略以上的精神。
因为它是C++ 式的物件导向程式最基本且精要的精神所在,当
你了解了以上的精神,你就可谓掌握了C++
的封装、继承、及动态连结这三把权仗的基本精神。
至於C++ 老手,以上的定义都是很自然就可以接受的。也许有
人会质疑以上的物件定义并未考虑到物件的永续性 (Object
Persistence)。没错,不过这并不是我疏忽了,而是在本章的
程式中图形的存取是以Timage来存取,因此所有向量式的物
件都已转化成点阵图了,自然不需考虑到物件的储存问题。
在後续章节,我还会再针对物件的永续性来做一讨论。现在我
们先就TImage的点阵图存取方式为平台讨论之。
最後,在完成了物件的定义之後,我们再将程式根据物件导向
的方式再加以改写之。因为我目前尚未加入选择物件的方法,
所以我只能用预设物件型态的方式来展示程式的结果。
// 表格建构函式,设定m_bDraw旗标初值
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
m_bDraw = FALSE;
}
// Form的OnCreate事件处理函式。Form建立时引发。
// 1.设定笔的颜色及宽度。
// 2.产生一个CLine绘图物件。
// 注:你可以自行修改CLine成CPolyline、CPolygon、CRect
等值。
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Canvas->Pen->Color = clRed;
Canvas->Pen->Width = 2;
m_pObj = new CLine(Canvas);
}
// Form的OnClose事件处理函式。Form关闭时引发。 标设为 TRUE。
// 2.呼叫绘图物件的OnMouseDown函式。
void __fastcall TForm1::FormMouseDown(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
m_bDraw = TRUE;
m_pObj->OnMouseDown(X,Y);
}
// 更改後的OnMouseMove物件处理函式。
// 1.判断m_bDraw旗标是否为 TRUE。
// 2.若是则呼叫绘图物件的OnMouseMove函式。
void __fastcall TForm1::FormMouseMove(TObject *Sender,
TShiftState Shift,
int X, int Y)
{
if (m_bDraw)
m_pObj->OnMouseMove(X,Y);
}
// 更改後的OnMouseUp物件处理函式。
// 1.将m_bDraw旗标设为 FALSE。
// 2.若是则呼叫绘图物件的OnMouseUp函式。
void __fastcall TForm1::FormMouseUp(TObject *Sender,
TMouseButton Button,
TShiftState Shift, int X, int Y)
{
m_bDraw = FALSE;
m_pObj->OnMouseUp(X,Y);
}
瞧!这就是更改後的程式,是不是变得格外简洁呢?除此之外,
它最大的优点在於,无论我们日後加入了多少绘图物件,你都
不需再修改以上程式中关於绘图物件的处理部份,只要再自行
增加一个物件宣告即可。和原先Borland公司产品内附由Object
Pascal修改而来的范例,它的C++ 血统纯正多了。而且若是
日後你想要将其修改成为一个物件式的绘图系统,也只需要很
简单的修改而已。
好吧!让我们先检阅现在的成果。
图XX-04 CLine绘图物件范例。
图XX-05 CPolyline绘图物件范例(将程式改成new CPolyline)
XX-04 工具列(ToolBar)的使用
到目前为止我们已经将所有绘图物件定义完成,因此理论上你
的程式应该可以画出各种不同的绘图物件了。但是前面我提
到,目前我们尚未将绘图物件的选择功能实作出来,因此我们
是以直接修改程式的方式来绘制不同的图形。这是为了说明方
便的权宜之计。
在一般的绘图程式中都是以工具列的方式来实作出绘图功能的
切换功能,如Windows 95内的小画家即是一典型例子。因此
接下来我就为你说明在C++
Builder中实作出工具列的方法。
图XX-06小画家使用的工具列
在C++ Builder中实作工具列的方式和其他的程式如Visual
C++,Borland C++ 不同。後两者都是直接使用Windows
95内建的工具列型别来达到此功能。然而在C++ Builder中因
为有一种更为简单且直接的方式来做到,因此就不采用上述作
法 (当然C++
Builder也可以用Windows 95内建的ToolBar型别,只是用法
较为复杂。)。
那麽在C++ Builder中是如何来实作出工具列呢?说穿了其实
很简单:那就是利用TPanel和TSpeedButton。
CPanel是一个多功能的容器元件,因此我们可以用它来做为工
具列的平台,使用CPanel是因为它是少数几个可做为容器元
件的元件,所以它会自动调整置於其上的软体元件的位置,因
此很适合做为放置工具列的平台。
注:在C++ Builder的程式模式中大量使用TPanel来做为容
器元件。它除了可以做为ToolBar的平台外,另外如状态列
(StatusBar)也可以用它来完成,而且它也可以用来做为画面
分割的工具,来达成在MFC中类似分割视窗(Splitter Window)
效果。
TSpeedButton快速按钮元件在功能上本来就和工具列有几分类
似,现在我们可以将相同属性的快速按钮元件整合在一个
TPanel中即可完成我们所要的工具列了。
最後我再将工具列的作法按部就班详述之:
(1) 在表格上加入TPanel元件。
晌颐窍Mぞ吡兄渺侗砀裆戏剑越?
定为浮贴於表格的上方。如此一来当表格大小改变时,工具列
的宽度为跟着改变,而高度则维持原先的高度。
将TSpeedButton加入TPanel原件上。
你可以在表格中加入多个上述的工具列,它们会依序自动排列
於表格的上方,因此你不需费心去处理这些额外的动作。
XX-04-01 TSpeedButton 元件解析及设定
ToolBar的几个基本要素是:
(1) 代表该功能的图形。
(2) 可依状况切换其状态。
当使用者点取该功能时,必须执行该功能。
我们来看看TSpeedButton如何达到以上的要求。
首先,TSpeedButton具备Glyph性质,可以指定其图形,所以
第一个要求不成问题,再来TSpeedButton具备以下三种状态:
一般按钮的功能。
可以除能/致能。
具备群组特性。(也就是说同一群组的TSpeedButton会互
相影响,因此可轻易做出互斥的功能,以绘图程式为例,一次
只能使用一种工具,因此当使用者选取工具时,除了被选取的
工具之外,其他的工具应该都呈浮起状态)
由上可知,TSpeedButton确实是实作ToolBar的适当人选。
XX-04-02将SpeedButton加入ToolBar
依照我们先前的需求,我们需要一个可以切换绘图工具的工具
列,因此我们就照前面所说的方式来完成它。
图XX-07工?
为SpeedButton命名。取一个有意义的名字。
依需要设定其高度及位置。
设定图形。
设定SpeedButton状态初值。
设定群组特性。
◎ 设定事件处理函式。
SpeedButton的命名原则和一般变数的命名原则相同,简单明
了就好。以本程式为例::PolygonButtonClick(TObject
*Sender)
{
delete m_pObj;
m_pObj = new CPolygon(Canvas);
}
//----------------------------------------------------
-----------------
void __fastcall TGraphEx::RectangleButtonClick(TObject
*Sender)
{
delete m_pObj;
m_pObj = new CRectangle(Canvas);
}
//----------------------------------------------------
-----------------
void __fastcall TGraphEx::EllipseButtonClick(TObject
*Sender)
{
delete m_pObj;
m_pObj = new CEllipse(Canvas);
}
//----------------------------------------------------
-----------------
void __fastcall TGraphEx::RoundRectButtonClick(TObject
*Sender)
{
delete m_pObj;
m_pObj = new CRoundRect(Canvas);
}
//----------------------------------------------------
-----------------
在完成了以上的设定之後,此程式就具备了利用绘图工具列来
切换绘图工具的功能。
图XX-10具备绘图工具列的绘图程式范例。
--
※ 来源:·BBS 荔园晨风站 bbs.szu.edu.cn·[FROM: 192.168.1.118]
[回到开始]
[上一篇][下一篇]
荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店