QT5开发及实例学习之十六Qt5基础图形的绘制


前言

  设计界面,区分各种形状及画笔颜色、画笔线宽、画笔风格、画笔顶帽、画笔连接点、填充模式、铺展效果、画刷颜色、画刷风格设置等。


一、绘图框架设计

  绘制各种图形实例的框架:
在这里插入图片描述
  首先在 PaintArea 类中完成各种图形显示功能的 Widget,重绘 paintEvent() 函数。然后在主窗口 MainWidget 类中完成各种图形参数的选择。

二、绘图区的实现

  PaintArea 类继承自 QWidget 类,在类声明中,首先声明一个枚举型数据 Shape,列举了所有实例可能用到的图形形状;其次声明 setShape() 函数用于设置形状,setPen() 函数用于设置画笔,setBrush() 函数用于设置画刷,setFillRule() 函数用于设置填充模式,以及重绘事件 paintEvent() 函数;最后声明表示各种属性的私有变量。
  PaintArea 类的构造函数用于完成初始化工作,设置图形显示区域的背景色及最小显示尺寸,具体代码如下:

	PaintArea::PaintArea(QWidget *parent) : QWidget(parent)
	{
	    setPalette(QPalette(Qt::white));
	    setAutoFillBackground(true);
	    setMinimumSize(400, 400);
	}

  PaintArea 类的重画函数代码如下:

	void PaintArea::paintEvent(QPaintEvent *)
	{
	    QPainter p(this);                   //新建一个 QPainter 对象
	    p.setPen(pen);                      //设置 QPainter 对象的画笔
	    p.setBrush(brush);                  //设置 QPainter 对象的画刷
	    QRect rect(50, 100, 300, 200);      //设定一个方形区域,为画长方形、圆角方形、椭圆形等做准备
	    static const QPoint points[4] =     //创建一个 QPoint 的数组,包含四个点,为画多边形、多边线及点做准备
	    {
	        QPoint(150, 100),
	        QPoint(300, 150),
	        QPoint(350, 250),
	        QPoint(100, 300)
	    };
	    int startAngle = 30*16;             //(c)
	    int spanAngle = 120*16;
	    QPainterPath path;                  //新建一个 QPainterPath 对象为画路径做准备
	    path.addRect(150, 150, 100, 100);
	    path.moveTo(100, 100);
	    path.cubicTo(300, 100, 200, 200, 300, 300);
	    path.cubicTo(100, 300, 200, 200, 100, 100);
	    path.setFillRule(fillRule);
	    switch(shape)                       //(d)
	    {
	        case Line:                      //直线
	            p.drawLine(rect.topLeft(), rect.bottomRight()); break;
	        case Rectangle:                 //长方形
	            p.drawRect(rect); break;
	        case RoundRect:                 //圆角方形
	            p.drawRoundRect(rect); break;
	        case Ellipse:                   //椭圆形
	            p.drawEllipse(rect); break;
	        case Polygon:                   //多边形
	            p.drawPolygon(points, 4); break;
	        case Polyline:                  //多边线
	            p.drawPolyline(points, 4); break;
	        case Points:                    //点
	            p.drawPoints(points, 4); break;
	        case Arc:                       //弧
	            p.drawArc(rect, startAngle, spanAngle); break;
	        case Path:                      //路径
	            p.drawPath(path); break;
	        case Text:                      //文字
	            p.drawText(rect, Qt::AlignCenter, QObject::tr("Hello Qt!")); break;
	        case Pixmap:                    //图片
	            p.drawPixmap(150, 150, QPixmap("butterfly.png")); break;
	    }
	}

(c)其中,参数 startAngle 表示起始角,为弧形的起始点与圆心之间连线与水平方向的夹角;参数 spanAngle 表示的是跨度角,为弧形起点、终点分别与圆心连线之间的夹角。在这里插入图片描述
  注意:用 QPainter 画弧形所使用的角度值,是以 1/16° 为单位的,在画弧时即 1° 用 16 表示。
