0.前言
这些教程假设您是Code::Blocks 或 wxWidgets 的初学者,事实上,它们也是学习它们的好教程;假设您对 C++ 有基本的了解,您能够看懂教程中的C++实例代码。
这套教程的其他文章可以从以下文章中找到:
在CodeBolcks下wxSmith的C++编程教程——wxSmith教程目录(序言) - lexyao - 博客园
编写这篇文章参照了Code::Blocks 用户文档中wxSmith教程的以下文章:
WxSmith tutorial: Drawing on the Screen and Saving Drawings - Code::Blocks
在这篇文章中你将看到以下内容:
- 关于在屏幕上绘图和保存绘图
- 在屏幕上绘图
- 将图片另存为 PNG 和 JPEG 文件
- 可展开的图
- 结束语
1.关于在屏幕上绘图和保存绘图
上一篇教程中我们讲述了基础的绘制图形的操作,在这一篇教程中我们将讲解更多的图形绘制知识。
在本教程中,我们将在屏幕上绘制如下图,并将其保存为.jpg和.png文件。
或许这篇教程的英文版(Code::Blocks 用户文档中wxSmith教程)的作者是俄罗斯人,他把图片中的方块解释为俄罗斯元素,而第二行的西里尔文字“Красная площадь”的意思是红场(莫斯科红场)。我在图片中添加了一行中文,哈哈哈。其实,图片中有什么不重要,重要的是我们以此为例讲述绘图的方法。
在 wxWidgets 中,人们总是在某种设备上下文上绘制一个图形。从使用 wxWidgets 的程序员的角度来看,设备上下文是一个黑匣子,它使我们不必知道如何将图形发送到打印机、屏幕、位图和.jpg文件的详细信息。完全相同的代码创建用于所有三个输出设备的图形。让我用示意图代码来说明我们的例子。
像往常一样,我们将从一个空应用程序开始,项目名称为tu08。在项目的主窗口中添加三个按钮,这篇教程中绘制图形的操作将通过点击这三个按钮启动。
构建并运行项目,确保项目能够正常构建、运行。运行后看到的主窗口如下图所示:
2.在屏幕上绘图
为了在屏幕上显示图表,我们需要一个框架中的面板,因此在 Code::Blocks 主菜单上单击 wxSmith 项并选择“添加 wxFrame”。当窗口出现询问类名并建议“NewFrame”时,让我们将其命名为“PictureFrame”(只要是合法的名字就行,你也可以用其他的合法的名字)。接受建议的其他默认值,然后完成添加框架。
在进一步讨论之前,我们必须修复当用户尝试关闭此框架时会发生什么。双击PictureFrame,此时将显示与框架关联的 C++ 代码。在文件底部,您应该会看到以下行:
void PictureFrame::OnClose(wxCloseEvent& event){}
我们希望当用户尝试关闭 PictureFrame 窗口时不能关闭主窗口。如果我们像以前一样将 Close() 命令放入大括号之间的正文中,则关闭此窗口将关闭整个应用程序。相反,我们放置 Destroy(),它会清除当前窗口,但不会关闭整个程序。所以我们应该有:
void PictureFrame::OnClose(wxCloseEvent& event){Destroy();}
现在回到编辑区(单击 C++ 代码上方栏中的 PictureFrame.wxs),在PictureFrame 上放置一个 wxBoxSizer,并在 wxBoxSizer中放置一个wxPanel面板。我们将在此面板绘制图片以在屏幕上查看。在属性浏览器中,选中其“Expand”框,取消选中“默认大小”,然后将“宽度”填写为 310,“高度”填写为 210。(准备画一个 300 X 200 的矩形,所以这给了他在边缘周围有一点额外的空间。
现在,我们需要为此面板的 Paint 事件添加一些代码。因此,单击面板,单击属性浏览器上方的 {},找到EVT_PAINT(它应该在列表的顶部),单击它,然后单击行右边缘的向下箭头,然后选择添加新处理程序。接受建议的名称,然后单击确定。
您会发现自己回到了PictureFrame.cpp,并显示了以下用于编写代码来处理此事件的框架:
void PictureFrame::OnPanel1Paint(wxPaintEvent& event) { }
我们只需要在框架中间添加两行,如下所示:
void PictureFrame::OnPanel1Paint(wxPaintEvent& event) {wxPaintDC dc( Panel1 );Repin(dc); }
上面代码中有一个名为Repin的函数,这是我们给绘制图形的函数取的名字。以下是函数Repin的代码,将这些代码复制到PictureFrame.cpp文件中PictureFrame::OnPanel1Paint之前:
void Repin(wxDC &dc) {// 将设备上下文清除为全白dc.SetBrush(*wxWHITE_BRUSH);dc.Clear();// 创建一个宽5像素的蓝色画笔用于绘制边框。wxColor Blue(0, 0, 255);wxPen myBluePen(Blue, 10, wxSOLID);// 告诉dc使用它 dc.SetPen(myBluePen);// 用蓝色边框绘制一个矩形,矩形的内部用当前画刷填充,当前画刷是白色dc.DrawRectangle(0, 0, 300, 200);// 设置画刷和画笔为红色dc.SetBrush(*wxRED_BRUSH);dc.SetPen(*wxRED_PEN);// 绘制高和宽都是40像素的矩形,它的左上角坐标为10,10dc.DrawRectangle(10, 10, 40, 40);// 创建一支宽3像素的绿色画笔,用于绘制一条实线wxPen myGreenPen(*wxGREEN, 3, wxSOLID);// 告诉dc开始用这支笔画画。 dc.SetPen(myGreenPen);// 画一条水平线dc.DrawLine(55, 40, 290, 40);// 将文本前景设置为黑色dc.SetTextForeground(*wxBLACK);// 用默认的字体、字号绘制一些文字dc.DrawText(wxT("Красная площадь"), 50, 60);dc.DrawText(wxT("我爱北京天安门"), 50, 90);// 创建一个16磅的字体,既不粗体, 也不斜体,无下划线。wxFont BigFont(16, wxFONTFAMILY_ROMAN, wxNORMAL, wxNORMAL, false);// 告诉dc使用这个字体 dc.SetFont(BigFont);// 写下我们图片的标题。dc.DrawText(wxT("Red Square"), 60, 10); }
至此我们已经完成了PictureFrame 绘制图形的程序设计,下面我们将添加代码在主窗口中通过点击按钮“Show”将PictureFrame 显示出来。为此,单击代码编辑器上方行中的 tu08Frame.wxs以返回主窗口,双击“Show”按钮为其添加事件响应函数,并添加以下代码(在tu08frame.cpp中):
void tu08Frame::OnButton1Click(wxCommandEvent& event) {PictureFrame* frm = new PictureFrame(this);frm->Show(); }
在tu08frame.cpp中包含头文件PictureFrame.h:
#include "PictureFrame.h"
最后,我们构建并运行的程序,在应用程序的主界面点击“Show”按钮,将会看到PictureFrame运行后的画面,说明Repin能够正常工作了。
你会发现,每点击Show按钮一次就会打开一个PictureFrame窗口。你可以关闭PictureFrame窗口;关闭主窗口时还没有关闭的PictureFrame窗口也会一同关闭。
3.将图片另存为 PNG 和 JPEG 文件
我们希望运行时单击“Save”按钮时绘图保存为 PNG 和 JPEG 文件,需要为响应Save按钮的点击事件添加代码。
要将我们的绘图保存到文件中,我们需要做以下工作:
- 首先创建一个位图,
- 然后创建一个内存设备上下文(DC),
- 接着将位图交给内存DC用作绘画的纸张,
- 然后让Repin在上面绘图,
- 最后从DC中释放位图,并将其写入为文件。
在编辑器中双击“Save”按钮,在按钮的事件响应函数中添加以下代码:
void tu08Frame::OnButton2Click(wxCommandEvent& event) {// 创建一个宽 300 像素,高 200 像素的位图。// 称之为“纸”,因为我们将在上面书写。wxBitmap *paper = new wxBitmap(300, 200);// 创建一个内存设备上下文 wxMemoryDC memDC;// 告诉memDC在“纸上”写。memDC.SelectObject(*paper);// 调用Repin函数在memDC上画我们的图像 Repin(memDC);// 告诉 memDC 在一个虚假的位图上写入;// 这释放了“纸张”,以便它可以将自己写入文件。 memDC.SelectObject(wxNullBitmap);// 将“纸”中的内容放入一个png和一个jpeg文件中。paper->SaveFile(_T("RedSquare.png"), wxBITMAP_TYPE_PNG, (wxPalette*)NULL);paper->SaveFile(_T("RedSquare.jpg"), wxBITMAP_TYPE_JPEG, (wxPalette*)NULL);delete paper; }
构建并运行应用程序,单击Save按钮,然后打开应用程序tu08.exe所在的文件夹,你会看到两个图形文件。
打开这两个文件,你会看到里面正是我们使用Repin绘制的图像。
(为了成功编译上述内容,您需要添加一个函数原型,以便 Repin() 在tu08Main.cpp中可见。在 PictureFrame.h 的末尾,在结束标头保护之前添加行 void Repin(wxDC &dc); 。)
4.可展开的图
当我们运行时单击“Show”按钮打开PictureFrame窗口,我们可以拖动改变窗口的大小,但绘图的大小不受影响。
让我们为 Stretch 按钮添加事件响应代码,并绘制一个图形,当用户调整显示它的框的大小时,它确实会改变大小和比例。该图将是一个红色矩形,带有固定宽度的绿色边框。
和创建PictureFrame时一样,在 Code::Blocks 主菜单上,选择 wxSmith >>Add wxFrame;接受框架的建议名称,即 NewFrame。使用 Destroy() 填充 OnClose 代码框。在框架上,放一个盒子尺寸;在尺寸管理器中放置一个面板,在属性浏览器中将面板设置为 200 宽和 200 高。请务选中“Expand”属性。通过单击 {} 图标将属性浏览器更改为事件浏览器,找到 Paint 事件,单击行右侧的下拉箭头,然后选择“Add new handler”。在显示的代码框中,添加代码以获取以下内容:
void NewFrame::OnPanel1Paint(wxPaintEvent& event) {wxPaintDC dc( Panel1 );dc.SetPen( wxPen( *wxGREEN, 5 ) ); // 绿色的笔宽5像素dc.SetBrush(*wxRED_BRUSH);// 获取窗口尺寸wxSize sz = GetClientSize();// 我们的矩形尺寸wxCoord w = sz.x/2 , h = sz.y/2;// 将矩形居中在窗口上,但不要在负位置绘制。int x = wxMax(0, (sz.x - w)/2);int y = wxMax(0, (sz.y - h)/2);wxRect rectToDraw(x, y, w, h);dc.DrawRectangle(rectToDraw); }
此代码引入了几个新的 wxWidgets 结构:wxSize、wxCoord、wxRect 和 wxMax 函数。从名称和此处的使用方式中可以清楚地看出它们的含义。当然,C++ 函数 GetClientSize() 是使绘图大小取决于绘制窗口大小的关键。
现在回到主窗口的代码中,在顶部添加 #include“NewFrame.h”行。然后让 wxSmith 给你标有“Stretch”的按钮的代码框架。在该代码框架中添加行以使整体如下所示:
void tu08Frame::OnButton3Click(wxCommandEvent& event) {NewFrame* ffrm = new NewFrame(this);ffrm->Show();}
构建并运行应用程序。单击“Stretch”按钮。您应该得到带有绿色边框的红色矩形的图片。现在尝试更改窗口的大小,矩形应该按比例放大或缩小。
5.结束语
在这篇教程中,我们讲述了更多的绘制图形的方法,文字也是一种图形。
要了解更多的图形绘制的方法,可以通过多看多练达到自己的目的。