Qt 自绘控件练习 太极八卦图

背景

这几天自己绘制了一些小部件,主要就是重写paintEvent()函数,这其中会涉及到坐标系相关内容,这次以绘制八卦图为契机,了解并掌握绘制事件中的坐标平移与旋转。
希望这个例子能起到抛砖引玉的作用,同时也希望对Qt有兴趣的伙伴们一起进步。

闲聊

“易有太极,是生两仪,两仪生四象,四象生八卦。”
大家对这句话应该都挺熟,我就不多说了。
八卦即八个卦相,分别为乾、坤、震、艮、离、坎、兑、巽。
八卦图分为伏羲八卦与文王八卦,也叫先天八卦与后天八卦。
这些大家应该也很熟,我就不卖弄啦。

效果

刚开始没有发现两条鱼的界限部分绘制反了,应该是反S形的,结果绘制成了S形了。道法自然,赶紧改正。
修改前的效果:
在这里插入图片描述
修改后的效果:
在这里插入图片描述

源码

八卦图

头文件

yinyang.h

/*******************************************************************************
 * @brief        the Eight Diagrams
 * @details      通过绘制阴阳八卦,掌握对坐标系的平移与旋转
 * @author       wujz
 * @date         2020-10-22 09:56:36
 * @version      1.0.0
 * @par Copyright (c): 
 * @par History:
*******************************************************************************/
#ifndef YINYANG_H
#define YINYANG_H

#include <QWidget>

class Yinyang : public QWidget
{
    Q_OBJECT
public:
    /* 八卦样式 */
    enum class EC_STYLE{
        XIAN_TIAN,      /* 伏羲先天八卦 */
        HOU_TIAN        /* 文王后天八卦 */
    };
    /* 爻 */
    enum class EC_YAO {
        YANG_YAO,       /* 阳爻 */
        YIN_YAO         /* 阴爻 */
    };
    /* 卦 */
    enum class EC_DIAGRAMS {
        QIAN,           /* 乾三连 */
        KUN,            /* 坤六断 */
        ZHEN,           /* 震仰盂 */
        GEN,            /* 艮覆碗 */
        LI,             /* 离中虚 */
        KAN,            /* 坎中满 */
        DUI,            /* 兑上缼 */
        XUN             /* 巽下断 */
    };

    explicit Yinyang(QWidget *parent = nullptr);
    virtual ~Yinyang();

    void set_angle(int angle);
    void set_radius(int radius);
    void set_style(EC_STYLE style);
    void set_yang_yao_length(int len);
    void set_yao_height(int height);
    void set_yao_space(int space);

protected:
    void paintEvent(QPaintEvent * event) override;
    void draw_yin_yang_yao(QPainter & painter, const QPointF & p1,
                  const QPointF & p2, EC_YAO yao);                /* 绘制爻 */
    void draw_diagram(QPainter & painter, EC_DIAGRAMS diagram);   /* 绘制卦 */
    void draw_the_eight_diagrams(QPainter & painter);             /* 绘制八卦图 */
    void draw_tai_ji(QPainter & painter);                         /* 绘制太极图 */

signals:

private:
    EC_STYLE my_style;
    int my_angle;        /* 旋转的角度 */
    int my_radius;       /* 太极图半径 */
    QTimer * my_timer;   /* 让太极图动起来 */

    int my_yang_yao_len; /* 阳爻长度 */
    int my_yao_height;   /* 爻高度 */
    int my_yao_space;    /* 卦中爻间距 */
};

#endif // YINYANG_H

实现文件

yinyang.cpp

#include <QPaintEvent>
#include <QPainter>
#include <QTimer>
#include "yinyang.h"

Yinyang::Yinyang(QWidget *parent)
    : QWidget(parent)
    , my_style(EC_STYLE::XIAN_TIAN)
    , my_angle(0)
    , my_radius(100)
    , my_yang_yao_len(70)
    , my_yao_height(10)
    , my_yao_space(10)
{
    my_timer = new QTimer(0);
    my_timer->setInterval(200);
    connect(my_timer, &QTimer::timeout, [=]{
        my_angle += 5;
        my_angle %= 360;
        update();
    });
    my_timer->start();
}

Yinyang::~Yinyang()
{
    if (my_timer) {
        if (my_timer->isActive())
            my_timer->stop();

        delete my_timer;
        my_timer = nullptr;
    }
}

void Yinyang::set_angle(int angle)
{
    my_angle = angle % 360;
}

void Yinyang::set_radius(int radius)
{
    my_radius = radius;
}

void Yinyang::set_style(Yinyang::EC_STYLE style)
{
    my_style = style;
}

void Yinyang::set_yang_yao_length(int len)
{
    my_yang_yao_len = len;
}

