阿牧路泽

哪有那么多坚强,无非是死扛罢了
  博客园  :: 首页  :: 新随笔  :: 联系 :: 管理

10、wxWidgets 绘画wxClientDC wxPaintDC wxMemoryDC

Posted on 2018-09-10 15:44  阿牧路泽  阅读(3452)  评论(0编辑  收藏  举报

wxDC

1、所有的绘图设备类都是继承自wxDC。

2、关于坐标系

  默认的坐标原点在屏幕左上角,当然这是可以改变的,使用函数SetDeviceOrigin。此函数仅改变当前dc的坐标原点,一般用于打印文稿的时候,设置打印设备的原点。

void SetDeviceOrigin(wxCoord x, wxCoord y)

   类型wxCoord的原型是整形int,英文中是坐标的意思。当然坐标系的方向也是可以改变的,使用以下函数:

1 void SetAxisOrientation(bool xLeftRight, bool yBottomUp)

  第一个参数:为true时,从左向右,反之。。。第二个参数:为true时,从下向上。主要用途为股票趋势图这类和数学关系比较大的场合,这些要求左下角为原点,x轴向右,y轴向上。

3、绘图设备的大小(逻辑单位--像素与设备单位--毫米)

  按照像素单位获取设备的大小:GetSize

  按照毫米单位获取设备的大小:GetSizeMM

  获取设备每英寸的像素密度ppi:GetPPI

  获取设备每像素占的位宽:GetDepth

  更改逻辑单位与设备单位的缩放比例:SetUserScale

1     wxClientDC dc(this);
2     dc.SetMapMode(wxMM_TEXT);
3     dc.SetUserScale(1.0,1.0);

在wxMM_TEXT模式下,缩放比例1.0,1.0,可以将逻辑单位与设备单位等同。

4、区域绘图

  所谓区域绘图,是指定一个区域,所有超过这个区域的范围都将被忽略。一般情况下,在某个区域内不停的绘制文字时,会有重影的情况,需要不断擦除这个区域,重新绘制文字。

 1 // 鼠标移动事件响应
 2 void wxFontSelectorCtrl::OnMouseEvent(wxMouseEvent &event)
 3 {
 4     wxClientDC dc(this);
 5     // 设置一个矩形区域
 6     dc.SetClippingRegion(wxPoint(0,0),wxSize(200,20));
 7     // 清除之前绘制的文字
 8     dc.Clear();
 9     // 设置文字的前景色
10     dc.SetTextForeground(wxColour(255,255,0));
11     // 设置文字的字体
12     dc.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("宋体")));
13     wxPoint pt = event.GetPosition();
14     dc.DrawText(wxString::Format(wxT("当前坐标x-%d y-%d"), pt.x, pt.y),wxPoint(0,0));
15     dc.SetFont(wxNullFont);
16     // 销毁之前设置的区域
17     dc.DestroyClippingRegion();
18 }

上面的绘图会有闪烁的情况,可以使用双缓冲绘图,代码如下:

1     wxClientDC clientDC(this);
2     // 设置一个矩形区域
3     clientDC.SetClippingRegion(wxPoint(0, 0), wxSize(200, 20));
4 
5     wxBufferedDC dc(&clientDC);
6     ...

完整代码如下:

main.h

 1 #include <wx/wx.h>
 2 #include <wx/dcbuffer.h>
 3 //定义主窗口类
 4 class MyPanel : public wxPanel
 5 {
 6 public:
 7     MyPanel(wxFrame * frame, wxWindowID id);
 8 
 9     void OnMotion(wxMouseEvent & event);
10 };
11 
12 class MyFrame : public wxFrame
13 {
14 public:
15     MyPanel * panel1;
16     MyFrame(const wxString& title);
17 
18 };
19 
20 //定义应用程序类
21 class MyApp : public wxApp
22 {
23 public:
24     virtual bool OnInit();
25 };

main.cpp

 1 #include "main.h"
 2 
 3 MyPanel::MyPanel(wxFrame * frame, wxWindowID id)
 4     :wxPanel(frame, id)
 5 {
 6     Connect(wxEVT_MOTION, wxMouseEventHandler(MyPanel::OnMotion));
 7 }
 8 
 9 void MyPanel::OnMotion(wxMouseEvent & event)
