绘图主要涉及到三个类QPainter、QPaintEngine、QPainDevice。其中,常用的是 QPaiter和 QPaintDevice。
QPainter
注意:
- 画图需要重写绘图事件虚函数。
- 如果在窗口绘图,必须放在绘图事件里实现。
- 绘图事件由内部自动调用,比如窗口需要
重绘的时候(即状态改变,如窗口大小改变),当然也可以人为调用。
void painterEvent(QPaintEvent *ev);
P.S:使用QPainter要引入头文件<QPainter>
下面我们来创建一个QPainter对象:
void Widget::paintEvent(QPaintEvent *event){
//方法一
QPainter p(this); //创建画家对象,指定当前窗口为绘图设备
//方法二
// QPainter p;
// p.begin(this);
// //绘图操作
// p.end();
}
12345678910
P.S:创建一个绘图对象QPainter时,要为它指定一个绘图设备,指定绘图设备有两种方式,一种是直接在构造函数中指定,另一种是通过调用QPainter对象的begin()方法和end()方法来实现。
接着,用这个QPainter对象来画一个背景图:
#include "Widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter p(this); //创建画家对象,指定当前窗口为绘图设备
p.drawPixmap(0,0,width(),height(),QPixmap("C:/Users/MSI-NB/Desktop/timg.jpg"));
}
P.S:
- 这里的参数是绘图的一个
矩形范围,以及Pixmap对象的路径来源。- 用rect()代替0,0,width(),height()效果是一样的
实现效果:
每次改变窗口大小都会引起重绘,系统自动调用paintEvent(),每次都会重新获取窗口的宽度和高度,所以我们看到画面会随着窗口大小的改变而改变,达到了自适应的效果。
我们还可以用QPainter来画线:
#include "Widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter p(this); //创建画家对象,指定当前窗口为绘图设备
p.drawPixmap(0,0,width(),height(),QPixmap("C:/Users/MSI-NB/Desktop/timg.jpg"));
//画线
p.drawLine(5,5,50,5);
p.drawLine(50,5,50,55);
p.drawLine(50,55,5,55);
p.drawLine(5,55,5,5);
}
实现效果:
QPen
显然,这样画出来的线过于单调,这时需要用QPen来对画笔进行设置:
QPen pen;
pen.setWidth(50); //设置画笔粗细
pen.setColor(QColor(97,3,5)); //设置画笔颜色
123
P.S:使用QPen需要先引入头文件<QPen>,并且不要忘了将画笔“交给”画家!
#include "Widget.h"
#include <QPainter>
#include <QPen>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter p(this); //创建画家对象,指定当前窗口为绘图设备
p.drawPixmap(0,0,width(),height(),QPixmap("C:/Users/MSI-NB/Desktop/timg.jpg"));
//定义画笔
QPen pen;
pen.setWidth(5); //设置画笔粗细
pen.setColor(QColor(97,3,5)); //设置画笔颜色
//将画笔交给画家,非常关键,注意它在代码中的位置,要先于画家的动作
p.setPen(pen);
//画线(画家的动作)
p.drawLine(5,5,50,5);
p.drawLine(50,5,50,55);
p.drawLine(50,55,5,55);
p.drawLine(5,55,5,5);
}
P.S:将画笔交给画家这一步一定要先于画家的动作,否则画笔的设置不会生效。
实现效果:
P.S:常用的颜色可以用Qt::颜色英文名进行设置,如Qt::red,但是能用英文单词表示的颜色有限,一般都采用rgb表示法,如QColor(97,3,5) 。
还可以调用QPen的setStyle()和setBrush()来设置画笔的风格、填充效果。
画笔风格如下:
下面来试试对画笔设置样式:
#include "Widget.h"
#include <QPainter>
#include <QPen>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter p(this); //创建画家对象,指定当前窗口为绘图设备
// p.drawPixmap(0,0,width(),height(),QPixmap("C:/Users/MSI-NB/Desktop/timg.jpg"));
//定义画笔
QPen pen;
pen.setWidth(5); //设置画笔粗细
pen.setColor(QColor(97,3,5)); //设置画笔颜色
pen.setStyle(Qt::DashDotLine); //设置画笔样式
//将画笔交给画家,非常关键
p.setPen(pen);
//画线
p.drawLine(5,5,50,5);
p.drawLine(50,5,50,55);
p.drawLine(50,55,5,55);
p.drawLine(5,55,5,5);
}
实现效果:
QBrush
当我们画出一个封闭图形时,还可以用画刷QBrush来实现填充效果。
填充效果如下:
下面我们画出矩形、圆形,并为它们设置填充效果:
绘制矩形需要调用drawRect(),参数是左上角坐标,以及矩形宽度和长度,当然也有其它重载函数,可以用键盘 ↑ ↓ 键来查看。
//画矩形
p.drawRect(100,100,50,50);
绘制圆形需要调用drawEllipse(),参数是圆心位置和长轴、短轴的长度。当长轴和短轴长度一样时,画出来的就是圆形了。
//画圆形
p.drawEllipse(150,150,80,80);
实现效果:
接下来,引入<QBrush>头文件,并为画家递上画刷:
#include "Widget.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter p(this); //创建画家对象,指定当前窗口为绘图设备
// p.drawPixmap(0,0,width(),height(),QPixmap("C:/Users/MSI-NB/Desktop/timg.jpg"));
//定义画笔
QPen pen;
pen.setWidth(2); //设置画笔粗细
pen.setColor(QColor(97,3,5)); //设置画笔颜色
pen.setStyle(Qt::DashDotLine); //设置画笔样式
//定义画刷
QBrush brush;
brush.setColor(QColor(16,7,117)); //设置画刷颜色
brush.setStyle(,Qt::HorPattern); //设置画刷样式
p.setBrush(brush); //将画刷交给画家
p.setPen(pen); //将画笔交给画家
//画线
p.drawLine(5,5,50,5);
p.drawLine(50,5,50,55);
p.drawLine(50,55,5,55);
p.drawLine(5,55,5,5);
//画矩形
p.drawRect(100,100,50,50);
//画圆形
p.drawEllipse(150,150,80,80);
}
实现效果:
总结:QPainter相当于画家,QPaintDevice相当于画板,画家要使用画笔和画刷时,都要调用相应的setXXX()方法。而画家在画板上绘制的每个图案,都要调用相应的drawXXX()方法。
手动更新窗口
我们知道,改变窗口大小会引起重绘,但是每次改变窗口大小才改变画面是不现实的,很多时候我们需要手动更新窗口。
查看帮助文档可以知道,很多情况下系统都会自动调用窗口的重绘,我们也可以通过调用窗口对象的update()方法,该方法会自动触发重绘事件,相当于间接调用paintEvent()。
P.S:update()可以通过设置参数指定重绘区域,如果不给参数,默认重绘整个窗口。当然还有其它重载函数。
下面我们来用update()实现点击鼠标让小喻言跑起来的效果:
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#pragma execution_character_set("utf-8")
#include <QWidget>
#include <QPushButton>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
void Widget::paintEvent(QPaintEvent *event);
void Widget::yuyanRun(); //喻言跑起来
private:
int x;
QPushButton *p_button=new QPushButton(this);
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QEvent>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
x=0;
p_button->move(300,300);
p_button->setText("冲啊");
connect(p_button,&QPushButton::pressed,this,&Widget::yuyanRun);
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter p(this); //创建画家对象,指定当前窗口为绘图设备
//这部分代码省略
//画喻言
p.drawPixmap(x,70,119,170,QPixmap("C:/Users/MSI-NB/Desktop/yuyan.png"));
}
void Widget::yuyanRun(){
x+=20; //每次图案横坐标移动20
update(); //系统自动调用paintEvent
}
实现效果:
图源:cytusss_,侵删。
P.S:在这里犯了一个错误,导致无论如何点击按钮都没有反应,原因是将变量x=0;写在了paintEvent()函数体内-_-||
注意:
- 千万不要把update()放到paintEvent()方法的实现中,否则程序将会进入死循环。
- 也不要在绘图事件中做太复杂的数据处理,因为你无法知道什么时候绘图事件会自动调用。
- 如果要在窗口上绘图,一定要在paintEvent()里实现!!
QBitmap与QPixmap的区别
QBitmap是QPixmap的子类,QPixmap常用来绘制彩色图片,而QBitmap只有黑白两种颜色。
使用QPixmap和QBitmap要引用头文件<QPixmap>和<QBitmap>,虽然后者是继承自前者的,但少了头文件声明还是会报错,提示QBitmap没有构造函数。
p.drawPixmap(x,70,119,170,QPixmap("C:/Users/MSI-NB/Desktop/yuyan.png"));
p.drawPixmap(x,400,119,170,QBitmap("C:/Users/MSI-NB/Desktop/yuyan.png"));
实现效果 :
P.S:如果图片背景是透明的,用QBitmap显示时,也会变成黑色。而如果图片背景是白色的,用QBitmap显示时,则会变成透明。
绘图设备
绘图设备主要有QPixmap、QImage、QPicture。
- QPixmap是最常用的,因为它是在屏幕上面绘图的,并且针对屏幕进行优化了,但它是平台相关的,且不能对图片进行修改。
- QImage是平台无关的,可以对图片进行修改,比如修改某个像素点的颜色,还可以在线程中绘图。
- QPicture是用来保存绘图状态(二进制文件)的。
QPixmap
在前面的实验中,我们都是将图形绘制在窗口中,但是绘图设备不仅可以是窗口,我们可以在上面绘图,然后再将图像保存到指定的路径。
#include "Widget.h"
#include <QPainter>
#include <QPixmap>
#include <QBrush>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//绘图设备
QPixmap map(400,300);
//画家
QPainter p(&map);
//填充背景
p.fillRect(0,0,400,300,QBrush(QColor(97,3,5)));
//绘制矩形
p.drawRect(0,0,120,120);
//保存图片
map.save("C:/Users/MSI-NB/Desktop/map.jpg");
}
Widget::~Widget()
{
}
实现效果:
P.S:填充背景不仅可以调用QPainter的fiilRect()方法,还可以调用QPixmap的fill()方法。
注意:使用填充的方法来进行背景的绘制时,要先于其它图形的绘制,否则会把其它图形遮盖住。
QImage
QImage的用法和QPixmap一样,但是QImage可以很容易地实现使图像背景为透明的效果。
#include "Widget.h"
#include <QPainter>
#include <QPixmap>
#include <QBrush>
#include <QImage>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// //绘图设备
// QPixmap map(400,300);
// //画家
// QPainter p(&map);
// //填充背景
// p.fillRect(0,0,400,300,QBrush(QColor(97,3,5)));
// //绘制矩形
// p.drawRect(0,0,120,120);
// //保存图片
// map.save("C:/Users/MSI-NB/Desktop/map.jpg");
//绘图设备
QImage img(400,300,QImage::Format_ARGB32); //设置背景为透明
//画家
QPainter p(&img);
//绘制矩形
p.drawImage(0,0,QImage("C:/Users/MSI-NB/Desktop/yuyan.png"));
//保存图片
img.save("C:/Users/MSI-NB/Desktop/img.jpg");
img.save("C:/Users/MSI-NB/Desktop/img.png");
}
Widget::~Widget()
{
}
P.S:使用QImage::Format_ARGB32可以将背景设为透明,查看帮助文档解锁更多用法~
实现效果:
同样一张图,在背景为透明的前提下,保存成png格式与jpg格式的显示效果是不同的。
注意:为图片设置背景为透明后,只有保存的文件格式为png时才能正常显示出透明背景!!如果保存为jpg格式,是不会有透明效果的!!补充一个小知识点——jpg是以RGB来存储图像,而png是以ARGB来存储图像,A(Alpha)即为透明度。
QPicture
QPicture用来存储绘画的状态,可以记录和重现QPainter命令的绘图设备。QPicture要配合QPainter的begin()方法使用。
//绘图设备
QImage img(400,300,QImage::Format_ARGB32);
//画家
QPainter p(&img);
//绘制图案
p.drawImage(0,0,QImage("C:/Users/MSI-NB/Desktop/yuyan.png"));
//定义QPicture对象
QPicture pic;
//QPicture对象作为参数传入begin()
p.begin(&pic);
//图像状态保存路径
pic.save("C:/Users/MSI-NB/Desktop/picture.png");
p.end();
实现效果:
发现文件打不开,那么这个文件有什么用呢?
虽然我们无法直接通过该二进制文件看到图像内容,但是我们用save()方法将它保存在某个路径,自然可以通过load()方法将其取出,并呈现在我们需要它呈现的地方。结合之前学过的计时器,我们来实现一个简单的小动画:
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#pragma execution_character_set("utf-8")
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
void Widget::paintEvent(QPaintEvent *event);
void Widget::timerEvent(QTimerEvent *event);
private:
int x;
};
#endif // WIDGET_H
#include "Widget.h"
#include <QPainter>
#include <QPixmap>
#include <QBrush>
#include <QImage>
#include <QPicture>
#include <QTimerEvent>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//初始化横坐标
x=0;
//启动计时器
startTimer(100);
//定义画家
QPainter p;
//定义QPicture对象
QPicture pic;
//关联画家与状态
p.begin(&pic);
//绘制图像
p.drawImage(0,70+x,QImage("C:/Users/MSI-NB/Desktop/yuyan.png"));
p.drawText(50,50,"可爱的我来了");
p.end();
//指定图像保存路径
pic.save("C:/Users/MSI-NB/Desktop/picture.png");
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
//定义QPicture对象
QPicture pic;
//读取图像状态
pic.load("C:/Users/MSI-NB/Desktop/picture.png");
//定义画家
QPainter p;
//在当前窗口绘制
p.begin(this);
p.drawPicture(x,70,pic);
p.end();
}
void Widget::timerEvent(QTimerEvent *event){
x+=5;
update(); //手动调用重绘事件
}
QPixmap和QImage的转化
QPixmap对屏幕进行了优化,但它是平台相关的;而QImage是平台无关的,却没有对屏幕进行优化,为了将两者的优点结合起来,有时候需要对它们进行转化。在绘制时使用QPixmap,在传输时使用QImage。
//QPixmap -> QImage
QImage image=pixmap.toImage(); //这个是成员函数
//QImage -> QPixmap
QPixmap pixmap=Pixmap::fromImage(image); //这个是静态函数
P.S:注意从QPixmap转化成QImage调用的是QPixmap对象的toImage()成员函数,从QImage转化成QPixmap调用的是QPixmap类的fromImage()静态函数。
不规则窗口
有时候我们打开一些应用程序时,会出现一个类似软件LOGO的东西,然后才进入程序操作界面,这个类似软件LOGO的东西也是一种窗口,是一种不规则的窗口。那么如何实现不规则的窗口呢?需要去窗口边表框,并将图像背景设置为透明:
#include "Widget.h"
#include <QPainter>
#include <QPixmap>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//去窗口边框
setWindowFlags(Qt::FramelessWindowHint|windowFlags());
//设置窗口背景为透明
setAttribute(Qt::WA_TranslucentBackground);
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter p(this);
p.drawPixmap(0,0,QPixmap("C:/Users/MSI-NB/Desktop/yuyan.png"));
}
注意:setWindowFlags(Qt::FramelessWindowHint|windowFlags());里的windowFlags()不能忘。
下面是去边框后的实现效果:
下面是背景设为透明后的实现效果:
是不是很酷!!但是去边框的同时也去掉了边框中带有的关闭按钮,同时也无法自由拖动窗口。因此,要实现一个真正的不规则窗口,我们还应该重写一些事件处理函数。
首先要明确,当我们点击窗口并拖动的时候,是相对于窗口左上角同步移动的,因此要获取到窗口左上角的初始坐标(相对于屏幕),以及鼠标当前的坐标(相对于屏幕),利用两个坐标之差来改变窗口在屏幕中的位置。
拖动窗口可以看作由两个鼠标事件组成——鼠标点击和鼠标移动。当鼠标点击事件发生时,求出窗口左上角坐标;当鼠标移动事件发生时,改变窗口在屏幕中的位置。
移动窗口需要用到move()方法,参数是窗口的左上角位置,因此在我们求出窗口左上角坐标和鼠标点击事件发生时鼠标的坐标的差值之后,要利用这个差值和鼠标移动事件发生时鼠标的坐标来反求出拖动窗口时,窗口左上角在屏幕中的坐标位置。
话不多说,上代码:
#include "Widget.h"
#include <QPainter>
#include <QPixmap>
#include <QMouseEvent>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//去窗口边框
setWindowFlags(Qt::FramelessWindowHint|windowFlags());
//设置窗口背景为透明
setAttribute(Qt::WA_TranslucentBackground);
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event){
QPainter p(this);
p.drawPixmap(0,0,QPixmap("C:/Users/MSI-NB/Desktop/yuyan.png"));
}
void Widget::mousePressEvent(QMouseEvent *event){
if(event->button()==Qt::RightButton){
//若按下的是鼠标右键,则关闭窗口
close();
}else if(event->button()==Qt::LeftButton){
//求出窗口左上角和鼠标当前坐标的差值
point=event->globalPos()-this->frameGeometry().topLeft();
}
}
void Widget::mouseMoveEvent(QMouseEvent *event){
//判断是否按下鼠标左键
if(event->buttons()&Qt::LeftButton){
//移动窗口
move(event->globalPos()-point);
}
}
说明:
setWindowFlags()中的WindowFlags是零个或多个窗口系统提示的组合。
WindowFlags实际上是32位的一个参数,它的每一位都有不同的含义,也对应了不同的功能。
如果我们要使用某个功能,比如去窗口边框,就要将windowFlags()与Qt::FramelessWindowHint进行按位或( | )。
如果要去掉一个功能,比如去掉最大化按钮,就要将windowFlags()与Qt::WindowMaximizeButtonHint按位取反( ~ )的结果进行按位与( & )。
this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); //实现功能
setWindowFlags(windowFlags()& ~Qt::WindowMaximizeButtonHint); //去除功能
【转自:blog.csdn.net/zoukp12345/… 【转自:blog.csdn.net/xuebing1995…
globalPos()获取的是对象相对于屏幕的坐标。frameGeometry()获取的是带边框的窗口在其父组件中的信息,返回一个矩形。buttons()用来检查可能伴随鼠标事件的鼠标按钮。
假设我的鼠标左键已经按下。
若移动鼠标,会发生的move事件,button返回Qt::NoButton,buttons返回LeftButton。
再按下右键,会发生press事件,button返回RightButton,buttons返回LeftButton|RightButton
再移动鼠标,会发生move事件,button返回Qt::NoButton,buttons返回LeftButton|RightButton
再松开左键,会发生Release事件,button返回LeftButton,buttons返回RightButton
总而言之,button返回“那个按钮发生了此事件”,buttons返回"发生事件时哪些按钮还处于按下状态"
【转自:www.cnblogs.com/fengyaoyao/…
实现效果:
P.S:如有错误,欢迎指正~