void Yinyang::set_yao_height(int height)
{
    my_yao_height = height;
}

void Yinyang::set_yao_space(int space)
{
    my_yao_space = space;
}

void Yinyang::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    /* 1. background */
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::gray);
    painter.drawRect(rect());

    /* 将坐标原点移到窗口中间位置 */
    painter.translate(width()>>1, height()>>1);

    /* 2. the Eight Diagrams */
    draw_the_eight_diagrams(painter);

    /* 3. Taiji */
    draw_tai_ji(painter);
}

/**
 * @brief Yinyang::draw_yin_yang_yao
 * 绘制爻
 * @param painter
 * @param p1   爻起点
 * @param p2   爻终点
 * @param yao  阴爻/阳爻
 * @remark 若yao为YIN_YAO,则p1,p2分别为阴爻前半部分的起点与终点
 */
void Yinyang::draw_yin_yang_yao(QPainter &painter, const QPointF &p1,
                                const QPointF &p2, Yinyang::EC_YAO yao)
{
    painter.save();

    painter.setPen(QPen(Qt::black, my_yao_height, Qt::SolidLine, Qt::FlatCap)); // do not cover the end of the line
    painter.drawLine(p1, p2);
    if (EC_YAO::YIN_YAO == yao) {
        qreal yin_yao_len = my_yang_yao_len*3/7.0;
        qreal space = my_yang_yao_len/7.0;
        QPointF pstart(p1), pstop(p2);
        pstart.setX(p2.x() + space);
        pstop.setX(p2.x() + yin_yao_len + space);
        painter.drawLine(pstart, pstop);
    }

    painter.restore();
}

/**
 * @brief Yinyang::draw_diagram
 * 绘制卦
 * @param painter
 * @param diagram 需要绘制的卦
 * @remark 根据需要绘制的卦,分别设置该卦的三个爻的起点,终点,属性,
 *         依据设置的样式,判定该卦的位置,设置相应的旋转角度
 */
