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_PEN
,wxGREEN_PEN
,wxBLUE_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,来平铺当前控件的背景的,这是框架的机制决定的。
要达到这个目标,子窗口必须获取那张图片,然后计算当前控件所占的位置大小,来裁剪那张图片作为背景的位图画刷,擦除背景。