一、原理与设计
所谓双缓冲机制,是指在绘制控件时,首先将要绘制的内容绘制在一个图片中,再将图片一次性地绘制到控件上。在早期的 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() 函数,如打印机等外部设备。
(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);
}
}
运行结果:
感谢阅读!!!!!