void Yinyang::draw_diagram(QPainter &painter, EC_DIAGRAMS diagram)
{
    painter.save();

    EC_YAO top = EC_YAO::YANG_YAO;        /* 三爻 */
    EC_YAO middle = EC_YAO::YANG_YAO;     /* 二爻 */
    EC_YAO bottom = EC_YAO::YANG_YAO;     /* 初爻 */

    QPointF p01, p02;                     /* 初爻起点与终点 */
    QPointF p11, p12;                     /* 二爻起点与终点 */
    QPointF p21, p22;                     /* 三爻起点与终点 */

    qint16 x(my_yang_yao_len>>1), y(my_yao_height + my_yao_space);
    qint16 twice_y(y<<1);
    qint16 triple_y(twice_y + y);
    qreal yin_yao_len = my_yang_yao_len*3/7.0; /* 设置阴爻间空隙为1/7阳爻长度 */
    qreal yin_yao_stop = x - yin_yao_len;

    /* 基于先天八卦的乾位旋转 */
    if (EC_DIAGRAMS::QIAN == diagram) { // 三连
        p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(x, -my_radius - triple_y);// top
        p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(x, -my_radius - twice_y);  // middle
        p01 = QPointF(-x, -my_radius - y), p02 = QPointF(x, -my_radius - y);              // bottom

        if (EC_STYLE::HOU_TIAN == my_style)
            painter.rotate(135);
    }
    else if (EC_DIAGRAMS::KUN == diagram) { // 三断
        p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(-yin_yao_stop, -my_radius - triple_y);
        p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(-yin_yao_stop, -my_radius - twice_y);
        p01 = QPointF(-x, -my_radius - y), p02 = QPointF(-yin_yao_stop, -my_radius - y);
        top = middle = bottom = EC_YAO::YIN_YAO;
        if (EC_STYLE::XIAN_TIAN == my_style)
            painter.rotate(180);
        else if (EC_STYLE::HOU_TIAN == my_style)
            painter.rotate(45);
    }
    else if (EC_DIAGRAMS::DUI == diagram) { // 上缺
        p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(-yin_yao_stop, -my_radius - triple_y);
        p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(x, -my_radius - twice_y);
        p01 = QPointF(-x, -my_radius - y), p02 = QPointF(x, -my_radius - y);
        top = EC_YAO::YIN_YAO;
        middle = bottom = EC_YAO::YANG_YAO;
        if (EC_STYLE::XIAN_TIAN == my_style)
            painter.rotate(-45);
        else if (EC_STYLE::HOU_TIAN == my_style)
            painter.rotate(90);
    }
    else if (EC_DIAGRAMS::LI == diagram) { // 中虚
        p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(x, -my_radius - triple_y);
        p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(-yin_yao_stop, -my_radius - twice_y);
        p01 = QPointF(-x, -my_radius - y), p02 = QPointF(x, -my_radius - y);
        top = bottom = EC_YAO::YANG_YAO;
        middle = EC_YAO::YIN_YAO;
        if (EC_STYLE::XIAN_TIAN == my_style)
            painter.rotate(-90);
    }
    else if (EC_DIAGRAMS::ZHEN == diagram) { // 仰盂
        p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(-yin_yao_stop, -my_radius - triple_y);
        p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(-yin_yao_stop, -my_radius - twice_y);
        p01 = QPointF(-x, -my_radius - y), p02 = QPointF(x, -my_radius - y);
        top = middle = EC_YAO::YIN_YAO;
        bottom = EC_YAO::YANG_YAO;
        if (EC_STYLE::XIAN_TIAN == my_style)
            painter.rotate(-135);
        else if (EC_STYLE::HOU_TIAN == my_style)
            painter.rotate(-90);
    }
    else if (EC_DIAGRAMS::XUN == diagram) { // 下断
        p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(x, -my_radius - triple_y);
        p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(x, -my_radius - twice_y);
        p01 = QPointF(-x, -my_radius - y), p02 = QPointF(-yin_yao_stop, -my_radius - y);
        top = middle = EC_YAO::YANG_YAO;
        bottom = EC_YAO::YIN_YAO;
        if (EC_STYLE::XIAN_TIAN == my_style)
            painter.rotate(45);
        else if (EC_STYLE::HOU_TIAN == my_style)
            painter.rotate(-45);
    }
    else if (EC_DIAGRAMS::KAN == diagram) { // 中满
        p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(-yin_yao_stop, -my_radius - triple_y);
        p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(x, -my_radius - twice_y);
        p01 = QPointF(-x, -my_radius - y), p02 = QPointF(-yin_yao_stop, -my_radius - y);
        middle = EC_YAO::YANG_YAO;
        top = bottom = EC_YAO::YIN_YAO;
        if (EC_STYLE::XIAN_TIAN == my_style)
            painter.rotate(90);
        else if (EC_STYLE::HOU_TIAN == my_style)
            painter.rotate(180);
    }
    else if (EC_DIAGRAMS::GEN == diagram) { // 覆碗
        p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(x, -my_radius - triple_y);
        p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(-yin_yao_stop, -my_radius - twice_y);
        p01 = QPointF(-x, -my_radius - y), p02 = QPointF(-yin_yao_stop, -my_radius - y);
        top = EC_YAO::YANG_YAO;
        middle = bottom = EC_YAO::YIN_YAO;
        if (EC_STYLE::XIAN_TIAN == my_style)
            painter.rotate(135);
        else if (EC_STYLE::HOU_TIAN == my_style)
            painter.rotate(-135);
    }
    else {
    	painter.restore();
        return;
    }

    draw_yin_yang_yao(painter, p21, p22, top);
    draw_yin_yang_yao(painter, p11, p12, middle);
    draw_yin_yang_yao(painter, p01, p02, bottom);

    painter.restore();
}

/* 绘制八卦 */
void Yinyang::draw_the_eight_diagrams(QPainter &painter)
{
    painter.save();

    draw_diagram(painter, EC_DIAGRAMS::QIAN);
    draw_diagram(painter, EC_DIAGRAMS::KUN);
    draw_diagram(painter, EC_DIAGRAMS::LI);
    draw_diagram(painter, EC_DIAGRAMS::KAN);
    draw_diagram(painter, EC_DIAGRAMS::DUI);
    draw_diagram(painter, EC_DIAGRAMS::XUN);
    draw_diagram(painter, EC_DIAGRAMS::ZHEN);
    draw_diagram(painter, EC_DIAGRAMS::GEN);

    painter.restore();
}

