Qt編寫自定義控件10-雲臺儀表盤

前言

作過安防視頻監控的同窗都清楚,在視頻監控系統軟件上均可以看到一個雲臺控制區域,能夠對球機進行下下左右等八個方位的運動控制,還能夠進行復位,通常都是美工做圖好,而後貼圖的形式加入到軟件中,好處是程序簡單,界面美工,主要取決於美工的美圖能力,缺點是對於各類分辨率的適應性稍微差點,須要不一樣的圖片切圖貼圖,除非默認作好的是大圖自適應看不出差異,可能大部分人所在的公司都是小公司,通常美工人員比較少甚至沒有,都須要程序員一人負責,甚至一開始就要考慮到各類分辨率的應用場景以及後期可能的換膚換色等。 以前作過不少自定義控件,大部分都採用了qpainter的形式繪製,有個好處就是自適應任意分辨率,因此思考着這個雲臺控制儀表盤也採用純painter繪製的形式,聽說純painter繪製還能夠輕鬆移植到qml中,這又堅決了我用qpainter繪製的決心。所謂心中有座標系,萬物皆painter。 觀察雲臺儀表盤下來,基本上就這幾部分組成,圓形底盤,八個角,中間部分按鈕,整個的控件的難點就在於八個角的定位,中間部分很好定位,並且八個角不是絕對的位置,都是相對於界面的寬高按照等比例自適應排列的。八個角的鼠標按下要作出對應的反應,發送出對應型號,網上大部分人都是切圖或者放置label或者按鈕來貼圖實現,綁定事件過濾器過濾鼠標按下而後再發出信號。我這裏爲了提高逼格,直接採用位置座標計算法。c++

實現的功能

  • 1:可設置背景顏色
  • 2:可設置基準顏色
  • 3:可設置邊框顏色
  • 4:可設置文本顏色
  • 5:可識別每一個角度+中間 鼠標按下併發出信號
  • 6:可設置八個角的圖標和中間圖標,隨便換
  • 7:內置4種雲臺風格 黑色+白色+藍色+紫色
  • 8:支持拓展鼠標進入離開時的切換
  • 9:精準識別內圓區域鼠標按下,而不是圓的矩形區域
  • 10:支持長按連續觸發,支持設定延時間隔和執行間隔

效果圖

頭文件代碼

#ifndef GAUGECLOUD_H
#define GAUGECLOUD_H

/**
 * 雲臺儀表盤控件 做者:feiyangqingyun(QQ:517216493) 2018-9-2
 * 1:可設置背景顏色
 * 2:可設置基準顏色
 * 3:可設置邊框顏色
 * 4:可設置文本顏色
 * 5:可識別每一個角度+中間 鼠標按下併發出信號
 * 6:可設置八個角的圖標和中間圖標,隨便換
 * 7:內置4種雲臺風格 黑色+白色+藍色+紫色
 * 8:支持拓展鼠標進入離開時的切換
 * 9:精準識別內圓區域鼠標按下,而不是圓的矩形區域
 * 10:支持長按連續觸發,支持設定延時間隔和執行間隔
 */

#include <QWidget>

#ifdef quc
#if (QT_VERSION < QT_VERSION_CHECK(5,7,0))
#include <QtDesigner/QDesignerExportWidget>
#else
#include <QtUiPlugin/QDesignerExportWidget>
#endif

class QDESIGNER_WIDGET_EXPORT GaugeCloud : public QWidget
#else
class GaugeCloud : public QWidget
#endif