10 {
11     wxClientDC clientDc(this);
12 
13     clientDc.SetClippingRegion(wxPoint(0, 0), wxSize(200, 20));
14 
15     wxBufferedDC dc(&clientDc);
16 
18     dc.Clear();
19 
20     dc.SetTextForeground(wxColor(0, 0, 0));
21 
22     dc.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("宋体")));
23     wxPoint pt = event.GetPosition();
24     dc.DrawText(wxString::Format(wxT("当前坐标x-%d, y-%d"), pt.x, pt.y), wxPoint(0, 0));
25     dc.SetFont(wxNullFont);
26 
27     dc.DestroyClippingRegion();
33 }
34 
35 MyFrame::MyFrame(const wxString& title)
36     :wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(400, 600))
37 {
38     MyPanel * panel1 = new MyPanel(this, wxID_ANY);
39 
40     Centre();//整个窗口在可视窗口中居中
41 }
42 
44 //声明应用程序
45 IMPLEMENT_APP(MyApp)
46 //初始化应用程序
47 bool MyApp::OnInit()
48 {
49     MyFrame *btnapp = new MyFrame(wxT("MyFrame"));
50     btnapp->Show(true);
51 
52     return true;
53 }

 

wxClientDC

  客户区绘图设备DC,用来在非重绘的事件处理函数中使用,即除了EVT_PAINT和EVT_NC_PAINT事件之外的都可以。

例如,在鼠标按住移动的时候,即拖拽状态下,绘制线条,代码如下:

 1 // 鼠标移动事件处理函数
 2 void wxFontSelectorCtrl::OnMotion(wxMouseEvent& event)
 3 {
 4     // 如果为拖拽状态
 5     if (event.Dragging())
 6     {
 7         wxClientDC dc(this);
 8         dc.SetPen(wxPen(*wxYELLOW, 1));//wxPen(*wxYELLOW, 1)定义一个画笔,颜色为黄色,画笔宽度为1
 9         dc.DrawPoint(event.GetPosition());//在鼠标的位置绘制一个点
10         dc.SetPen(wxNullPen);//清除画笔
11     }
12 }

客户区的绘图用的最多的就是背景绘制,这是与前景绘制EVT_PAINT相对应的,只有需要重绘时才发生,如下:

	EVT_ERASE_BACKGROUND(wxFontSelectorCtrl::OnErase)
 1 // 背景擦除事件处理函数
 2 void wxFontSelectorCtrl::OnErase(wxEraseEvent& event) {
 3     // 获取一个设备DC
 4     wxClientDC * clientDC = NULL;
 5     if(!event.GetDC()) clientDC = new wxClientDC(this);
 6     wxDC * dc = clientDC ? clientDC : event.GetDC();
 7     // 绘制黄色背景
 8     dc->SetBrush(wxBrush(wxColour(255,255,0)));
 9     wxSize sz = GetClientSize();
10     dc->DrawRectangle(wxRect(0,0,sz.x,sz.y));
11     dc->SetBrush(wxNullBrush);
12     // 清除可能创建的clientDC
13     if (clientDC) wxDELETE(clientDC);
14 }

wxPaintDC

  重绘前景的设备DC,只有当需要重绘时,才会发生重绘事件。什么叫需要重绘时?手动发出一个重绘事件:Reflash与ReflashRect这两个函数,如果没有立即重绘,可以强制调用Update函数。被动发出一个重绘事件:被别人挡住后,重新出现,或最小化后再重新出现,都会发生重绘事件。

    EVT_PAINT(wxFontSelectorCtrl::OnPaint)
 1 // 前景事件处理函数
 2 void wxFontSelectorCtrl::OnPaint(wxPaintEvent& event)
 3 {
 4     wxPaintDC dc(this);
 5     dc.SetPen(*wxBLACK_PEN);
 6     dc.SetBrush(*wxRED_BRUSH);
 7  
 8     // 判断这个区域是否需要重绘
 9     wxRect rectToDraw(0,0,100,100);//定义一个矩形区域,矩形的边长为100
10     if (IsExposed(rectToDraw)) {
11         dc.DrawEllipse(wxPoint(0, 0), wxSize(50, 50));//画一个原型,圆点位置为(50, 50)
12     }
13     dc.SetBrush(wxNullBrush);//清楚画刷颜色
14     dc.SetPen(wxNullPen);//请出去画笔颜色
15 }

  防止重绘事件闪烁,可以让擦除背景的函数为空,将前景与背景的绘制全部统一到前景中来,利用双缓冲绘图来实现。

 1 // 前景事件处理函数
 2 void wxFontSelectorCtrl::OnPaint(wxPaintEvent& event)
 3 {
 4     wxBufferedPaintDC dc(this);
 5  
 6     PrepareDC(dc);
 7     // 绘制背景色
 8     ...
 9     // 绘制前景色
10     dc.SetPen(*wxBLACK_PEN);
11     dc.SetBrush(*wxRED_BRUSH);
12  
13     dc.DrawEllipse(wxPoint(0, 0), wxSize(50, 50));
14     dc.SetBrush(wxNullBrush);
15     dc.SetPen(wxNullPen);
16 }

