1、所有的绘图设备类都是继承自wxDC。
2、关于坐标系
默认的坐标原点在屏幕左上角,当然这是可以改变的,使用函数SetDeviceOrigin。
此函数仅改变当前dc的坐标原点,一般用于打印文稿的时候,设置打印设备的原点。
void SetDeviceOrigin(wxCoord x, wxCoord y)
类型wxCoord的原型是整形int,英文中是坐标的意思。
当然坐标系的方向也是可以改变的,使用以下函数:
void SetAxisOrientation(bool xLeftRight, bool yBottomUp)
第一个参数:为true时,从左向右,反之。。。
第二个参数:为true时,从下向上。
主要用途为股票趋势图这类和数学关系比较大的场合,这些要求左下角为原点,x轴向右,y轴向上。
3、绘图设备的大小(逻辑单位--像素与设备单位--毫米)
按照像素单位获取设备的大小:GetSize
按照毫米单位获取设备的大小:GetSizeMM
获取设备每英寸的像素密度ppi:GetPPI
获取设备每像素占的位宽:GetDepth
更改逻辑单位与设备单位的缩放比例:SetUserScale
wxClientDC dc(this);
dc.SetMapMode(wxMM_TEXT);
dc.SetUserScale(1.0,1.0);
在wxMM_TEXT模式下,缩放比例1.0,1.0,可以将逻辑单位与设备单位等同。
4、区域绘图
所谓区域绘图,是指定一个区域,所有超过这个区域的范围都将被忽略。
一般情况下,在某个区域内不停的绘制文字时,会有重影的情况,需要不断擦除这个区域,重新绘制文字。
// 鼠标移动事件响应
void wxFontSelectorCtrl::OnMouseEvent(wxMouseEvent &event)
{
wxClientDC dc(this);
// 设置一个矩形区域
dc.SetClippingRegion(wxPoint(0,0),wxSize(200,20));
// 清除之前绘制的文字
dc.Clear();
// 设置文字的前景色
dc.SetTextForeground(wxColour(255,255,0));
// 设置文字的字体
dc.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("宋体")));
wxPoint pt = event.GetPosition();
dc.DrawText(wxString::Format(wxT("当前坐标x-%d y-%d"), pt.x, pt.y),wxPoint(0,0));
dc.SetFont(wxNullFont);
// 销毁之前设置的区域
dc.DestroyClippingRegion();
}
上面的绘图会有闪烁的情况,可以使用双缓冲绘图,代码如下:
wxClientDC clientDC(this);
// 设置一个矩形区域
clientDC.SetClippingRegion(wxPoint(0, 0), wxSize(200, 20));
wxBufferedDC dc(&clientDC);
...
客户区绘图设备DC,用来在非重绘的事件处理函数中使用,即除了EVT_PAINT和EVT_NC_PAINT事件之外的都可以。
例如,在鼠标按住移动的时候,即拖拽状态下,绘制线条,代码如下:
// 鼠标移动事件处理函数
void wxFontSelectorCtrl::OnMotion(wxMouseEvent& event)
{
// 如果为拖拽状态
if (event.Dragging())
{
wxClientDC dc(this);
dc.SetPen(wxPen(*wxYELLOW, 1));
dc.DrawPoint(event.GetPosition());
dc.SetPen(wxNullPen);
}
}
客户区的绘图用的最多的就是背景绘制,这是与前景绘制EVT_PAINT相对应的,只有需要重绘时才发生,如下:
EVT_ERASE_BACKGROUND(wxFontSelectorCtrl::OnErase)
// 背景擦除事件处理函数
void wxFontSelectorCtrl::OnErase(wxEraseEvent& event) {
// 获取一个设备DC
wxClientDC * clientDC = NULL;
if(!event.GetDC()) clientDC = new wxClientDC(this);
wxDC * dc = clientDC ? clientDC : event.GetDC();
// 绘制黄色背景
dc->SetBrush(wxBrush(wxColour(255,255,0)));
wxSize sz = GetClientSize();
dc->DrawRectangle(wxRect(0,0,sz.x,sz.y));
dc->SetBrush(wxNullBrush);
// 清除可能创建的clientDC
if (clientDC) wxDELETE(clientDC);
}
wxPaintDC
重绘前景的设备DC,只有当需要重绘时,才会发生重绘事件。什么叫需要重绘时?
手动发出一个重绘事件:Reflash与ReflashRect这两个函数,如果没有立即重绘,可以强制调用Update函数。
被动发出一个重绘事件:被别人挡住后,重新出现,或最小化后再重新出现,都会发生重绘事件。
EVT_PAINT(wxFontSelectorCtrl::OnPaint)
// 前景事件处理函数
void wxFontSelectorCtrl::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
dc.SetPen(*wxBLACK_PEN);
dc.SetBrush(*wxRED_BRUSH);
// 判断这个区域是否需要重绘
wxRect rectToDraw(0,0,100,100);
if (IsExposed(rectToDraw)) {
dc.DrawEllipse(wxPoint(0, 0), wxSize(50, 50));
}
dc.SetBrush(wxNullBrush);
dc.SetPen(wxNullPen);
}
防止重绘事件闪烁,可以让擦除背景的函数为空,将前景与背景的绘制全部统一到前景中来,利用双缓冲绘图来实现。
// 前景事件处理函数
void wxFontSelectorCtrl::OnPaint(wxPaintEvent& event)
{
wxBufferedPaintDC dc(this);
PrepareDC(dc);
// 绘制背景色
...
// 绘制前景色
dc.SetPen(*wxBLACK_PEN);
dc.SetBrush(*wxRED_BRUSH);
dc.DrawEllipse(wxPoint(0, 0), wxSize(50, 50));
dc.SetBrush(wxNullBrush);
dc.SetPen(wxNullPen);
}
wxMemoryDC
双缓冲绘图就是利用这个DC实现的,我们可以把所有的绘制,先在内存DC上绘制好,然后再输出到我们需要绘制的DC中。
下面我们演示一个利用内存DC绘制一个位图的实现代码:
wxMemoryDC memDC;
wxBitmap bitmap(200,200);
memDC.SelectObject(bitmap);
memDC.SetBackground(*wxWHITE_BRUSH);
memDC.Clear();
memDC.SetPen(*wxRED_PEN);
memDC.SetBrush(*wxTRANSPARENT_BRUSH);
memDC.DrawRectangle(wxRect(10,10,100,100));
memDC.SelectObject(wxNullBitmap);
1、wxColour(wxColour wc(255,0,0)红色),还有第4个参数,是apha通道
系统自带的颜色有:wxBLACK,wxWHITE, wxRED, wxBLUE, wxGREEN, wxCYAN,wxLIGHT_GREY,wxNullColour
wxSystemSettings::GetColour获取系统颜色(wxSYS COLOUR 3DFACE)
2、wxPen(wxPen wp(颜色,宽度,线型))
系统的线型有:wxSOLID,wxTRANSPARENT,wxDOT,wxLONG_DASH,wxSHORT_DASH,wxDOT_DASH
3、wxBrush(wxBrush wb(颜色,画刷类型))
画刷类型:wxSOLID,wxTRANSPARENT,wxBDIAGONAL_HATCH,wxCROSSDIAG_HATCH,wxSTIPPLE
系统画刷:wxGREEN BRUSH, wxWHITE BRUSH, wxBLACK BRUSH, wxGREY BRUSH,wxMEDIUM GREY BRUSH, wxLIGHT GREY BRUSH,wxtrANSPARENT BRUSH,wxNullBrush
4、wxFont(wxFont font(16, wxFONTFAMILY_SWISS, wxNORMAL, wxBOLD, true,wxT("Consolas"), wxFONTENCODING_ISO8859_1);)
也可以获取字体:wxFont* font = wxTheFontList->FindOrCreateFont(12, wxSWISS,wxNORMAL, wxNORMAL);
5、wxPalette(调色板,估计用的很少)
1、常用的函数有:
- Blit,拷贝此设备一部分到另一个设备
- Clear,刷新背景
- SetClippingRegion和DestroyClippingRegion:设置和释放区域
- DrawBitmap和DrawIcon:在某个位置画一个位图或者图标
- DrawCircle和DrawEllipse:画一个园和椭圆
- DrawLine和DrawLines:画线
- DrawPoint:画一个点像素
- DrawPolygon和DrawPolyPolygon:画多边形
- DrawRectangle和DrawRoundedRectangle:画矩形,或圆角矩形
- DrawText和DrawRotatedText:绘制文本,旋转文本
- DrawSpline:画一条平滑的曲线
- FloodFill:使用画刷填充
- SetBackground:设备背景画刷
- SetBackgroundMode:设置背景是实体还是透明wxTRANSPARENT和wxSOLID
- SetBrush:设置画刷
- SetPen:设置画笔
- SetFont:设置字体
- SetPalette:设置调色板
- SetTextForeground和SetTextBackground:设置字体前景色和后景色
- GetPixel:获取像素
- GetTextExtent和GetPartialTextExtents:获取文本的大小
- GetSize和GetSizeMM:获取设备的尺寸
- DeviceToLogicalX:设备坐标转化到逻辑坐标
- LogicalToDeviceX:逻辑坐标转化到设备坐标
- SetMapMode:逻辑坐标到设备坐标的映射模式
- SetAxisOrientation:设备x,y方向
- SetDeviceOrigin:设置坐标原点
- SetUserScale:设置缩放指
前景与背景
关于这个问题,wxWidgets框架中比较难以理解,也不同于MFC,因为它是多平台兼容的。
背景的擦除,默认情况下是会调用最近一次的SetBackgroundColour传入的颜色参数来擦除背景。
这个最近一次,概念比较模糊,资料比较欠缺,我的理解是当前控件的直系亲属,比如它的父窗口,或者父窗口的父窗口,设置过Colour,那么默认就以这个Colour为准。
这个擦除的动作,其实和wxDC的函数Clear是一样的,都是调用当前的背景画刷来擦除背景。
也就是说,默认情况下,背景的擦除是调用的当前的背景画刷,而当前的背景画刷默认情况下是直系亲属的默认背景画刷。
如果你没有重写EVT_ERASE_BACKGROUND这个事件,那么可以在EVT_PAINT中如下擦除背景:
wxPaintDC dc(this);
dc.SetBackground(wxBrush(wxColour(200, 100, 10)));
dc.Clear();
效果是一样的,还可以把wxPaintDC换成wxBufferedPaintDC,解决闪烁的问题。
如果主窗口的背景是一张图片,而子控件想要达到透明的效果,是不能通过调用获取主窗口的DC,来平铺当前控件的背景的,这是框架的机制决定的。
要达到这个目标,子窗口必须获取那张图片,然后计算当前控件所占的位置大小,来裁剪那张图片作为背景的位图画刷,擦除背景。
自绘标题栏
使用绘图相关知识,来自绘一个标题栏
1、从wxPanel继承一个子类,当做标题栏控件
class wxCaptionPanel :public wxPanel
2、定义构造函数与初始化的一些代码
wxCaptionPanel() { Init(); }
wxCaptionPanel(wxWindow *parent,
wxWindowID winid = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL | wxNO_BORDER,
const wxString& name = wxPanelNameStr)
{
Init();
Create(parent, winid, pos, size, style, name);
}
bool Create(wxWindow *parent,
wxWindowID winid = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL | wxNO_BORDER,
const wxString& name = wxPanelNameStr);
void Init();
3、定义一些控件的属性,比如标题栏的图标,文字,以及右侧的最小化与关闭按钮的图片和状态,等等
wxString m_title;
// 标题栏的区域
wxRect m_caption_rect;
// 窗口图标
wxIcon m_icon;
// 按钮的图片
std::vector m_skin_bmps;
std::vector m_min_bmps;
std::vector m_close_bmps;
// 按钮的区域
wxRect m_skin_rect;
wxRect m_min_rect;
wxRect m_close_rect;
// 按钮的状态
int m_skin_status;
int m_min_status;
int m_close_status;
4、标题栏的关闭与最小化按钮的三种状态
// 按钮的三种状态
enum BTN_STATUS
{
BTN_NORMAL = 0,BTN_HOVER,BTN_CLICK
};
5、相关的事件处理函数
BEGIN_EVENT_TABLE(wxCaptionPanel, wxPanel)
EVT_PAINT(wxCaptionPanel::OnPaint)
EVT_LEFT_DOWN(wxCaptionPanel::OnLeftDown)
EVT_LEFT_UP(wxCaptionPanel::OnLeftUp)
EVT_MOTION(wxCaptionPanel::OnMotion)
EVT_LEAVE_WINDOW(wxCaptionPanel::OnLeave)
EVT_MOUSE_CAPTURE_LOST(wxCaptionPanel::OnMouseLost)
END_EVENT_TABLE()
6、OnPaint绘图函数
void wxCaptionPanel::OnPaint(wxPaintEvent& event)
{
wxBufferedPaintDC dc(this);
dc.Clear();
// 绘制标题图标
dc.DrawIcon(m_icon, wxPoint(0,0));
// 绘制标题文字
dc.SetTextForeground(wxColour(50, 50, 50));
dc.DrawText(m_title, wxPoint(40, 10));
// 绘制按钮
dc.DrawBitmap(m_skin_bmps[m_skin_status], wxPoint(m_skin_rect.x, m_skin_rect.y), true);
dc.DrawBitmap(m_min_bmps[m_min_status], wxPoint(m_min_rect.x, m_min_rect.y), true);
dc.DrawBitmap(m_close_bmps[m_close_status], wxPoint(m_close_rect.x, m_close_rect.y), true);
}
#pragma once
#include
#include
#include "MyApp.h"
#include "MyFrame.h"
#include
// 按钮的三种状态
enum BTN_STATUS
{
BTN_NORMAL = 0,BTN_HOVER,BTN_CLICK
};
class wxCaptionPanel :public wxPanel
{
DECLARE_EVENT_TABLE()
public:
wxCaptionPanel() { Init(); }
wxCaptionPanel(wxWindow *parent,
wxWindowID winid = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL | wxNO_BORDER,
const wxString& name = wxPanelNameStr)
{
Init();
Create(parent, winid, pos, size, style, name);
}
bool Create(wxWindow *parent,
wxWindowID winid = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL | wxNO_BORDER,
const wxString& name = wxPanelNameStr);
void Init();
void SetTitle(const wxString & title) { m_title = title; Refresh(); };
// 事件处理
void OnPaint(wxPaintEvent& event);
void OnMotion(wxMouseEvent& event);
void OnLeave(wxMouseEvent& event);
void OnMouseLost(wxMouseCaptureLostEvent& event) {};
void OnLeftDown(wxMouseEvent& event);
void OnLeftUp(wxMouseEvent& event);
void RefreshBtn();
private:
wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxCaptionPanel);
MyFrame * m_pFrame;
wxSize m_offset;
wxString m_title;
// 标题栏的区域
wxRect m_caption_rect;
// 窗口图标
wxIcon m_icon;
// 按钮的图片
std::vector m_skin_bmps;
std::vector m_min_bmps;
std::vector m_close_bmps;
// 按钮的区域
wxRect m_skin_rect;
wxRect m_min_rect;
wxRect m_close_rect;
// 按钮的状态
int m_skin_status;
int m_min_status;
int m_close_status;
};
#include "stdafx.h"
#include "wxCaptionPanel.h"
IMPLEMENT_DYNAMIC_CLASS(wxCaptionPanel, wxPanel)
BEGIN_EVENT_TABLE(wxCaptionPanel, wxPanel)
EVT_PAINT(wxCaptionPanel::OnPaint)
EVT_LEFT_DOWN(wxCaptionPanel::OnLeftDown)
EVT_LEFT_UP(wxCaptionPanel::OnLeftUp)
EVT_MOTION(wxCaptionPanel::OnMotion)
EVT_LEAVE_WINDOW(wxCaptionPanel::OnLeave)
EVT_MOUSE_CAPTURE_LOST(wxCaptionPanel::OnMouseLost)
END_EVENT_TABLE()
bool wxCaptionPanel::Create(wxWindow *parent,
wxWindowID winid,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name)
{
if (!wxPanel::Create(parent, winid, pos, size, style, name)) return false;
SetBackgroundColour(wxColour(0, 206, 209));
return true;
}
// 初始化标题栏
void wxCaptionPanel::Init() {
m_offset = wxSize(-1, -1);
m_title = wxT("");
m_pFrame = (MyFrame *)wxTheApp->GetTopWindow();
m_icon = wxICON(MAIN_ICON);
// 初始化按钮图片
m_skin_bmps.emplace_back(wxT("res/btn_Skin_normal.png"), wxBITMAP_TYPE_PNG);
m_skin_bmps.emplace_back(wxT("res/btn_Skin_highlight.png"), wxBITMAP_TYPE_PNG);
m_skin_bmps.emplace_back(wxT("res/btn_Skin_down.png"), wxBITMAP_TYPE_PNG);
m_min_bmps.emplace_back(wxT("res/btn_mini_normal.png"), wxBITMAP_TYPE_PNG);
m_min_bmps.emplace_back(wxT("res/btn_mini_highlight.png"), wxBITMAP_TYPE_PNG);
m_min_bmps.emplace_back(wxT("res/btn_mini_down.png"), wxBITMAP_TYPE_PNG);
m_close_bmps.emplace_back(wxT("res/btn_close_normal.png"), wxBITMAP_TYPE_PNG);
m_close_bmps.emplace_back(wxT("res/btn_close_highlight.png"), wxBITMAP_TYPE_PNG);
m_close_bmps.emplace_back(wxT("res/btn_close_down.png"), wxBITMAP_TYPE_PNG);
// 初始化标题栏与按钮的位置
wxSize szSkin = m_skin_bmps[0].GetSize();
wxSize szMin = m_min_bmps[0].GetSize();
wxSize szClose = m_close_bmps[0].GetSize();
int width = m_pFrame->GetClientSize().GetWidth();
int height = m_pFrame->GetClientSize().GetHeight();
m_caption_rect = wxRect(0, 0, width - szSkin.x - szMin.x - szClose.x, height);
m_skin_rect = wxRect(width - szSkin.x - szMin.x - szClose.x, 0, szSkin.x, szSkin.y);
m_min_rect = wxRect(width - szMin.x - szClose.x, 0, szMin.x, szMin.y);
m_close_rect = wxRect(width - szClose.x, 0, szClose.x, szClose.y);
}
void wxCaptionPanel::OnPaint(wxPaintEvent& event)
{
wxBufferedPaintDC dc(this);
dc.Clear();
// 绘制标题图标
dc.DrawIcon(m_icon, wxPoint(0,0));
// 绘制标题文字
dc.SetTextForeground(wxColour(50, 50, 50));
dc.DrawText(m_title, wxPoint(40, 10));
// 绘制按钮
dc.DrawBitmap(m_skin_bmps[m_skin_status], wxPoint(m_skin_rect.x, m_skin_rect.y), true);
dc.DrawBitmap(m_min_bmps[m_min_status], wxPoint(m_min_rect.x, m_min_rect.y), true);
dc.DrawBitmap(m_close_bmps[m_close_status], wxPoint(m_close_rect.x, m_close_rect.y), true);
}
// 鼠标移动事件
void wxCaptionPanel::OnMotion(wxMouseEvent& event)
{
wxPoint pt = event.GetPosition();
bool isDown = event.LeftIsDown();
// 拖动时按下,并且在标题栏范围内
if (isDown && event.Dragging() && m_caption_rect.Contains(pt))
{
wxPoint mouse_pos = ClientToScreen(pt);
m_pFrame->Move(mouse_pos - m_offset);
return;
}
m_skin_status = m_skin_rect.Contains(pt) ? (isDown ? BTN_CLICK : BTN_HOVER) : BTN_NORMAL;
m_min_status = m_min_rect.Contains(pt) ? (isDown ? BTN_CLICK : BTN_HOVER) : BTN_NORMAL;
m_close_status = m_close_rect.Contains(pt) ? (isDown ? BTN_CLICK : BTN_HOVER) : BTN_NORMAL;
RefreshBtn();
}
// 鼠标离开标题栏事件
void wxCaptionPanel::OnLeave(wxMouseEvent& event) {
m_skin_status = BTN_NORMAL;
m_min_status = BTN_NORMAL;
m_close_status = BTN_NORMAL;
RefreshBtn();
}
// 鼠标按下事件
void wxCaptionPanel::OnLeftDown(wxMouseEvent& event)
{
CaptureMouse();
// 当前鼠标的屏幕坐标
wxPoint pt = event.GetPosition();
wxPoint mouse_pos = ClientToScreen(pt);
// 当前主窗口的屏幕坐标
wxPoint wnd_pos = m_pFrame->GetPosition();
// 计算鼠标的坐标点到窗口左上角的偏移量
m_offset.x = mouse_pos.x - wnd_pos.x;
m_offset.y = mouse_pos.y - wnd_pos.y;
if (m_skin_rect.Contains(pt)) {
m_skin_status = BTN_CLICK;
}
else if (m_min_rect.Contains(pt)) {
m_min_status = BTN_CLICK;
}
else if (m_close_rect.Contains(pt)) {
m_close_status = BTN_CLICK;
}
RefreshBtn();
}
// 鼠标抬起事件
void wxCaptionPanel::OnLeftUp(wxMouseEvent& event)
{
if (HasCapture()) ReleaseCapture();
wxPoint pt = event.GetPosition();
if (m_skin_rect.Contains(pt)) {
m_skin_status = BTN_NORMAL;
srand((unsigned)time(0));
SetBackgroundColour(wxColour(rand() % 255, rand() % 255, rand() % 255));
Refresh();
}else if (m_min_rect.Contains(pt)) {
m_min_status = BTN_NORMAL;
m_pFrame->Iconize();
}else if (m_close_rect.Contains(pt)) {
m_close_status = BTN_NORMAL;
m_pFrame->Close();
}
}
// 刷新按钮区域,状态没有变动的,就不刷新,防止闪烁
void wxCaptionPanel::RefreshBtn() {
static int skin_old_status = BTN_NORMAL;
static int min_old_status = BTN_NORMAL;
static int close_old_status = BTN_NORMAL;
if (skin_old_status != m_skin_status) {
skin_old_status = m_skin_status;
RefreshRect(m_skin_rect);
}
if (min_old_status != m_min_status) {
min_old_status = m_min_status;
RefreshRect(m_min_rect);
}
if (close_old_status != m_close_status) {
close_old_status = m_close_status;
RefreshRect(m_close_rect);
}
}