/* 绘制太极图 */
void Yinyang::draw_tai_ji(QPainter &painter)
{
    painter.save();

    /* 旋转坐标系统 */
    painter.rotate(my_angle);

    /* 白鱼 */
    QPainterPath circle, tmp;
    circle.arcTo(-my_radius, -my_radius, my_radius<<1, my_radius<<1, 270, 180);
    tmp.addEllipse(QPointF(0, -my_radius>>1), my_radius>>1, my_radius>>1);
    circle -= tmp; /* 上半部减去半个圆 */

    tmp.clear();
    tmp.addEllipse(QPointF(0, my_radius>>1), my_radius>>1, my_radius>>1);
    circle += tmp; /* 下半部加上半个圆 */
    painter.fillPath(circle, Qt::white);

    tmp.clear();
    tmp.addEllipse(QPointF(0, my_radius>>1), my_radius>>2, my_radius>>2);
    painter.fillPath(tmp, Qt::black); /* 画上鱼眼 */

    /* 黑鱼 */
    circle.clear();
    circle.arcTo(-my_radius, -my_radius, my_radius<<1, my_radius<<1, 90, 180);
    tmp.clear();
    tmp.addEllipse(QPointF(0, -my_radius>>1), my_radius>>1, my_radius>>1);
    circle += tmp; /* 上半部加上半个圆 */

    tmp.clear();
    tmp.addEllipse(QPointF(0, my_radius>>1), my_radius>>1, my_radius>>1);
    circle -= tmp; /* 下半部减去半个圆 */
    painter.fillPath(circle, Qt::black);

    tmp.clear();
    tmp.addEllipse(QPointF(0, -my_radius>>1), my_radius>>2, my_radius>>2);
    painter.fillPath(tmp, Qt::white); /* 画上黑鱼的大白眼 */

    painter.restore();
}

测试文件

头文件

testtaiji.h

#ifndef TESTTAIJI_H
#define TESTTAIJI_H

#include <QWidget>
#include <QGridLayout>
#include "yinyang.h"

class TestTaiji : public QWidget
{
    Q_OBJECT
public:
    explicit TestTaiji(QWidget *parent = nullptr);
    virtual ~TestTaiji();

signals:

private:
    QGridLayout * my_gridlayout;
    Yinyang * my_yinyang_xian_tian;
    Yinyang * my_yinyang_hou_tian;
};

#endif // TESTTAIJI_H

实现文件

#include "testtaiji.h"

TestTaiji::TestTaiji(QWidget *parent) : QWidget(parent)
{
    my_yinyang_xian_tian = new Yinyang;

    my_yinyang_hou_tian = new Yinyang;
    my_yinyang_hou_tian->set_style(Yinyang::EC_STYLE::HOU_TIAN);

    my_gridlayout = new QGridLayout;
    my_gridlayout->addWidget(my_yinyang_xian_tian, 0, 0);
    my_gridlayout->addWidget(my_yinyang_hou_tian, 0, 1);
    setLayout(my_gridlayout);
}

TestTaiji::~TestTaiji()
{
    if (my_yinyang_xian_tian) {
        delete my_yinyang_xian_tian;
        my_yinyang_xian_tian = nullptr;
    }
    if (my_yinyang_hou_tian) {
        delete my_yinyang_hou_tian;
        my_yinyang_hou_tian = nullptr;
    }
    if (my_gridlayout) {
        delete my_gridlayout;
        my_gridlayout = nullptr;
    }
}

main文件

#include <QApplication>
#include "testtaiji.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    TestTaiji ttj;
    ttj.show();

    return a.exec();
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一个跨平台的C++应用程序开发框架,它提供了丰富的GUI(图形用户界面)和功能组件,其中包括自绘控件自绘控件是指通过重写绘制函数来实现自定义外观和行为的控件。 在Qt中,自绘控件通常是从QWidget类派生而来的子类。要使用自绘控件,你需要重写QWidget的paintEvent()函数,并在其中进行绘制操作。paintEvent()函数会在控件需要重新绘制时被调用,你可以在该函数中使用Qt提供的绘图工具进行绘制。 以下是使用paintEvent()函数自绘控件的基本步骤: 1. 创建一个继承自QWidget的子类,并重写其paintEvent()函数。 2. 在paintEvent()函数中,创建一个QPainter对象,并使用该对象进行绘制操作。 3. 使用QPainter提供的绘图函数(如drawRect()、drawText()等)来实现你想要的外观效果。 4. 根据需要,可以在其他事件处理函数中添加交互逻辑,例如鼠标点击事件等。 下面是一个简单的示例代码,展示了如何使用paintEvent()函数自绘一个简单的矩形控件: ```cpp #include <QtWidgets> class MyWidget : public QWidget { public: MyWidget(QWidget *parent = nullptr) : QWidget(parent) {} protected: void paintEvent(QPaintEvent *event) override { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 设置抗锯齿 painter.fillRect(rect(), Qt::blue); // 绘制蓝色背景 painter.setPen(Qt::white); // 设置画笔颜色为白色 painter.drawRect(rect().adjusted(10, 10, -10, -10)); // 绘制带边距的矩形 } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); MyWidget widget; widget.resize(200, 200); widget.show(); return app.exec(); } ``` 这个示例中,我们创建了一个名为MyWidget的自定义控件,重写了其paintEvent()函数,在其中使用QPainter对象绘制了一个带有蓝色背景和白色边框的矩形。在main()函数中,我们创建了一个应用程序对象,并显示了这个自定义控件

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值