QT 绘图原理与双缓冲机制
文章目录
- [QT 绘图原理与双缓冲机制](#QT 绘图原理与双缓冲机制)
-
- [一、Qt 窗口 API 基础知识](#一、Qt 窗口 API 基础知识)
-
- [1.1 窗口显示与关闭](#1.1 窗口显示与关闭)
- [1.2 窗口尺寸](#1.2 窗口尺寸)
- [1.3 窗口位置](#1.3 窗口位置)
- [1.4 窗口标题与图标](#1.4 窗口标题与图标)
- [1.5 窗口标志与状态](#1.5 窗口标志与状态)
- [1.6 窗口刷新](#1.6 窗口刷新)
- [1.7 父子关系](#1.7 父子关系)
- [1.8 常用事件处理函数](#1.8 常用事件处理函数)
- [1.9 对象名称与属性](#1.9 对象名称与属性)
- [1.10 案例:窗口 API 位置函数测试](#1.10 案例:窗口 API 位置函数测试)
- [二、Qt 绘图框架设计(QPainter 绘图)](#二、Qt 绘图框架设计(QPainter 绘图))
-
- [2.1 案例:QPainter 基本形状绘制](#2.1 案例:QPainter 基本形状绘制)
- [三、Qt 双缓冲机制](#三、Qt 双缓冲机制)
-
- [3.1 传统绘制(单缓冲)的问题](#3.1 传统绘制(单缓冲)的问题)
- [3.2 双缓冲的解决方案](#3.2 双缓冲的解决方案)
- [3.3 案例:手绘板双缓冲实现](#3.3 案例:手绘板双缓冲实现)
- 小结
一、Qt 窗口 API 基础知识
Qt 中所有可视控件均继承自 QWidget,因此以下 API 对 QWidget、QDialog、QMainWindow 等窗口类均适用。
1.1 窗口显示与关闭
| 方法 | 说明 |
|---|---|
show() |
以非模态方式显示窗口 |
hide() |
隐藏窗口 |
close() |
关闭窗口(触发 closeEvent) |
exec() |
以模态方式显示对话框(阻塞调用,仅 QDialog 可用) |
open() |
以非模态方式显示对话框(不阻塞) |
isVisible() |
判断窗口是否可见 |
setModal(bool) |
设置是否模态(仅 QDialog) |
setWindowModality(Qt::WindowModal) |
设置模态级别 |
1.2 窗口尺寸
| 方法 | 说明 |
|---|---|
resize(w, h) |
设置窗口大小(用户可通过拖拽改变) |
setFixedSize(w, h) |
设置固定大小(禁止拖拽改变) |
setMinimumSize(w, h) |
设置最小尺寸 |
setMaximumSize(w, h) |
设置最大尺寸 |
size() |
获取当前尺寸,返回 QSize |
width() / height() |
获取宽度 / 高度 |
setGeometry(x, y, w, h) |
同时设置位置和大小 |
1.3 窗口位置
| 方法 | 说明 |
|---|---|
move(x, y) |
移动窗口到指定坐标 |
pos() |
获取窗口位置,返回 QPoint |
x() / y() |
获取窗口左上角坐标 |
frameGeometry() |
获取包含边框的完整几何信息 |
geometry() |
获取客户区几何信息(不含边框) |
1.4 窗口标题与图标
| 方法 | 说明 |
|---|---|
setWindowTitle("标题") |
设置窗口标题 |
windowTitle() |
获取窗口标题 |
setWindowIcon(QIcon("图标路径")) |
设置窗口图标 |
1.5 窗口标志与状态
| 方法 | 说明 |
|---|---|
setWindowFlags(Qt::FramelessWindowHint) |
去掉窗口边框 |
setWindowFlags(Qt::WindowStaysOnTopHint) |
窗口置顶 |
setWindowOpacity(0.5) |
设置窗口透明度(0.0 ~ 1.0) |
showFullScreen() |
全屏显示 |
showMaximized() |
最大化显示 |
showMinimized() |
最小化显示 |
showNormal() |
恢复普通显示 |
isMaximized() |
判断是否已最大化 |
isMinimized() |
判断是否已最小化 |
isFullScreen() |
判断是否已全屏 |
1.6 窗口刷新
| 方法 | 说明 |
|---|---|
update() |
请求重绘,自动合并多次调用,推荐使用 |
repaint() |
立即强制重绘(不经过事件队列,慎用) |
updateGeometry() |
通知布局系统重新计算控件几何 |
1.7 父子关系
| 方法 | 说明 |
|---|---|
setParent(parent) |
设置父控件 |
parent() |
获取父控件 |
findChild<T*>("objectName") |
按对象名称查找子控件 |
findChildren<T*>() |
查找所有匹配类型的子控件 |
说明: 子控件的坐标相对于父控件;父控件移动时子控件跟随移动;父控件销毁时子控件自动销毁。
1.8 常用事件处理函数
| 虚函数 | 触发时机 |
|---|---|
paintEvent(QPaintEvent *) |
需要重绘时 |
resizeEvent(QResizeEvent *) |
窗口大小改变时 |
closeEvent(QCloseEvent *) |
窗口关闭时 |
mousePressEvent(QMouseEvent *) |
鼠标按下时 |
mouseMoveEvent(QMouseEvent *) |
鼠标移动时 |
mouseReleaseEvent(QMouseEvent *) |
鼠标释放时 |
keyPressEvent(QKeyEvent *) |
键盘按下时 |
keyReleaseEvent(QKeyEvent *) |
键盘释放时 |
enterEvent(QEvent *) |
鼠标进入控件时 |
leaveEvent(QEvent *) |
鼠标离开控件时 |
focusInEvent(QFocusEvent *) |
获得焦点时 |
focusOutEvent(QFocusEvent *) |
失去焦点时 |
1.9 对象名称与属性
| 方法 | 说明 |
|---|---|
setObjectName("名称") |
设置对象名称(可供 findChild 查找) |
objectName() |
获取对象名称 |
setProperty("属性名", 值) |
动态设置属性(可用于样式表选择器) |
property("属性名") |
获取属性值 |
setEnabled(bool) |
启用 / 禁用控件 |
setVisible(bool) |
设置可见性 |
1.10 案例:窗口 API 位置函数测试
dialog.h
cpp
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QLabel>
#include <QGridLayout>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = nullptr);
~Dialog();
private:
QGridLayout *glayout;
QLabel *labelgeometry;
QLabel *labelgeometryvalue;
QLabel *labelwidth;
QLabel *labelwidthvalue;
QLabel *labelheight;
QLabel *labelheightvalue;
QLabel *labelrect;
QLabel *labelrectvalue;
QLabel *labelsize;
QLabel *labelsizevalue;
// 窗口大小变化事件
void resizeEvent(QResizeEvent *);
// 窗口移动事件
void moveEvent(QMoveEvent *);
public:
// 更新各 Label 显示值
void UpdateLabelFunc();
};
#endif // DIALOG_H
dialog.cpp
cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(300, 160);
setWindowTitle("Qt窗口常用的API位置函数测试");
glayout = new QGridLayout(this);
labelgeometry = new QLabel("函数geometry():");
labelgeometryvalue = new QLabel;
labelwidth = new QLabel("函数width():");
labelwidthvalue = new QLabel;
labelheight = new QLabel("函数height():");
labelheightvalue = new QLabel;
labelrect = new QLabel("函数rect():");
labelrectvalue = new QLabel;
labelsize = new QLabel("函数size():");
labelsizevalue = new QLabel;
glayout->addWidget(labelgeometry, 0, 0);
glayout->addWidget(labelgeometryvalue, 0, 1);
glayout->addWidget(labelwidth, 1, 0);
glayout->addWidget(labelwidthvalue, 1, 1);
glayout->addWidget(labelheight, 2, 0);
glayout->addWidget(labelheightvalue, 2, 1);
glayout->addWidget(labelrect, 3, 0);
glayout->addWidget(labelrectvalue, 3, 1);
glayout->addWidget(labelsize, 4, 0);
glayout->addWidget(labelsizevalue, 4, 1);
UpdateLabelFunc();
}
Dialog::~Dialog()
{
}
// 窗口大小变化时刷新 Label 显示
void Dialog::resizeEvent(QResizeEvent *)
{
UpdateLabelFunc();
}
// 窗口移动时刷新 Label 显示
void Dialog::moveEvent(QMoveEvent *)
{
UpdateLabelFunc();
}
// 更新所有 Label 的值
void Dialog::UpdateLabelFunc()
{
// geometry() 返回相对父窗口的 x, y, width, height
QString strGeom;
QString s1, s2, s3, s4;
strGeom = s1.setNum(geometry().x()) + ","
+ s2.setNum(geometry().y()) + ","
+ s3.setNum(geometry().width()) + ","
+ s4.setNum(geometry().height());
labelgeometryvalue->setText(strGeom);
// width() / height()
QString strW, strH;
labelwidthvalue->setText(strW.setNum(width()));
labelheightvalue->setText(strH.setNum(height()));
// rect() 始终从 (0,0) 开始,表示客户区矩形
QString strRect;
QString r1, r2, r3, r4;
strRect = r1.setNum(rect().x()) + ","
+ r2.setNum(rect().y()) + ","
+ r3.setNum(rect().width()) + ","
+ r4.setNum(rect().height());
labelrectvalue->setText(strRect);
// size() 返回客户区宽高
QString strSize;
QString sz1, sz2;
strSize = sz1.setNum(size().width()) + ","
+ sz2.setNum(size().height());
labelsizevalue->setText(strSize);
}

二、Qt 绘图框架设计(QPainter 绘图)
Qt 的绘图系统是一套成熟、跨平台的二维图形渲染体系,核心围绕 QPainter / QPaintDevice / QPaintEngine 三层架构展开。以下从架构设计、核心机制、坐标系统、性能优化等维度进行系统梳理。
2.1 案例:QPainter 基本形状绘制
painterarea.h
cpp
#ifndef PAINTERAREA_H
#define PAINTERAREA_H
#include <QWidget>
#include <QPen> // 画笔:绘制直线、曲线、多边形轮廓
#include <QBrush> // 画刷:填充矩形、多边形等形状内部
#include <QPainter>
class PainterArea : public QWidget
{
Q_OBJECT
public:
explicit PainterArea(QWidget *parent = nullptr);
// 可绘制形状枚举
enum shape {
Line, // 直线
Rectangle, // 矩形
RoundRect, // 圆角矩形
Ellipse, // 椭圆
Polygon, // 多边形
Polyline, // 折线(不闭合)
Arc, // 弧线
Chord, // 弦
Pie, // 扇形
Path // 自定义路径
};
void setShape(shape);
void setPen(QPen);
void setBrush(QBrush);
void setFillRule(Qt::FillRule); // 设置填充规则
protected:
void paintEvent(QPaintEvent *) override; // 重绘事件
signals:
public slots:
private:
shape shp;
QPen pen;
QBrush brsh;
Qt::FillRule fillrle;
};
#endif // PAINTERAREA_H
painterarea.cpp
cpp
#include "painterarea.h"
PainterArea::PainterArea(QWidget *parent)
: QWidget(parent)
{
setPalette(QPalette(Qt::white));
setAutoFillBackground(true);
setMinimumSize(410, 410);
}
void PainterArea::setShape(shape sh)
{
shp = sh;
update(); // 请求重绘
}
void PainterArea::setPen(QPen sh)
{
pen = sh;
update();
}
void PainterArea::setBrush(QBrush sh)
{
brsh = sh;
update();
}
void PainterArea::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.setPen(pen);
p.setBrush(brsh);
QRect rect(55, 110, 290, 180);
switch (shp) {
case Line:
p.drawLine(rect.topLeft(), rect.bottomRight());
break;
case Rectangle:
p.drawRect(rect);
break;
case RoundRect:
p.drawRoundedRect(rect, 25, 25); // 圆角半径 25px
break;
case Ellipse:
p.drawEllipse(rect);
break;
case Polygon: {
QPolygon polygon;
polygon << QPoint(200, 110)
<< QPoint(345, 200)
<< QPoint(290, 290)
<< QPoint(110, 290)
<< QPoint(55, 200);
p.drawPolygon(polygon);
break;
}
case Polyline: {
QPolygon polyline;
polyline << QPoint(55, 290)
<< QPoint(130, 110)
<< QPoint(200, 250)
<< QPoint(270, 110)
<< QPoint(345, 290);
p.drawPolyline(polyline);
break;
}
case Arc:
// 起始角 0°,跨度 270°(Qt 角度单位为 1/16 度)
p.drawArc(rect, 0 * 16, 270 * 16);
break;
case Chord:
p.drawChord(rect, 0 * 16, 270 * 16);
break;
case Pie:
p.drawPie(rect, 0 * 16, 270 * 16);
break;
case Path: {
QPainterPath path;
path.moveTo(55, 200);
path.cubicTo(130, 110, 270, 290, 345, 200); // 三次贝塞尔曲线
p.drawPath(path);
break;
}
default:
break;
}
}
void PainterArea::setFillRule(Qt::FillRule rle)
{
fillrle = rle;
update();
}
widget.h
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QLabel>
#include <QComboBox>
#include <QGridLayout>
#include <QColorDialog>
#include "painterarea.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
PainterArea *labelshapeArea;
QLabel *labelshape;
QComboBox *comboxboxshape;
QGridLayout *glayout;
private slots:
void dispShapeFunc(int);
};
#endif // WIDGET_H
widget.cpp
cpp
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
labelshapeArea = new PainterArea;
labelshapeArea->setPen(QPen(Qt::blue, 4));
glayout = new QGridLayout;
setWindowTitle("Qt绘图框架测试 --- QPainter类");
labelshape = new QLabel("绘制形状:");
comboxboxshape = new QComboBox;
comboxboxshape->addItem("Line", PainterArea::Line);
comboxboxshape->addItem("Rectangle", PainterArea::Rectangle);
comboxboxshape->addItem("RoundRect", PainterArea::RoundRect);
comboxboxshape->addItem("Ellipse", PainterArea::Ellipse);
comboxboxshape->addItem("Polygon", PainterArea::Polygon);
comboxboxshape->addItem("Polyline", PainterArea::Polyline);
comboxboxshape->addItem("Arc", PainterArea::Arc);
comboxboxshape->addItem("Chord", PainterArea::Chord);
comboxboxshape->addItem("Pie", PainterArea::Pie);
comboxboxshape->addItem("Path", PainterArea::Path);
connect(comboxboxshape, SIGNAL(currentIndexChanged(int)),
this, SLOT(dispShapeFunc(int)));
// 启动时默认选择 Line
dispShapeFunc(0);
glayout->addWidget(labelshape, 0, 0);
glayout->addWidget(comboxboxshape, 0, 1);
QHBoxLayout *mainlayout = new QHBoxLayout(this);
mainlayout->addWidget(labelshapeArea);
mainlayout->addLayout(glayout);
}
Widget::~Widget()
{
}
void Widget::dispShapeFunc(int v)
{
PainterArea::shape sp = PainterArea::shape(
comboxboxshape->itemData(v, Qt::UserRole).toInt());
labelshapeArea->setShape(sp);
}

三、Qt 双缓冲机制
核心思想: "先在图片上画,再贴到控件上。"
3.1 传统绘制(单缓冲)的问题
如果直接在屏幕控件上绘图,当图形复杂或刷新频率较高时,人眼可能会看到绘制的中间过程------比如先画了一条线,再画一个圆,再填充颜色------这会导致画面闪烁,体验很差。
3.2 双缓冲的解决方案
步骤一:后台绘制(Off-Screen Buffer)
先在内存中创建一个不可见的"图片"(离屏缓冲区 / Buffer Image)。所有的绘图操作(画线、填充颜色等)都在这张内存图片上完成。因为是在内存中操作,速度极快,且用户完全看不见中间过程。
步骤二:前台显示(Flip / Blit)
待所有复杂的绘制工作在内存图片上全部完成后,再一次性将这张完整图片"复制"(绘制)到屏幕上的控件中。用户看到的始终是完整的最终结果,从而彻底消除闪烁。
3.3 案例:手绘板双缓冲实现
drawwidget.h
cpp
#ifndef DRAWWIDGET_H
#define DRAWWIDGET_H
#include <QWidget>
#include <QtGui>
#include <QMouseEvent> // 鼠标事件
#include <QPaintEvent> // 绘制事件
#include <QResizeEvent> // 窗口尺寸变化事件
#include <QColor>
#include <QPixmap> // 离屏画布(双缓冲核心)
#include <QPainter> // 绘图引擎
#include <QPalette>
#include <QPen>
class DrawWidget : public QWidget
{
Q_OBJECT
public:
explicit DrawWidget(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void paintEvent(QPaintEvent *) override;
void resizeEvent(QResizeEvent *) override;
public slots:
void setStyle(int s); // 设置画笔线型风格
void setWidth(int w); // 设置画笔线宽
void setColor(QColor c); // 设置画笔颜色
void clearFunc(); // 清除画布
signals:
private:
QPixmap *pix; // 离屏画布(双缓冲缓冲区)
QPoint startPos; // 线段起点
QPoint endPos; // 线段终点
int style; // 线型
int widthss; // 线宽
QColor color; // 颜色
};
#endif // DRAWWIDGET_H
drawwidget.cpp
cpp
#include "drawwidget.h"
// 构造函数:初始化画板控件
DrawWidget::DrawWidget(QWidget *parent)
: QWidget(parent)
{
setAutoFillBackground(true); // 允许自动填充背景色
setPalette(QPalette(Qt::white)); // 设置背景色为白色
// 创建与控件等大的离屏画布(双缓冲关键:后续所有绘制先画到这里)
pix = new QPixmap(size());
pix->fill(Qt::white); // 初始填充白色背景
// 初始化绘图参数
style = Qt::SolidLine; // 默认线型:实线
widthss = 2; // 默认线宽:2px
color = Qt::black; // 默认颜色:黑色
setMinimumSize(600, 400);
}
// 设置画笔线型风格
void DrawWidget::setStyle(int s)
{
style = s;
}
// 设置画笔线宽
void DrawWidget::setWidth(int w)
{
widthss = w;
}
// 设置画笔颜色
void DrawWidget::setColor(QColor c)
{
color = c;
}
// 清除画布:重新创建空白离屏缓冲区
void DrawWidget::clearFunc()
{
QPixmap *cPix = new QPixmap(size());
cPix->fill(Qt::white);
pix = cPix; // 替换为新画布
update(); // 触发 paintEvent 刷新界面
}
// 鼠标按下:记录起始点
void DrawWidget::mousePressEvent(QMouseEvent *e)
{
startPos = e->pos();
}
// 鼠标移动:在离屏画布上绘制线段(双缓冲第一步)
void DrawWidget::mouseMoveEvent(QMouseEvent *e)
{
QPainter painter;
QPen pen;
pen.setStyle((Qt::PenStyle)style);
pen.setWidth(widthss);
pen.setColor(color);
painter.begin(pix); // 在离屏画布 pix 上开始绘制
painter.setPen(pen);
painter.drawLine(startPos, e->pos()); // 从上一个点到当前鼠标位置画线
painter.end(); // 结束绘制
startPos = e->pos(); // 更新起点,保证下一段线首尾相连
update(); // 触发 paintEvent,将离屏画布刷新到屏幕
}
// 绘制事件:将离屏画布一次性贴到控件上(双缓冲第二步)
void DrawWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.drawPixmap(QPoint(0, 0), *pix); // 整张图片一次贴出,彻底消除闪烁
}
// 窗口尺寸改变:扩展画布并保留已有内容
void DrawWidget::resizeEvent(QResizeEvent *event)
{
// 仅当窗口比当前画布大时才需要重建
if (height() > pix->height() || width() > pix->width()) {
QPixmap *newPix = new QPixmap(size()); // 创建新尺寸的画布
newPix->fill(Qt::white); // 填充白色背景
QPainter ps(newPix);
ps.drawPixmap(QPoint(0, 0), *pix); // 将旧画布内容复制到新画布上
pix = newPix; // 替换为新画布
}
QWidget::resizeEvent(event); // 调用父类方法,保证正常行为
}
mainwindow.h
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QComboBox>
#include <QToolBar>
#include <QToolButton>
#include <QSpinBox>
#include <QGridLayout>
#include <QColorDialog>
#include "drawwidget.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
void CreateToolBarFunc();
QGridLayout *glayout;
DrawWidget *drawWidget;
QLabel *labelstyle;
QComboBox *comboboxstyle;
QLabel *labelwidth;
QSpinBox *spinboxlabelwidth;
QToolButton *colorbutton;
QToolButton *clearbutton;
private slots:
void dispstyle();
void dispcolor();
};
#endif // MAINWINDOW_H
mainwindow.cpp
cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle("图形绘制 --- 双缓冲机制");
drawWidget = new DrawWidget;
setCentralWidget(drawWidget);
CreateToolBarFunc();
setMinimumSize(600, 400);
dispstyle();
drawWidget->setWidth(spinboxlabelwidth->value()); // 初始化线宽
drawWidget->setColor(Qt::blue); // 初始化颜色
}
MainWindow::~MainWindow()
{
}
void MainWindow::CreateToolBarFunc()
{
QToolBar *toolBar = addToolBar("Tool");
// 线型选择
labelstyle = new QLabel("线型风格:");
comboboxstyle = new QComboBox;
comboboxstyle->addItem("SolidLine", static_cast<int>(Qt::SolidLine));
comboboxstyle->addItem("DotLine", static_cast<int>(Qt::DotLine));
connect(comboboxstyle, SIGNAL(activated(int)), this, SLOT(dispstyle()));
// 线宽选择
labelwidth = new QLabel("线型宽度:");
spinboxlabelwidth = new QSpinBox;
connect(spinboxlabelwidth, SIGNAL(valueChanged(int)),
drawWidget, SLOT(setWidth(int)));
// 颜色选择按钮
colorbutton = new QToolButton;
QPixmap pixmap(20, 20);
pixmap.fill(Qt::blue); // 与初始化颜色一致
colorbutton->setIcon(QIcon(pixmap));
colorbutton->setToolTip("选择颜色");
connect(colorbutton, SIGNAL(clicked()), this, SLOT(dispcolor()));
// 清除按钮
clearbutton = new QToolButton;
clearbutton->setText("清除绘制");
connect(clearbutton, SIGNAL(clicked()), drawWidget, SLOT(clearFunc()));
// 加入工具栏
toolBar->addWidget(labelstyle);
toolBar->addWidget(comboboxstyle);
toolBar->addWidget(labelwidth);
toolBar->addWidget(spinboxlabelwidth);
toolBar->addWidget(colorbutton);
toolBar->addWidget(clearbutton);
}
void MainWindow::dispstyle()
{
drawWidget->setStyle(
comboboxstyle->itemData(comboboxstyle->currentIndex(),
Qt::UserRole).toInt());
}
void MainWindow::dispcolor()
{
QColor color = QColorDialog::getColor(static_cast<int>(Qt::black), this);
if (color.isValid()) {
drawWidget->setColor(color);
// 同步更新颜色按钮图标
QPixmap ps(20, 20);
ps.fill(color);
colorbutton->setIcon(QIcon(ps));
}
}

小结
本文从 Qt 窗口 API 基础出发,逐步深入到 QPainter 绘图框架,最后通过手绘板案例完整演示了双缓冲机制的设计与实现。核心要点回顾:
- Qt 窗口 API 以
QWidget为基类,提供了显示、尺寸、位置、事件处理等完整的方法体系 - QPainter 绘图框架 基于 QPainter / QPaintDevice / QPaintEngine 三层架构,支持直线、矩形、椭圆、多边形、贝塞尔曲线等多种形状
- 双缓冲机制 通过
QPixmap离屏画布先完成所有绘制操作,再一次性贴到屏幕------避免了逐笔绘制导致的闪烁问题