{
    Q_OBJECT
    Q_ENUMS(CloudStyle)

    Q_PROPERTY(QColor baseColor READ getBaseColor WRITE setBaseColor)
    Q_PROPERTY(QColor bgColor READ getBgColor WRITE setBgColor)
    Q_PROPERTY(QColor arcColor READ getArcColor WRITE setArcColor)
    Q_PROPERTY(QColor borderColor READ getBorderColor WRITE setBorderColor)
    Q_PROPERTY(QColor textColor READ getTextColor WRITE setTextColor)
    Q_PROPERTY(QColor enterColor READ getEnterColor WRITE setEnterColor)
    Q_PROPERTY(QColor pressColor READ getPressColor WRITE setPressColor)

    Q_PROPERTY(QString iconText READ getIconText WRITE setIconText)
    Q_PROPERTY(QString centerText READ getCenterText WRITE setCenterText)
    Q_PROPERTY(CloudStyle cloudStyle READ getCloudStyle WRITE setCloudStyle)

    Q_PROPERTY(bool autoRepeat READ getAutoRepeat WRITE setAutoRepeat)
    Q_PROPERTY(int autoRepeatDelay READ getAutoRepeatDelay WRITE setAutoRepeatDelay)
    Q_PROPERTY(int autoRepeatInterval READ getAutoRepeatInterval WRITE setAutoRepeatInterval)

public:
    enum CloudStyle {
        CloudStyle_Black = 0,   //黑色風格
        CloudStyle_White = 1,   //白色風格
        CloudStyle_Blue = 2,    //藍色風格
        CloudStyle_Purple = 3   //紫色風格
    };

    explicit GaugeCloud(QWidget *parent = 0);
    ~GaugeCloud();

protected:
    void enterEvent(QEvent *);
    void leaveEvent(QEvent *);
    void mousePressEvent(QMouseEvent *);
    void mouseReleaseEvent(QMouseEvent *);
    void mouseMoveEvent(QMouseEvent *);
    void paintEvent(QPaintEvent *);
    void drawCircle(QPainter *painter, int radius, const QBrush &brush);
    void drawArc(QPainter *painter);
    void drawText(QPainter *painter);

private:
    QColor bgColor;                 //背景顏色
    QColor baseColor;               //基準顏色
    QColor arcColor;                //圓弧顏色
    QColor borderColor;             //邊框顏色
    QColor textColor;               //文字顏色
    QColor enterColor;              //懸停文字顏色
    QColor pressColor;              //按下文字顏色

    QString iconText;               //八個角圖標
    QString centerText;             //中間圖標
    CloudStyle cloudStyle;          //雲臺樣式

    bool autoRepeat;                //是否重複執行按下
    int autoRepeatDelay;            //延時執行按下動做
    int autoRepeatInterval;         //重複執行間隔

    bool enter;                     //鼠標是否進入
    bool pressed;                   //鼠標是否按下
    bool inCenter;                  //是否在內圓中
    QPoint lastPoint;               //鼠標按下處的座標
    QRectF centerRect;              //中間區域
    QRectF leftRect;                //左側圖標區域
    QRectF topRect;                 //上側圖標區域
    QRectF rightRect;               //右側圖標區域
    QRectF bottomRect;              //下側圖標區域
    QRectF leftTopRect;             //左上角圖標區域
    QRectF rightTopRect;            //右上角圖標區域
    QRectF leftBottomRect;          //左下角圖標區域
    QRectF rightBottomRect;         //右下角圖標區域

    QFont iconFont;                 //圖形字體
    int position;                   //最後按下的位置
    QTimer *timer;                  //定時器觸發長按

private slots:
    void checkPressed();

private:
    double twoPtDistance(const QPointF &pt1, const QPointF &pt2);

public:
    QColor getBgColor()             const;
    QColor getBaseColor()           const;
    QColor getArcColor()            const;
    QColor getBorderColor()         const;
    QColor getTextColor()           const;
    QColor getEnterColor()          const;
    QColor getPressColor()          const;

    QString getIconText()           const;
    QString getCenterText()         const;
    CloudStyle getCloudStyle()      const;

    bool getAutoRepeat()            const;
    int getAutoRepeatDelay()        const;
    int getAutoRepeatInterval()     const;

    QSize sizeHint()                const;
    QSize minimumSizeHint()         const;

public Q_SLOTS:
    //設置背景顏色
    void setBgColor(const QColor &bgColor);
    //設置基準顏色
    void setBaseColor(const QColor &baseColor);
    //設置圓弧顏色
    void setArcColor(const QColor &arcColor);
    //設置邊框顏色
    void setBorderColor(const QColor &borderColor);
    //設置文本顏色
    void setTextColor(const QColor &textColor);
    //設置懸停文本顏色
    void setEnterColor(const QColor &enterColor);
    //設置按下文本顏色
    void setPressColor(const QColor &pressColor);

    //設置八個角圖標
    void setIconText(const QString &iconText);
    //設置中間圖標
    void setCenterText(const QString &centerText);
    //設置雲臺樣式
    void setCloudStyle(const CloudStyle &cloudStyle);

    //設置是否啓用長按重複執行
    void setAutoRepeat(bool autoRepeat);
    //設置延時執行長按時間
    void setAutoRepeatDelay(int autoRepeatDelay);
    //設置長按重複執行間隔
    void setAutoRepeatInterval(int autoRepeatInterval);

Q_SIGNALS:
    //鼠標按下的區域,共9個,從0-8依次表示底部/左下角/左側/左上角/頂部/右上角/右側/右下角/中間
    void mousePressed(int position);
    //鼠標鬆開
    void mouseReleased(int position);
};

