QT5开发及实例学习之十七Qt5双缓冲机制


一、原理与设计

  所谓双缓冲机制,是指在绘制控件时,首先将要绘制的内容绘制在一个图片中,再将图片一次性地绘制到控件上。在早期的 Qt 版本中,若直接在控件上进行绘制工作,则在控件重绘时会产生闪烁地现象,控件重绘频繁时,闪烁尤为明显。双缓冲机制可以有效地消除这种闪烁现象。自 Qt5 版本之后,QWidget 控件已经能够自动处理闪烁的问题。因此,在控件上直接绘图时,不用再操心显示的闪烁问题,但双缓冲机制在很多场合仍然有其用武之地。当所需绘制的内容较复杂并需要频繁刷新,或者每次只需要刷新整个控件的一小部分时,仍应尽量采用双缓冲机制。

  实现一个简单的绘图工具,可以选择线型、线宽、颜色等基本要素。QMainWindow 对象作为主窗口,QToolBar 对象作为工具栏,QWidget 对象作为主窗口的中央窗体,也就是绘图区。通过响应鼠标事件进行绘图,而这是在绘图区窗体完成的,所以首先实现此窗体 DrawWidget 对鼠标事件进行重定义;然后实现可以选择线型、线宽及颜色等基本要素的主窗口。
  绘图工具框架:
在这里插入图片描述

二、绘图区的实现

  DrawWidget 类继承自 QWidget 类,在类声明中对鼠标事件 mousePressEvent() 和 mouseMoveEvent(),重绘事件 paintEvent()、尺寸变化事件 resizeEvent() 进行了重定义。setStyle()、setWidth() 及 setColor() 函数主要用于为主窗口传递各种与绘图有关的参数。

(1)DrawWidget 构造函数完成对窗体参数及部分功能的初始化工作,具体代码如下:

DrawWidget::DrawWidget(QWidget *parent) : QWidget(parent)
{
    setAutoFillBackground(true);    //对窗体背景色的设置
    setPalette(QPalette(Qt::white));
    pix = new QPixmap(size());      //此QPixmap对象用于准备随时接收绘制的内容
    pix->fill(Qt::white);           //填充背景色为白色
    setMinimumSize(600, 400);       //设置绘制区窗体的最小尺寸
}

(2)setStyle() 函数接收主窗口传来的线型风格参数,setWidth() 函数接收主窗口传来的线宽参数值,setColor() 函数接收主窗口传来的画笔颜色值。具体代码如下:

void DrawWidget::setStyle(int s)
{
    style = s;
}

void DrawWidget::setWidth(int w)
{
    weight = w;
}

void DrawWidget::setColor(QColor c)
{
    color = c;
}

(3)重定义鼠标按下事件 mousePressEvent(),在按下鼠标按键时,记录当前的鼠标位置值 startPos,具体代码如下:

void DrawWidget::mousePressEvent(QMouseEvent *e)
{
    startPos = e->pos();
}

(4)重定义鼠标移动事件 mouseMoveEvent() ,鼠标移动事件在默认情况下,在鼠标按键被按下的同时拖拽鼠标时被触发。
  QWidget 的 mouseTracking 属性指示窗体是否追踪鼠标,默认为 false(不追踪),即在至少有一个鼠标按键被按下的前提下移动鼠标才触发 mouseMoveEvent() 事件,可以通过 setMouseTracking(bool enable) 方法对该属性值进行设置。如果设置为追踪,则无论鼠标按键是否被按下,只要鼠标移动,就会触发 mouseMoveEvent() 事件。在此事件处理函数中,完成向 QPixmap 对象中绘图的工作。具体代码如下:

void DrawWidget::mouseMoveEvent(QMouseEvent *e)
{
    QPainter *painter = new QPainter();     //新建一个QPainter对象
    QPen pen;                               //新建一个QPen对象
    pen.setStyle((Qt::PenStyle)style);      //(a)
    pen.setWidth(weight);                   //设置画笔的线宽值
    pen.setColor(color);                    //设置画笔的颜色
    painter->begin(pix);                    //(b)
    painter->setPen(pen);                   //将QPen对象应用到绘制对象中
    //绘制从startPos到鼠标当前位置的直线
    painter->drawLine(startPos, e->pos());
    painter->end();
    startPos = e->pos();                    //更新鼠标的当前位置,为下次绘制做准备
    update();                               //重绘绘制区窗体
}

 (a)设置画笔的线型,style 表示当前选择的线型是 Qt::PenStyle 枚举数据中的第几个元素。
 (b)以 QPixmap 对象为 QPaintDevice 参数绘制。在构造一个 QPainter 对象时,就立即开始对绘画设备进行绘制。此构造 QPainter 对象是短时期的,如应定义在 QWidget::PaintEvent() 中,并只能调用一次。此构造函数调用开始于 begin() 函数,并且在 QPainter 的析构函数中自动调用 end() 函数。由于当一个 QPainter 对象的初始化失败时构造函数不能提供反馈信息,所以在绘制外部设备时应使用 begin() 和 end() 函数,如打印机等外部设备。
  下面是使用 begin() 和 end() 函数的一个例子:

void MyWidget::paintEvent(QPaintEvent *)
{
	QPainter p;
	p.begin(this);
	p.drawLine(...);
	p.end();
}

(5)重绘函数 paintEvent() 完成绘制区窗体的更新工作,只需调用 drawPixmap() 函数将用于接收图形绘制的 QPixmap 对象绘制在绘制区窗体控件上即可。具体代码如下:

void DrawWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawPixmap(QPoint(0, 0), *pix);
}