wxMemoryDC

  双缓冲绘图就是利用这个DC实现的,我们可以把所有的绘制,先在内存DC上绘制好,然后再输出到我们需要绘制的DC中。下面我们演示一个利用内存DC绘制一个位图的实现代码:

1     wxMemoryDC memDC;//创建一个内存设备上下文
2     wxBitmap bitmap(200,200);//使用当前的颜色深度创建一个200*200的位图
    //将创建的图片和内存设备上下文关联
3 memDC.SelectObject(bitmap);//SelectObject()函数的作用是把一个对象(位图、画笔、画刷等)选入指定的设备描述表,新的对象代替同一类型的老对象 4 memDC.SetBackground(*wxWHITE_BRUSH); 5 memDC.Clear(); 6 memDC.SetPen(*wxRED_PEN); 7 memDC.SetBrush(*wxTRANSPARENT_BRUSH); 8 memDC.DrawRectangle(wxRect(10,10,100,100)); 9 memDC.SelectObject(wxNullBitmap);//解除设备上下文和位图的关联

绘图工具

 1     1、wxColour(wxColour wc(255,0,0)红色),还有第4个参数,是apha通道
 2   //系统自带的颜色有:wxBLACK,wxWHITE, wxRED, wxBLUE, wxGREEN, wxCYAN,wxLIGHT_GREY,wxNullColour,wxSystemSettings::GetColour获取系统颜色(wxSYS COLOUR 3DFACE)
 3  4     2、wxPen(wxPen wp(颜色,宽度,线型))
 5     //系统的线型有:wxSOLID,wxTRANSPARENT,wxDOT,wxLONG_DASH,wxSHORT_DASH,wxDOT_DASH
 6     3、wxBrush(wxBrush wb(颜色,画刷类型))
 7     //画刷类型:wxSOLID,wxTRANSPARENT,wxBDIAGONAL_HATCH,wxCROSSDIAG_HATCH,wxSTIPPLE
 8     //系统画刷:wxGREEN BRUSH, wxWHITE BRUSH, wxBLACK BRUSH, wxGREY BRUSH,wxMEDIUM GREY BRUSH, wxLIGHT GREY BRUSH,wxtrANSPARENT BRUSH,wxNullBrush
 9     4、wxFont(wxFont font(16, wxFONTFAMILY_SWISS, wxNORMAL, wxBOLD, true,wxT("Consolas"), wxFONTENCODING_ISO8859_1);)
10     //也可以获取字体:wxFont* font = wxTheFontList->FindOrCreateFont(12, wxSWISS,wxNORMAL, wxNORMAL);
11     5、wxPalette(调色板,估计用的很少)

绘图原理

绘制图形

计算机绘制图像与人类画画有很多相似的地方。也需要画板、绘图工具(笔、刷子)。面板Panel就是我们的画板,绘图工具在程序中被称为设备上下文(Device Context,简称DC)。DC提供了绘制各种图像的方法。

在wxWidgets常用wxPaintDC绘制图像。它可以绘制各种图像。例如:直线、矩形、

说明

  • wxPoint表示一个二维坐标的点,构造函数是wxPoint(int x,int y)
  • wxCoord表示一个数字值,原型是整形int
  • wxSize表示宽高,构造函数是wxPoint(int width,int height)