#endif //GAUGECLOUD_H

核心代碼

void GaugeCloud::paintEvent(QPaintEvent *)
{
    int width = this->width();
    int height = this->height();
    int side = qMin(width, height);

    //以中心點爲基準,分別計算八方位區域和中間區域
    QPointF center = this->rect().center();
    double centerSize = (double)side / ((double)100 / 30);
    double iconSize = (double)side / ((double)100 / 10);
    double offset1 = 3.6;
    double offset2 = 2.65;

    //計算當前按下點到中心點的距離,若是小於內圓的半徑則認爲在內圓中
    double offset = twoPtDistance(lastPoint, this->rect().center());
    inCenter = (offset <= centerSize / 2);

    //中間區域
    centerRect = QRectF(center.x() - centerSize / 2, center.y() - centerSize / 2, centerSize, centerSize);
    //左側圖標區域
    leftRect = QRectF(center.x() - iconSize * offset1, center.y() - iconSize / 2, iconSize, iconSize);
    //上側圖標區域
    topRect = QRectF(center.x() - iconSize / 2, center.y() - iconSize * offset1, iconSize, iconSize);
    //右側圖標區域
    rightRect = QRectF(center.x() + iconSize * (offset1 - 1), center.y() - iconSize / 2, iconSize, iconSize);
    //下側圖標區域
    bottomRect = QRectF(center.x() - iconSize / 2, center.y() + iconSize * (offset1 - 1), iconSize, iconSize);
    //左上角圖標區域
    leftTopRect = QRectF(center.x() - iconSize * offset2, center.y() - iconSize * offset2, iconSize, iconSize);
    //右上角圖標區域
    rightTopRect = QRectF(center.x() + iconSize * (offset2 - 1), center.y() - iconSize * offset2, iconSize, iconSize);
    //左下角圖標區域
    leftBottomRect = QRectF(center.x() - iconSize * offset2, center.y() + iconSize * (offset2 - 1), iconSize, iconSize);
    //右下角圖標區域
    rightBottomRect = QRectF(center.x() + iconSize * (offset2 - 1), center.y() + iconSize * (offset2 - 1), iconSize, iconSize);

    //繪製準備工做,啓用反鋸齒,平移座標軸中心,等比例縮放
    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    painter.translate(width / 2, height / 2);
    painter.scale(side / 200.0, side / 200.0);

    if (cloudStyle == CloudStyle_Black) {
        //繪製外圓背景
        drawCircle(&painter, 99, bgColor);
        //繪製圓弧
        drawArc(&painter);
        //繪製中間圓盤背景
        drawCircle(&painter, 83, baseColor);
        //繪製內圓背景
        drawCircle(&painter, 40, arcColor);
        //繪製內圓邊框
        drawCircle(&painter, 33, borderColor);
        //繪製內圓
        drawCircle(&painter, 30, inCenter ? bgColor : baseColor);
    } else if (cloudStyle == CloudStyle_White) {
        //繪製外圓背景
        drawCircle(&painter, 99, QColor(249, 249, 249));

        //設置圓錐漸變
        QConicalGradient gradient(0, 0, 100);
        gradient.setColorAt(0, QColor(34, 163, 169));
        gradient.setColorAt(0.4, QColor(240, 201, 136));
        gradient.setColorAt(0.7, QColor(211, 77, 37));
        gradient.setColorAt(1, QColor(34, 163, 169));

        //繪製彩色外圓
        drawCircle(&painter, 90, gradient);
        //繪製中間圓盤背景
        drawCircle(&painter, 83, QColor(245, 245, 245));
        //繪製內圓背景
        drawCircle(&painter, 33, QColor(208, 208, 208));
        //繪製內圓邊框
        drawCircle(&painter, 32, QColor(208, 208, 208));
        //繪製內圓
        drawCircle(&painter, 30, inCenter ? QColor(255, 255, 255) : QColor(245, 245, 245));
    } else if (cloudStyle == CloudStyle_Blue) {
        //設置圓錐漸變
        QConicalGradient gradient(0, 0, 100);
        gradient.setColorAt(0, QColor(79, 163, 219));
        gradient.setColorAt(0.4, QColor(227, 183, 106));
        gradient.setColorAt(0.7, QColor(217, 178, 109));
        gradient.setColorAt(1, QColor(79, 163, 219));

        //繪製色彩外圓
        drawCircle(&painter, 99, gradient);
        //繪製中間圓盤背景
        drawCircle(&painter, 91, QColor(31, 66, 98));
        //繪製內圓背景
        drawCircle(&painter, 33, QColor(23, 54, 81));
        //繪製內圓邊框
        drawCircle(&painter, 30, QColor(150, 150, 150));
        //繪製內圓
        drawCircle(&painter, 30, inCenter ? QColor(35, 82, 133) : QColor(34, 73, 115));
    } else if (cloudStyle == CloudStyle_Purple) {
        //設置圓錐漸變
        QConicalGradient gradient(0, 0, 100);
        gradient.setColorAt(0, QColor(87, 87, 155));
        gradient.setColorAt(0.4, QColor(129, 82, 130));
        gradient.setColorAt(0.7, QColor(54, 89, 166));
        gradient.setColorAt(1, QColor(87, 87, 155));

        //繪製色彩外圓
        drawCircle(&painter, 99, gradient);
        //繪製中間圓盤背景
        drawCircle(&painter, 91, QColor(55, 55, 92));
        //繪製內圓背景
        drawCircle(&painter, 33, QColor(49, 48, 82));
        //繪製內圓邊框
        drawCircle(&painter, 30, QColor(82, 78, 131));
        //繪製內圓
        drawCircle(&painter, 30, inCenter ? QColor(85, 81, 137) : QColor(62, 59, 103));
    }

    //繪製八方位+中間圖標
    drawText(&painter);

#if 0
    //重置座標系,並繪製八方位區域及中間區域,判斷是否正確
    painter.resetMatrix();
    painter.resetTransform();
    painter.setPen(Qt::white);
    painter.drawRect(centerRect);
    painter.drawRect(leftRect);
    painter.drawRect(topRect);
    painter.drawRect(rightRect);
    painter.drawRect(bottomRect);
    painter.drawRect(leftTopRect);
    painter.drawRect(rightTopRect);
    painter.drawRect(leftBottomRect);
    painter.drawRect(rightBottomRect);
#endif
}