(d)switch(shape){…}:使用一个 switch() 语句,对所要画的形状做判断,调用 QPainter 的各个 draw() 函数完成图形的绘制。
 (1)利用 QPainter 绘制图形 (Shape) 。
  Qt为开发者提供了丰富的绘制基本图形的 draw() 函数,如下所示:
在这里插入图片描述
  除此之外,QPainter 类还提供了一个 drawPixmap() 函数,可以直接将图片画到刻画控件上。
 (2)利用 QPainterPath 绘制简单图形。
  利用 QPainterPath 绘制简单图形,QPainterPath 类为 QPainter 类提供了一个存储容器,里面包含了所要绘制的内容的集合及绘制的顺序,如长方形、多边形、曲线等各种任意图形。当需要绘制此预先存储在 QPainterPath 对象中的内容时,只需调用 QPainter 类的 drawPath() 函数即可。
  QPainterPath 类提供了许多函数接口,可以很方便地加入一些规则图形。例如,addRect() 函数加入一个方形,addEllipse() 函数加入一个椭圆形,addText() 函数加入一个字符串,addPolygon() 函数加入一个多边形等。同时,QPainterPath 类还提供了 addPath() 函数,用于加入另一个 QPainterPath 对象中保存的内容。
  QPainterPath 对象的当前点自动处在上一部分图形内容的结束点上,若下一部分图形的起点不在此结束点,则需调用 moveTo() 函数将当前点移动到下一部分图形的起点。

  cubicTo() 函数绘制的是贝塞尔曲线,如下所示。在这里插入图片描述
  需要三个参数,分别表示三个点 cubicTo(c1, c2, endPoint);c1,c2 为贝塞尔曲线的控制点。

  利用 QPainterPath 类可以实现 QPainter 类的 draw() 函数能够实现的所有图形。
  例如,对于 QPainter::drawRect() 函数,除可用上面介绍的 QPainterPath::addRect() 的方式实现外,还可以用如下方式实现:

	QPainterPath path;
	path.moveTo(0, 0);
	path.lineTo(200, 0);
	path.lineTo(200, 100);
	path.lineTo(0, 100);
	path.lineTo(0, 0);

  这是一个更通用的方法,其他(如多边形等)图形都能够使用这种方式实现。

三、主窗口的实现

  主窗口类 MainWidget 继承自 QWidget 类,包含完成各种图形参数选择的控制区的声明、一系列设置与画图相关参数的槽函数的声明,以及一个绘图区 PaintArea 对象的声明。
  MainWidget 类得构造函数中创建了各参数选择控件,代码如下:

MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
    paintArea = new PaintArea();
    shapeLabel = new QLabel(tr("形状:"));     //形状选择下拉列表框
    shapeComboBox = new QComboBox();
    shapeComboBox->addItem(tr("Line"), PaintArea::Line);    //(a)
    shapeComboBox->addItem(tr("Rectangle"), PaintArea::Rectangle);
    shapeComboBox->addItem(tr("RoundedRect"), PaintArea::RoundRect);
    shapeComboBox->addItem(tr("Ellipse"), PaintArea::Ellipse);
    shapeComboBox->addItem(tr("Polygon"), PaintArea::Polygon);
    shapeComboBox->addItem(tr("Polyline"), PaintArea::Polyline);
    shapeComboBox->addItem(tr("Points"), PaintArea::Points);
    shapeComboBox->addItem(tr("Arc"), PaintArea::Arc);
    shapeComboBox->addItem(tr("Path"), PaintArea::Path);
    shapeComboBox->addItem(tr("Text"), PaintArea::Text);
    shapeComboBox->addItem(tr("Pixmap"), PaintArea::Text);
    connect(shapeComboBox, SIGNAL(activated(int)), this, SLOT(showShape(int)));
    penColorLabel = new QLabel(tr("画笔颜色:"));            //画笔颜色选择控件
    penColorFrame = new QFrame();
    penColorFrame->setFrameStyle(QFrame::Panel|QFrame::Sunken);
    penColorFrame->setAutoFillBackground(true);
    penColorFrame->setPalette(QPalette(Qt::blue));
    penColorBtn = new QPushButton(tr("更改"));
    connect(penColorBtn, SIGNAL(clicked()), this, SLOT(showPenColor()));
    penWidthLabel = new QLabel(tr("画笔线宽:"));            //画笔线宽选择控件
    penWidthSpinBox = new QSpinBox();
    penWidthSpinBox->setRange(0, 20);
    connect(penWidthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(showPenWidth(int)));
    penStyleLabel = new QLabel(tr("画笔风格:"));            //画笔风格选择下拉列表框
    penStyleComboBox = new QComboBox();
    penStyleComboBox->addItem(tr("SolidLine"), static_cast<int>(Qt::SolidLine));    //(b)
    penStyleComboBox->addItem(tr("DashLine"), static_cast<int>(Qt::DashLine));
    penStyleComboBox->addItem(tr("DotLine"), static_cast<int>(Qt::DotLine));
    penStyleComboBox->addItem(tr("DashDotLine"), static_cast<int>(Qt::DashDotLine));
    penStyleComboBox->addItem(tr("DashDotDotLine"), static_cast<int>(Qt::DashDotDotLine));
    penStyleComboBox->addItem(tr("CustomDashLine"), static_cast<int>(Qt::CustomDashLine));
    connect(penStyleComboBox, SIGNAL(activated(int)), this, SLOT(showPenStyle(int)));
    penCapLabel = new QLabel(tr("画笔顶帽:"));              //画笔顶帽风格选择下拉列表框
    penCapComboBox = new QComboBox();
    penCapComboBox->addItem(tr("SquareCap"), Qt::SquareCap);    //(c)
    penCapComboBox->addItem(tr("FlatCap"), Qt::FlatCap);
    penCapComboBox->addItem(tr("RoundCap"), Qt::RoundCap);
    connect(penCapComboBox, SIGNAL(activated(int)), this, SLOT(showPenCap(int)));
    penJoinLabel = new QLabel(tr("画笔连接点:"));            //画笔连接点风格选择下拉列表框
    penJoinComboBox = new QComboBox();
    penJoinComboBox->addItem(tr("BevelJoin"), Qt::BevelJoin);   //(d)
    penJoinComboBox->addItem(tr("MiterJoin"), Qt::MiterJoin);
    penJoinComboBox->addItem(tr("RoundJoin"), Qt::RoundJoin);
    connect(penJoinComboBox, SIGNAL(activated(int)), this, SLOT(showPenJoin(int)));
    fillRuleLabel = new QLabel(tr("填充模式:"));            //填充模式选择下拉列表框
    fillRuleComboBox = new QComboBox();
    fillRuleComboBox->addItem(tr("Odd Even"), Qt::OddEvenFill); //(e)
    fillRuleComboBox->addItem(tr("Winding"), Qt::WindingFill);
    connect(fillRuleComboBox, SIGNAL(activated(int)), this, SLOT(showFillRule()));
    spreadLabel = new QLabel(tr("铺展效果:"));              //铺展效果选择下拉列表框
    spreadComboBox = new QComboBox();
    spreadComboBox->addItem(tr("PadSpread"), QGradient::PadSpread);         //(f)
    spreadComboBox->addItem(tr("RepeatSpread"), QGradient::RepeatSpread);
    spreadComboBox->addItem(tr("ReflectSpread"), QGradient::ReflectSpread);
    connect(spreadComboBox, SIGNAL(activated(int)), this, SLOT(showSpreadStyle()));
    brushColorLabel = new QLabel(tr("画刷颜色:"));          //画刷颜色选择控件
    brushColorFrame = new QFrame();
    brushColorFrame->setFrameStyle(QFrame::Panel|QFrame::Sunken);
    brushColorFrame->setAutoFillBackground(true);
    brushColorFrame->setPalette(QPalette(Qt::green));
    brushColorBtn = new QPushButton(tr("更改"));
    connect(brushColorBtn, SIGNAL(clicked()), this, SLOT(showBrushColor()));
    brushStyleLabel = new QLabel(tr("画刷风格:"));          //画刷风格选择下拉列表框
    brushStyleComboBox = new QComboBox();
    brushStyleComboBox->addItem(tr("SolidPattern"), static_cast<int>(Qt::SolidPattern));    //(g)
    brushStyleComboBox->addItem(tr("Densel1Pattern"), static_cast<int>(Qt::Dense1Pattern));
    brushStyleComboBox->addItem(tr("Densel2Pattern"), static_cast<int>(Qt::Dense2Pattern));
    brushStyleComboBox->addItem(tr("Densel3Pattern"), static_cast<int>(Qt::Dense3Pattern));
    brushStyleComboBox->addItem(tr("Densel4Pattern"), static_cast<int>(Qt::Dense4Pattern));
    brushStyleComboBox->addItem(tr("Densel5Pattern"), static_cast<int>(Qt::Dense5Pattern));
    brushStyleComboBox->addItem(tr("Densel6Pattern"), static_cast<int>(Qt::Dense6Pattern));
    brushStyleComboBox->addItem(tr("Densel7Pattern"), static_cast<int>(Qt::Dense7Pattern));
    brushStyleComboBox->addItem(tr("HorPattern"), static_cast<int>(Qt::HorPattern));
    brushStyleComboBox->addItem(tr("VerPattern"), static_cast<int>(Qt::VerPattern));
    brushStyleComboBox->addItem(tr("CrossPattern"), static_cast<int>(Qt::CrossPattern));
    brushStyleComboBox->addItem(tr("BDiagPattern"), static_cast<int>(Qt::BDiagPattern));
    brushStyleComboBox->addItem(tr("FDiagPattern"), static_cast<int>(Qt::FDiagPattern));
    brushStyleComboBox->addItem(tr("DiagCrossPattern"), static_cast<int>(Qt::DiagCrossPattern));
    brushStyleComboBox->addItem(tr("LinearGradientPattern"), static_cast<int>(Qt::LinearGradientPattern));
    brushStyleComboBox->addItem(tr("ConicalGradientPattern"), static_cast<int>(Qt::ConicalGradientPattern));
    brushStyleComboBox->addItem(tr("RadialGradientPattern"), static_cast<int>(Qt::RadialGradientPattern));
    brushStyleComboBox->addItem(tr("TexturePattern"), static_cast<int>(Qt::TexturePattern));
    connect(brushStyleComboBox, SIGNAL(activated(int)), this, SLOT(showBrush(int)));
    rightLayout = new QGridLayout();                        //控制面板布局
    rightLayout->addWidget(shapeLabel, 0, 0);
    rightLayout->addWidget(shapeComboBox, 0, 1);
    rightLayout->addWidget(penColorLabel, 1, 0);
    rightLayout->addWidget(penColorFrame, 1, 1);
    rightLayout->addWidget(penColorBtn, 1, 2);
    rightLayout->addWidget(penWidthLabel, 2, 0);
    rightLayout->addWidget(penWidthSpinBox, 2, 1);
    rightLayout->addWidget(penStyleLabel, 3, 0);
    rightLayout->addWidget(penStyleComboBox, 3, 1);
    rightLayout->addWidget(penCapLabel, 4, 0);
    rightLayout->addWidget(penCapComboBox, 4, 1);
    rightLayout->addWidget(penJoinLabel, 5, 0);
    rightLayout->addWidget(penJoinComboBox, 5, 1);
    rightLayout->addWidget(fillRuleLabel, 6, 0);
    rightLayout->addWidget(fillRuleComboBox, 6, 1);
    rightLayout->addWidget(spreadLabel, 7, 0);
    rightLayout->addWidget(spreadComboBox, 7, 1);
    rightLayout->addWidget(brushColorLabel, 8, 0);
    rightLayout->addWidget(brushColorFrame, 8, 1);
    rightLayout->addWidget(brushColorBtn, 8, 2);
    rightLayout->addWidget(brushStyleLabel, 9, 0);
    rightLayout->addWidget(brushStyleComboBox, 9, 1);
    QHBoxLayout *mainLayout = new QHBoxLayout(this);        //整体布局
    mainLayout->addWidget(paintArea);
    mainLayout->addLayout(rightLayout);
    mainLayout->setStretchFactor(paintArea, 1);
    mainLayout->setStretchFactor(rightLayout, 0);
    showShape(shapeComboBox->currentIndex());               //显示默认的图形
}

