所谓双缓冲(double-buffers)绘图
,就是在进行绘制时,先将所有内容都绘制到一个绘图设备(如 QPixmap)上,然后再将整个图像绘制到部件上显示出来。
使用双缓冲绘图可以避免显示时的闪烁现象。
从Qt 4.0开始,QWidget部件的所有绘制都自动使用了双缓冲,所以一般没有必要在 paintEvent()函数中使用双缓冲代码来避免闪烁。
虽然在一般的绘图中无须手动使用双缓冲绘图,不过要想实现一些绘图效果,还是要借助于双缓冲的概念。
下面的程序实现使用鼠标在界面上绘制一个任意大小的矩形的功能。
这里需要两张画布,它们都是QPixmap
实例,其中一个tempPix
用来作为临时缓冲区
,当鼠标正在拖动矩形进行绘制时,将内容先绘制到tempPix
上,然后将tempPix
绘制到界面上;而另一个pix
作为缓冲区,用来保存已经完成的绘制。
当松开鼠标完成矩形的绘制后,则将tempPix
的内容复制到pix
上。
为了绘制时不显示拖影,在移动鼠标过程中,每绘制一次都要在刚开始绘制这个矩形的图像上进行绘制,所以需要在每次绘制tempPix
之前,先将pix的内容复制到tempPix 上
。
新建Qt Widgets应用,项目名称为mydoublebuffers,基类选择QWidget,类名为 Widget。
建立完成后,在 widget.h文件中添加如下内容:
private:
Ui::Widget *ui;
// 缓冲区
QPixmap pix;
// 临时缓冲区
QPixmap tempPix;
QPoint startPoint;
QPoint endPoint;
// 是否正在绘图的标志
bool isDrawing;
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
在这里定义了方法和绘画设备等
然后到widget.cpp文件中,先添加头文件
#include
#include
然后到构造函数里面添加一些变量并初始化
pix = QPixmap(400, 300);
pix.fill(Qt::white);
tempPix = pix;
isDrawing = false;
pix是400,300尺寸的QPixmap设备,白色,复制给tempPix防止重影,isDrawing 关闭
下面是鼠标事件处理函数:
void Widget::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) {
// 当鼠标左键按下时获取当前位置作为矩形的开始点
startPoint = event->pos();
// 标记正在绘图
isDrawing = true;
}
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton) {
// 当按着鼠标左键进行移动时,获取当前位置作为结束点,绘制矩形
endPoint = event->pos();
// 将缓冲区的内容复制到临时缓冲区,这样进行动态绘制时,
// 每次都是在缓冲区图像的基上进行绘制,就不会产生拖影现象了
tempPix = pix;
// 更新显示
update();
}
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) {
// 当鼠标左键松开时,获取当前位置为结束点,完成矩形绘制
endPoint = event->pos();
// 标记已经结束绘图
isDrawing = false;
update();
}
}
这里在鼠标按下事件处理函数中获取了要绘制矩形左上角的位置,然后标记正在绘制矩形。
在鼠标移动事件处理函数中获取了要绘制矩形的右下角的位置,然后动态绘制矩形,这里为了不会绘制出一堆小矩形而产生所谓的拖影现象
,就要在绘制临时缓冲区前,将缓冲区的内容复制到临时缓冲区中。这样每次都是在缓冲区图像的基础上进行绘制的,所以不会产生拖影现象。
最后在鼠标按键释放事件处理函数中,获取矩形的右下角坐标,标记已经结束绘制。
下面添加重绘事件处理函数的定义:
void Widget::paintEvent(QPaintEvent *event)
{
int x = startPoint.x();
int y = startPoint.y();
int width = endPoint.x() - x;
int height = endPoint.y() - y;
QPainter painter;
painter.begin(&tempPix);
painter.drawRect(x, y, width, height);
painter.end();
painter.begin(this);
painter.drawPixmap(0, 0, tempPix);
// 如果已经完成了绘制,那么更新缓冲区
if(!isDrawing)
pix = tempPix;
}
这里先在临时缓冲区tempPix
中进行绘图,然后将其绘制到界面上。
最后判断是否已经完成了绘制,如果是,则将临时缓冲区中的内容复制到缓冲区中,这样就完成了整个矩形的绘制。
这个例子中的关键是pix和 tempPix的相互复制
,如果想将这个程序进行扩展,可以查看一下网站上的涂鸦板程序。
与这个例子很相似的一个应用是橡皮筋线,就是我们在Windows桌面上拖动鼠标出现的橡皮筋选择框。Qt中提供了QRubberBand类来实现橡皮筋线,使用它只需要在几个鼠标事件处理函数中进行设置即可,具体应用可以查看该类的帮助文档。