void GaugeCloud::drawCircle(QPainter *painter, int radius, const QBrush &brush)
{
    painter->save();
    painter->setPen(Qt::NoPen);
    painter->setBrush(brush);

    //繪製圓
    painter->drawEllipse(-radius, -radius, radius * 2, radius * 2);
    painter->restore();
}

void GaugeCloud::drawArc(QPainter *painter)
{
    int radius = 91;
    painter->save();
    painter->setBrush(Qt::NoBrush);

    QPen pen;
    pen.setWidthF(10);
    pen.setColor(arcColor);
    painter->setPen(pen);

    QRectF rect = QRectF(-radius, -radius, radius * 2, radius * 2);
    painter->drawArc(rect, 0 * 16, 360 * 16);

    painter->restore();
}

void GaugeCloud::drawText(QPainter *painter)
{
    bool ok;
    int radius = 100;
    painter->save();

    //判斷當前按下座標是否在中心區域,按下則文本不一樣顏色
    if (inCenter) {
        if (pressed) {
            position = 8;
            if (autoRepeat) {
                QTimer::singleShot(autoRepeatDelay, timer, SLOT(start()));
            }

            emit mousePressed(position);
        }

        painter->setPen(pressed ? pressColor : enterColor);
    } else {
        painter->setPen(textColor);
    }

    QFont font;
    font.setPixelSize(30);
#if (QT_VERSION >= QT_VERSION_CHECK(4,8,0))
    font.setHintingPreference(QFont::PreferNoHinting);
#endif
    painter->setFont(font);

    //繪製中間圖標
    QRectF centerRect(-radius, -radius, radius * 2, radius * 2);
    QString centerText = this->centerText.replace("0x", "");
    QChar centerChar = QChar(centerText.toInt(&ok, 16));
    painter->drawText(centerRect, Qt::AlignCenter, centerChar);

    //繪製八方位圖標
    radius = 70;
    int offset = 15;
    int steps = 8;
    double angleStep = 360.0 / steps;

    font.setPixelSize(28);
    painter->setFont(font);

    //從下側圖標開始繪製,順時針旋轉
    QRect iconRect(-offset / 2, radius - offset, offset, offset);
    QString iconText = this->iconText.replace("0x", "");
    QChar iconChar = QChar(iconText.toInt(&ok, 16));
    for (int i = 0; i < steps; i++) {
        //判斷鼠標按下的是哪一個區域
        bool contains = false;
        if (bottomRect.contains(lastPoint) && i == 0) {
            contains = true;
        } else if (leftBottomRect.contains(lastPoint) && i == 1) {
            contains = true;
        } else if (leftRect.contains(lastPoint) && i == 2) {
            contains = true;
        } else if (leftTopRect.contains(lastPoint) && i == 3) {
            contains = true;
        } else if (topRect.contains(lastPoint) && i == 4) {
            contains = true;
        } else if (rightTopRect.contains(lastPoint) && i == 5) {
            contains = true;
        } else if (rightRect.contains(lastPoint) && i == 6) {
            contains = true;
        } else if (rightBottomRect.contains(lastPoint) && i == 7) {
            contains = true;
        }

        if (contains) {
            if (pressed) {
                position = i;
                if (autoRepeat) {
                    QTimer::singleShot(autoRepeatDelay, timer, SLOT(start()));
                }

                emit mousePressed(position);
            }

            painter->setPen(pressed ? pressColor : enterColor);
        } else {
            painter->setPen(textColor);
        }

        painter->drawText(iconRect, Qt::AlignCenter, iconChar);
        painter->rotate(angleStep);
    }

    painter->restore();
}

