具体而微的绘图程式-c++ Borland 入门
2008-03-08 21:40:51 来源:WEB开发网 銆�

核心提示:在本章中我将为你示范如何在C++Builder中撰写一个完整的绘图程式,藉由这个程式的撰写,具体而微的绘图程式-c++ Borland 入门,你会更加了解C++Builder的 Canvas 绘图精神,而在撰写这个程式的同时,//-void __fastcall TGraphEx::LineButtonClick(TO
在本章中我将为你示范如何在C++Builder中撰写一个完整的绘图程式。藉由这个程式的撰写,你会更加了解C++Builder的 Canvas 绘图精神,而在撰写这个程式的同时,我们也可将相关的技术做一个整体的检阅。此绘图程式的执行结果如下:

void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { Canvas->LineTo(X,Y); } 在写完了以上两个事件处理函式之後,我们就可以在Form上面作画了,你可以用滑鼠在Form上面拖戈出一条条直线。其执行结果大致如图XX-01:


- 滑鼠移动时,判定滑鼠按下的旗标是否设为 True,若为 True,则移动至原点,并画一条由原点至目前所在点的线。同时更新原点位置至目前所在之点。
- 滑鼠放开时,将记录滑鼠按下的旗标设为False。

{ 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 x,int y) { m_pCanvas->Pen->Mode = pmXor; m_pCanvas->Rectangle(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMove.y); m_ptMove.x = x; m_ptMove.y = y; m_pCanvas->Rectangle(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMove.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_ptMove.y,4,4); m_ptMove.x = x; m_ptMove.y = y; m_pCanvas->RoundRect(m_ptOrigin.x,m_ptOrigin.y,m_ptMove.x,m_ptMove.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关闭时引发。 // 1.杀掉绘图物件。 void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { delete m_pObj; } // 更改後的OnMouseDown物件处理函式。 // 1.将m_bDraw旗标设为 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++ 血统纯正多了。而且若是日後你想要将其修改成为一个物件式的绘图系统,也只需要很简单的修改而已。 好吧!让我们先检阅现在的成果。



- 设定TPanel的Align性质为 alTop。因为我们希望工具列置於表格上方,所以将它设定为浮贴於表格的上方。如此一来当表格大小改变时,工具列的宽度为跟着改变,而高度则维持原先的高度。
- 将TSpeedButton加入TPanel原件上。
- 当使用者点取该功能时,必须执行该功能。
- 一般按钮的功能。
- 可以除能/致能。
- 具备群组特性。(也就是说同一群组的TSpeedButton会互相影响,因此可轻易做出互斥的功能,以绘图程式为例,一次只能使用一种工具,因此当使用者选取工具时,除了被选取的工具之外,其他的工具应该都呈浮起状态)

- 为SpeedButton命名。取一个有意义的名字。
- 依需要设定其高度及位置。
- 设定图形。
- 设定SpeedButton状态初值。
- 设定群组特性。
◎ 设定事件处理函式。 SpeedButton的命名原则和一般变数的命名原则相同,简单明了就好。以本程式为例,我们就可以LineButton、RectangleButton等名字命名之。命名时只要改变SpeedButton的Name性质即可。 至於设定图形,只要先选取该SpeedButton,然後至物件检视器点取Glyph性质,然後将欲选取的点阵图Load进来,即可完成设定图形的程序了。


在完成了以上的设定之後,此程式就具备了利用绘图工具列来切换绘图工具的功能。

[]
赞助商链接