注意,屏幕使用的坐标系被称为屏幕坐标系,默认的坐标原点在屏幕左上角。与我们数学中使用的笛卡尔坐标系是有区别的。
注意:如果两个图形存在重合的部分,后面绘制的会覆盖前面绘制的。

设置绘制

上面的图形都是黑线白底,线的宽度是1个像素,可以通过下面的方式修改。

 

说明

颜色设置有两种方式

  1、使用内置的宏定义。例如:wxRED_PENwxGREEN_PENwxBLUE_PEN等。

  2、 使用wxColour定义颜色,使用RGB模型。

注意
SetPen()不能改变文字的颜色,需要下面的使用SetFont()

1 // 画文字 
2 dc.SetTextForeground(wxColour(255,0 , 255));// 设置字体颜色 
3 
4 dc.SetFont(wxFontInfo(12).Bold(2).FaceName(wxT("MS yahei"))); // 设置字体大小,粗细,字体 
5 
6 dc.DrawText(wxT("测试文字"), wxPoint(200, 160));

设置字体颜色要使用SetTextForeground()方法。

绘制图片

绘制图片需要用到如下两个函数:

【示例】

1 // 绘制图片
2 wxInitAllImageHandlers();
3 dc.DrawBitmap(wxBitmap(wxT("logo.png"),wxBITMAP_TYPE_ANY),wxPoint(30,30));

鼠标事件

界面上的图像有一部分使用代码生成,也有一部分使用鼠标创建。添加鼠标事件方式与绘制事件一样。需要新建鼠标处理函数并且绑定到事件中。但是鼠标事件种类要比绘图事件多,常用的有如下几个。

【示例】

 1 #include <wx/wx.h>
 2 class Move : public wxFrame
 3 {
 4     public: Move(const wxString& title): wxFrame(NULL, wxID_ANY, title)
 5     {
 6         wxPanel* panel = new wxPanel(this, -1);
 7         st1 = new wxStaticText(panel, -1, wxT(""), wxPoint(10, 10));
 8         st2 = new wxStaticText(panel, -1, wxT(""), wxPoint(10, 30));
 9         panel->Bind(wxEVT_MOTION,&Move::OnMove,this);
10     }
11     void OnMove(wxMouseEvent & event)
12     {
13         wxPoint size = event.GetPosition();
14         st1->SetLabel(wxString::Format(wxT("x: %d"), size.x ));
15         st2->SetLabel(wxString::Format(wxT("y: %d"), size.y ));
16         event.Skip(); Update();
17     }
18 private:
19     wxStaticText *st1;
20     wxStaticText *st2;
21 };
22 class MyApp : public wxApp
23 {
24     public: virtual bool OnInit()
25     {
26         Move *move = new Move(wxT("Move event"));
27         move->Show(true);
28         return true;
29     }
30 };
31 IMPLEMENT_APP(MyApp)

前景与背景

关于这个问题,wxWidgets框架中比较难以理解,也不同于MFC,因为它是多平台兼容的。背景的擦除,默认情况下是会调用最近一次的SetBackgroundColour传入的颜色参数来擦除背景。这个最近一次,概念比较模糊,资料比较欠缺,我的理解是当前控件的直系亲属,比如它的父窗口,或者父窗口的父窗口,设置过Colour,那么默认就以这个Colour为准。这个擦除的动作,其实和wxDC的函数Clear是一样的,都是调用当前的背景画刷来擦除背景。也就是说,默认情况下,背景的擦除是调用的当前的背景画刷,而当前的背景画刷默认情况下是直系亲属的默认背景画刷。

如果你没有重写EVT_ERASE_BACKGROUND这个事件,那么可以在EVT_PAINT中如下擦除背景:

1     wxPaintDC dc(this);
2     dc.SetBackground(wxBrush(wxColour(200, 100, 10)));
3     dc.Clear();

效果是一样的,还可以把wxPaintDC换成wxBufferedPaintDC,解决闪烁的问题。

如果主窗口的背景是一张图片,而子控件想要达到透明的效果,是不能通过调用获取主窗口的DC,来平铺当前控件的背景的,这是框架的机制决定的。

要达到这个目标,子窗口必须获取那张图片,然后计算当前控件所占的位置大小,来裁剪那张图片作为背景的位图画刷,擦除背景。