0.前言
这些教程假设您是Code::Blocks 或 wxWidgets 的初学者,事实上,它们也是学习它们的好教程;假设您对 C++ 有基本的了解,您能够看懂教程中的C++实例代码。
这套教程的其他文章可以从以下文章中找到:
在CodeBolcks下wxSmith的C++编程教程——wxSmith教程目录(序言) - lexyao - 博客园
编写这篇文章参照了Code::Blocks 用户文档中wxSmith教程的以下文章:
WxSmith tutorial: Creating items with custom paint and mouse handling - Code::Blocks
在这篇文章中你将看到以下内容:
- 使用自定义绘制程序创建项目
- 创建简单资源并覆盖绘制方法
- 使用鼠标取点绘制曲线
- 绘制图形常用的函数
- 结束语
1.使用自定义绘制程序创建项目
从Code::Blocks 用户文档中wxSmith教程(原英文版教程)中表达的意思来看,原作者是想编写一个绘制图形的教程,其中也包括使用鼠标事件。可惜的是原作者只写了一个开头,写出完整的教程。
按着这篇教程的立意,要写的内容也是非常有用的,所以我决定根据我的一些猜想写出教程后续的内容 —— 当然,只是从使用的角度出发确定教程的内容,不一定是原教程作者的本意。
我们已经在之前的教程中学习了使用 wxSmith 的好技巧,现在是时候做更好的了。通常在编写一些更高级的应用程序时,有时发现 wxWidgets 提供的组件不符合我们的目的,尤其是当我们需要以图形形式呈现一些数据时。希望与几乎所有 GUI 工具包一样,wxWidgets 也为此做好了准备,并使我们能够创建具有自定义绘图和鼠标处理的项目 —— 这允许我们创建任何我们喜欢的项目。
在我们开始创建自定义项目之前,需要一些背景知识。 您应该知道的第一件事是 wxWidgets 如何绘制项目。
大多数项目都有操作系统或系统中可用的其他工具包(如 gtk+)提供的自定义绘制程序。对于此类项目,当作系统发布重新绘制项目的请求时,此请求要么由 wxWidgets 将消息转发回系统,要么甚至在没有任何 wxWidgets 干预的情况下完成整个过程。不幸的是,这意味着我们无法替换大多数项目的绘制程序,即使它在某些平台上有效,但在其他平台上也无法使用。在 wxSmith 中可用的项目中,只有 wxPanel 可以保证使用自定义绘制程序。
现在,如果我们在 wxPanel 上覆盖绘画,wxWidgets 将在每个绘画请求上发布两个事件:
- 擦除背景事件 (EVT_ERASE_BACKGROUND)
- 绘制事件 (EVT_PAINT)
第一个用于在添加内容之前准备我们的项目所占用的区域,第二个用于进行实际绘图。我们不必覆盖这两个事件,因此我们可以只绘制自定义背景或仅绘制自定义内容,而将背景保留为默认背景。
在编写自定义绘制方法之前,您应该知道的第二件事是 wxWidgets 使用 wxDC 对象进行绘制。在擦除背景事件的情况下,此对象在事件中提供;对于 Paint 事件,我们必须手动创建此对象。要获取有关 wxDC 和绘图函数的更多信息,请查看 wxWidgets 文档中的此页面。这个页面中有wxWidgets 2.8.8中wxDC 的介绍,你也可以在往上获得其他版本的文档。
2.创建简单资源并覆盖绘制方法
像往常一样,我们将从一个空应用程序开始,项目名称为tu07。项目只有一个简单的窗口,出于我们的目的,我们需要在窗口中添加一个 wxPanel 作为我们绘制图形的场所。用鼠标调整wxPanel的大小,为我们绘制图形留出空间:
现在让我们转到属性浏览器中的事件选项卡。您可能会看到 wxPanel 可以处理比其他项目更多的事件。这是因为它可以用作自定义项的基础,这些项可能具有任何标准事件的自定义处理程序。好的,让我们找到EVT_PAINT方法(它应该在顶部)并添加一个新的空事件。
我们应该在处理程序中做的第一件事是创建我们将使用的 wxDC 对象。如果您查看 wxWidgets 文档,您可能会读到不这样做会导致 wxWidgets 进入无限循环,因为 paint 方法将被一遍又一遍地调用。
我们可以在这里使用几种类型的 wxDC 对象。最常用的是 wxPaintDC:
void tu07Frame::OnPanel1Paint(wxPaintEvent& event) {wxPaintDC dc( Panel1 ); }
你可以看到我们传递了面板的指针作为参数 - wxPaintDC 需要一个指向我们将在其构造函数中绘制的项目的指针。请记住不要使用 this 指针。
现在让我们添加一些绘图代码 - 只是一些对角线来测试绘图是否有效:
void tu07Frame::OnPanel1Paint(wxPaintEvent& event) {wxPaintDC dc( Panel1 );dc.DrawLine( 0, 0, 100, 100 ); }
DrawLine( 0, 0, 100, 100 )是最简单的绘图函数,它的意思是绘制一条从坐标(0,0),到(100,100)的直线。DrawLine函数的定义如下:
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
现在,我们构建并运行应用程序,窗口将如下所示:
看到这个画面,说明我们已经能够在面板上绘制图形了。为了更好地理解坐标的含义,我们可以尝试修改坐标值,多绘制几条直线,看是不是达到了我们预期的效果。比如我们把绘图直线的函数重复使用三次:
dc.DrawLine(0, 0, 100, 100);dc.DrawLine(100, 100, 200, 0);dc.DrawLine(200, 0, 300, 100);
构建并运行应用程序,看增加的直线绘制的位置是不是我们预想的位置?
3.使用鼠标取点绘制曲线
鼠标是我们常用的取点工具,wxPanel有多个鼠标触发的事件。在这里我们将使用鼠标左键按下时鼠标的位置作为绘制直线的端点。为此我先准备几个变量和代码。
在tu07Main.app中添加以下代码:
1 int nCount = 5, nPos = 0; 2 wxPoint points[nCount]; 3 4 void tu07Frame::OnPanel1Paint(wxPaintEvent& event) 5 { 6 wxPaintDC dc(Panel1); 7 dc.DrawLine(0, 0, 100, 100); 8 dc.DrawLine(100, 100, 200, 0); 9 dc.DrawLine(200, 0, 300, 100); 10 dc.DrawPolygon(nCount, points); 11 }
其中,dc.DrawPolygon绘制拟合曲线,nCount指定顶点的个数,points指定顶点的坐标。
构建并运行应用程序,虽然没有看到绘制的曲线,却也没有出现错误,这说明代码没有问题,只是还没有设置坐标数据。
怎么设置坐标数据呢?我是这样想的:
- 在窗口中用鼠标点击,取得鼠标点击位置的坐标保存在变量points[nPos]中。
- 得到一个点的坐标后将nPos+1,这样下一次就会设置另一个顶点,如此循环。
下面我们添加响应鼠标点击事件的代码:选择面板wxPanel,在属性浏览器中切换到事件页面,找到EVT_LEFT_DOWN事件,添加新的事件处理程序,并输入以下代码:
1 void tu07Frame::OnPanel1LeftDown(wxMouseEvent& event) 2 { 3 points[nPos] = event.GetPosition(); 4 nPos++; 5 if(nPos >= nCount)nPos = 0; 6 Refresh(); 7 }
上述代码中第3行取得鼠标点击时的坐标位置保存到变量points中;第4、5行指定下一个要修改的顶点;第6行刷新窗口让程序执行OnPanel1Paint把改变数据后的图形画出来。
如果有兴趣,你也可以尝试添加一条语句,使用points[nPos]为圆心画一个小的圆圈用来标记下一个要改变位置的顶点。
4.绘制图形常用的函数
在程序中通过右键菜单查找函数DrawLine的定义可以找到dc.h文件中wxDC类的定义,从中可以看到可以使用的绘制图形以及与绘制图形有关的函数。
也可以打开以下网址查看其中关于wxDC成员函数的介绍:
wxDC
5.结束语
绘制图形是很有用的功能,在这一篇教程中讲述了绘制图形的基础知识,希望能起到抛砖引玉的效果。