(a)QComboBox 的 additem() 函数可以仅插入文本,也可以同时插入与文本相对应的具体数据,通常为枚举类型数据,便于后面操作时确定选择的是哪个数据。
(b)选用不同的参数,对应画笔的不同风格。
(c)选用不同的参数,对应画笔顶帽的不同风格。其中,Qt::SquareCap 表示在线条的顶点处是方形的,且线条绘制的区域包括了端点,并且再往外延伸半个线宽的长度;Qt::FlatCap 表示在线条的顶点处是方形的,但线条绘制区域不包括端点在内;Qt::RoundCap 表示在线条的顶点处是圆形的,且线条绘制区域包含了端点。
(d)选用不同的参数,对应画笔连接点的不同风格。其中,Qt::BevelJoin 风格连接点是指两条线的中心线顶点相汇,相连处依然保留线条各自的方形顶端;Qt::MiterJoin 风格连接点是指两条线的中心线顶点相汇,相连处线条延长到线的外侧汇集至点,形成一个尖顶的连接;Qt::RoundJoin 风格连接点是指两条线的中心线顶点相汇,相连处以圆弧形连接。
(e)Qt 为 QPainterPath 类提供了两种填充规则,分别是 Qt::OddEvenFill 和 Qt::WindingFill。这两种填充规则在判定图形中某一点是处于内部还是外部时,判断依据不同。
  其中,Qt::OddEvenFill 填充规则判断的依据是从图形中某一点画一条水平线到图形外。若这条水平线与图形边线的交点数目为奇数,则说明此点位于图形的内部;若交点数目为偶数,则此点位于图形的外部。
  而 Qt::WindingFill 填充规则的判断依据则是从图形中某一点画一条水平线到图形外,每个交点外边线的方向可能向上,也可能向下,将这些交点数累加,方向相反的相互抵消,若最后结果不为 0 则说明此点在图形内,若最后结果为 0 则说明在图形外。
  其中,边线的方向是由 QPainterPath 创建时根据描述的顺序决定的。如果采用 addRect() 或 addPolygon() 等函数加入的图形,默认是按顺时针方向。
