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

目录

  • 背景
  • 闲聊
  • 效果
  • 源码
    • 八卦图
      • 头文件
      • 实现文件
    • 测试文件
      • 头文件
      • 实现文件
      • main文件

背景

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

闲聊

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

效果

刚开始没有发现两条鱼的界限部分绘制反了,应该是反S形的,结果绘制成了S形了。道法自然,赶紧改正。
修改前的效果:
Qt 自绘控件练习 太极八卦图_第1张图片
修改后的效果:
Qt 自绘控件练习 太极八卦图_第2张图片

源码

八卦图

头文件

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 

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 
#include 
#include 
#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 
#include 
#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 
#include "testtaiji.h"

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

    return a.exec();
}

你可能感兴趣的:(qt,qt,自绘,八卦)