控件介紹

  1. 超過140個精美控件,涵蓋了各類儀表盤、進度條、進度球、指南針、曲線圖、標尺、溫度計、導航條、導航欄,flatui、高亮按鈕、滑動選擇器、農曆等。遠超qwt集成的控件數量。
  2. 每一個類均可以獨立成一個單獨的控件,零耦合,每一個控件一個頭文件和一個實現文件,不依賴其餘文件,方便單個控件以源碼形式集成到項目中,較少代碼量。qwt的控件類環環相扣,高度耦合,想要使用其中一個控件,必須包含全部的代碼。
  3. 所有純Qt編寫,QWidget+QPainter繪製,支持Qt4.6到Qt5.12的任何Qt版本,支持mingw、msvc、gcc等編譯器,不亂碼,可直接集成到Qt Creator中,和自帶的控件同樣使用,大部分效果只要設置幾個屬性便可,極爲方便。
  4. 每一個控件都有一個對應的單獨的包含該控件源碼的DEMO,方便參考使用。同時還提供一個全部控件使用的集成的DEMO。
  5. 每一個控件的源代碼都有詳細中文註釋,都按照統一設計規範編寫,方便學習自定義控件的編寫。
  6. 每一個控件默認配色和demo對應的配色都很是精美。
  7. 超過120個可見控件,6個不可見控件。
  8. 部分控件提供多種樣式風格選擇,多種指示器樣式選擇。
  9. 全部控件自適應窗體拉伸變化。
  10. 集成自定義控件屬性設計器,支持拖曳設計,所見即所得,支持導入導出xml格式。
  11. 自帶activex控件demo,全部控件能夠直接運行在ie瀏覽器中。
  12. 集成fontawesome圖形字體+阿里巴巴iconfont收藏的幾百個圖形字體,享受圖形字體帶來的樂趣。
  13. 全部控件最後生成一個dll動態庫文件,能夠直接集成到qtcreator中拖曳設計使用。

SDK下載

  • SDK下載連接:https://pan.baidu.com/s/1tD9v1YPfE2fgYoK6lqUr1Q 提取碼:lyhk
  • 自定義控件+屬性設計器欣賞:https://pan.baidu.com/s/1l6L3rKSiLu_uYi7lnL3ibQ 提取碼:tmvl
  • 下載連接中包含了各個版本的動態庫文件,全部控件的頭文件,使用demo。
  • 自定義控件插件開放動態庫dll使用(永久免費),無任何後門和限制,請放心使用。
  • 目前已提供22個版本的dll,其中包括了qt5.12.3 msvc2017 32+64 mingw 32+64 的。
  • 不按期增長控件和完善控件,不按期更新SDK,歡迎各位提出建議,謝謝!
  • widget版本(QQ:517216493)qml版本(QQ:373955953)三峯駝(QQ:278969898)。

相關文章
相關標籤/搜索