(f)铺展效果有三种,分别为 QGradient::PadSpread、QGradient::RepeatSpread 和 QGradient::ReflectSpread。其中,PadSpread 是默认的铺展效果,也是最常见的铺展效果,没有被渐变覆盖的区域填充单一的起始颜色或终止颜色;RepeatSpread 效果与 ReflectSpread 效果只对线性渐变和圆形渐变起作用。
  使用 QGradient 的 setColorAt() 函数设置起止的颜色,其中,第 1 个参数表示所设颜色点的位置,取值范围为 0.0~1.0,0.0 表示起点,1.0 表示终点;第 2 个参数表示该点的颜色值。除可设置起点和终点的颜色外,如有需要还可设置中间任意位置的颜色,例如:setColorAt(0.3, Qt::white),设置起、终点之间 1/3 位置的颜色为白色。
(g)选用不同的参数,对应画刷的不同风格。

  showShape() 槽函数,根据当前下拉列表框中选择的选项,调用 PaintArea 类的 setShape() 函数设置 PaintArea 对象的形状参数,代码如下:

void MainWidget::showShape(int value)
{
    PaintArea::Shape shape = PaintArea::Shape(shapeComboBox->itemData(value, Qt::UserRole).toInt());
    paintArea->setShape(shape);
}

  其中,QComboBox 类的 itemData 方法返回当前显示的下拉列表框数据,是一个 QVariant 对象,此对象与控件初始化时插入的枚举型数据相关,调用 QVariant 类的 toInt() 函数获得此数据在枚举型数据集合中的序号。

  showBrush() 槽函数的具体实现代码如下:

void MainWidget::showBrush(int value)
{
    //获得画刷的颜色
    QColor color = brushColorFrame->palette().color(QPalette::Window);
    Qt::BrushStyle style = Qt::BrushStyle(brushStyleComboBox->itemData(value, Qt::UserRole).toInt());   //(a)
    if(style == Qt::LinearGradientPattern)                       //(b)
    {
        QLinearGradient linearGradient(0, 0, 400, 400);
        linearGradient.setColorAt(0.0, Qt::white);
        linearGradient.setColorAt(0.2, color);
        linearGradient.setColorAt(1.0, Qt::black);
        linearGradient.setSpread(spread);
        paintArea->setBrush(linearGradient);
    }
    else if(style == Qt::RadialGradientPattern)                 //(c)
    {
        QRadialGradient radialGradient(200, 200, 150, 150, 100);
        radialGradient.setColorAt(0.0, Qt::white);
        radialGradient.setColorAt(0.2, color);
        radialGradient.setColorAt(1.0, Qt::black);
        radialGradient.setSpread(spread);
        paintArea->setBrush(radialGradient);
    }
    else if(style == Qt::ConicalGradientPattern)                //(d)
    {
        QConicalGradient conicalGradient(200, 200, 30);
        conicalGradient.setColorAt(0.0, Qt::white);
        conicalGradient.setColorAt(0.2, color);
        conicalGradient.setColorAt(1.0, Qt::black);
        paintArea->setBrush(conicalGradient);
    }
    else if(style == Qt::TexturePattern)
    {
        paintArea->setBrush(QBrush(QPixmap("image.png")));
    }
    else
    {
        paintArea->setBrush(QBrush(color, style));
    }
}

(a)获得所选的画刷风格,若选择的是渐变或者纹理图案,则需要进行一定的处理。
(b)主窗口的 style 变量值为 Qt::LinearGradientPattern 时,表明选择的是线形渐变。
  QLinearGradient linearGradient(startPoint, endPoint) 创建线形渐变类对象需要两个参数,分别表示起止点位置。
(c)主窗口的 style 变量值为 Qt::RadialGradientPattern 时,表明选择的是圆形渐变。
  QRadialGradiend radialGradient(startPoint, r, endPoint) 创建圆形渐变类对象需要三个参数,分别表示圆心位置、半径值和焦点位置。表示以 startPoint 作为圆心和焦点的位置,以 startPoint 和 endPoint 之间的距离 r 为半径,当然圆心和焦点的位置也可以不重合。
(d)主窗口的 style 变量值为 Qt::ConicalGradientPattern 时,表明选择的是锥形渐变。
  QConicalGradient conicalGradient(startPoint, -(180*angle)/PI) 创建锥形渐变类对象需要两个参数,分别是锥形的顶点位置和渐变分界线与水平方向的夹角。锥形渐变不需要设置铺展效果,它的铺展效果只能是 QGradient::PadSpread。
  注意:锥形渐变的方向默认是逆时针方向。

  Qt 画图的坐标系默认以左上角为原点,X 轴向右,Y 轴向下。此坐标系可受 QPainter 类控制,对其进行变形,QPainter 类提供了相应的变形函数,包括旋转、缩放、平移和切变。调用这些函数时,显示设备的坐标系会发生相应变形,但绘制内容相对坐标系的位置并不会发生改变,因此看起来像是对绘制内容进行了变形,而实质上是坐标系的变形。若还需要更加复杂的变形,则可以采用 QMatrix 类实现。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值