(6)调整绘制区大小函数 resizeEvent() ,当窗体的大小发生改变时,效果看起来虽然像是绘制区大小改变了,但实际能够进行绘制的区域仍然没有改变。因为绘图的大小并没有改变,还是原来绘制区窗口的大小,所以在窗体尺寸变化时应及时调整用于绘制的 QPixmap 对象的大小。具体代码如下:

void DrawWidget::resizeEvent(QResizeEvent *event)
{
    if(height() > pix->height() || width() > pix->width())  //(a)
    {
        QPixmap *newPix = new QPixmap(size());              //创建一个新的QPixmap对象
        newPix->fill(Qt::white);                            //填充新QPixmap对象newPix的颜色为白色背景
        QPainter p(newPix);
        p.drawPixmap(QPoint(0, 0), *pix);                   //在newPix中绘制原pix中的内容
        pix = newPix;                                       //将newPix赋值给pix作为新的绘制图形接收对象
    }
    QWidget::resizeEvent(event);                            //完成其余工作
}

 (a)判断改变后的窗体长或宽是否大于原窗体的长和宽。若大于则进行相应的调整,否则直接调用 QWidget 的 resizeEvent() 函数返回。
(7)clear() 函数完成绘制区的清除工作,只需要调用一个新的、干净的 QPixmap 对象来代替 pix ,并调用 update() 函数重绘即可。具体代码如下:

void DrawWidget::clear()
{
    QPixmap *clearPix = new QPixmap(size());
    clearPix->fill(Qt::white);
    pix = clearPix;
    update();
}

三、主窗口的实现

  主窗口类 MainWindow 继承自 QMainWindow 类,只包含一个工具栏和一个中央窗体。首先,声明一个构造函数、一个用于创建工具栏的函数 createToolBar()、一个用于进行选择线型风格的槽函数 showStyle() 和一个用于进行颜色选择的槽函数 showColor()。然后,声明一个 DrawWidget 类对象作为主窗口的私有变量,以及声明代表线型风格、线宽选择、颜色选择及清除按钮的私有变量。

(1)MainWindow 类的构造函数完成初始化工作,具体代码如下:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    drawWidget = new DrawWidget();  //新建一个DrawWidget对象
    setCentralWidget(drawWidget);   //新建的DrawWidget对象作为主窗口的中央窗体
    createToolBar();                //实现一个工具栏
    setMinimumSize(600, 400);       //设置主窗口的最小尺寸
    showStyle();                    //初始化线型,设置控件中的当前值作为初始值
    drawWidget->setWidth(widthSpinBox->value());    //初始化线宽
    drawWidget->setColor(Qt::black);                //初始化颜色
}

(2)createToolBar() 函数完成工具栏创建,具体代码如下:

void MainWindow::createToolBar()
{
    QToolBar *toolBar = addToolBar("Tool");            //为主窗口新建一个工具栏对象
    styleLabel = new QLabel(QObject::tr("线型风格:"));  //创建线型风格选择控件
    styleComboBox = new QComboBox();
    styleComboBox->addItem(QObject::tr("SolidLine"), static_cast<int>(Qt::SolidLine));
    styleComboBox->addItem(QObject::tr("DashLine"), static_cast<int>(Qt::DashLine));
    styleComboBox->addItem(QObject::tr("DotLine"), static_cast<int>(Qt::DotLine));
    styleComboBox->addItem(QObject::tr("DashDotLine"), static_cast<int>(Qt::DashDotLine));
    styleComboBox->addItem(QObject::tr("DashDotDotLine"), static_cast<int>(Qt::DashDotDotLine));
    connect(styleComboBox, SIGNAL(activated(int)), this, SLOT(showStyle()));

    widthLabel = new QLabel(QObject::tr("线宽:"));      //创建线宽选择控件
    widthSpinBox = new QSpinBox();
    connect(widthSpinBox, SIGNAL(valueChanged(int)), drawWidget, SLOT(setWidth(int)));

    colorBtn = new QToolButton();                       //创建颜色选择控件
    QPixmap pixmap(20, 20);
    pixmap.fill(Qt::black);
    colorBtn->setIcon(QIcon(pixmap));
    connect(colorBtn, SIGNAL(clicked()), this, SLOT(showColor()));

    clearBtn = new QToolButton();                       //创建清除按钮
    clearBtn->setText(QObject::tr("清除"));
    connect(clearBtn, SIGNAL(clicked()), drawWidget, SLOT(clear()));

    toolBar->addWidget(styleLabel);
    toolBar->addWidget(styleComboBox);
    toolBar->addWidget(widthLabel);
    toolBar->addWidget(widthSpinBox);
    toolBar->addWidget(colorBtn);
    toolBar->addWidget(clearBtn);
}

(3)改变线型参数的槽函数 showStyle(),通过调用 DrawWidget 类的 setStyle() 函数将当前线型选择控件中的线型参数传给绘制区;设置画笔颜色的槽函数 showColor(),通过调用 DrawWidget 类的 setColor() 函数将用户在标准颜色对话框中选择的颜色值传给绘制区。具体代码如下:

void MainWindow::showStyle()
{
    drawWidget->setStyle(styleComboBox->itemData(styleComboBox->currentIndex(), Qt::UserRole).toInt());
}

void MainWindow::showColor()
{
    QColor color = QColorDialog::getColor(static_cast<int>(Qt::black), this);
    //使用颜色对话框QColorDialog获得一个颜色值
    if(color.isValid())
    {
        //将新选择的颜色传给绘制区,用于改变画笔的颜色值
        drawWidget->setColor(color);
        QPixmap p(20, 20);
        p.fill(color);
        colorBtn->setIcon(QIcon(p));                    //更新颜色选择按钮上的颜色显示
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值