Qt編寫自定義控件7-自定義可拖動多邊形

前言

自定義可拖動多邊形控件,原創做者是趙彥博(QQ:408815041 zyb920@hotmail.com),創做之初主要是爲了可以在視頻區域內用戶自定義可拖動的多個區域,便可用來做爲警惕區域,也可用來其餘的處理,拿到對應的多邊形座標集合,本控件的主要難點是如何計算一個點在一個多邊形區域內,什麼時候完成一個多邊形區域,支持多個多邊形。c++

實現的功能

  • 1:自定義隨意繪製多邊形
  • 2:產生閉合形狀後可單擊選中移動整個多邊形
  • 3:可拉動某個點
  • 4:支持多個多邊形
  • 5:鼠標右鍵退出繪製
  • 6:可設置各類顏色

效果圖

頭文件代碼

#ifndef CUSTOMGRAPHICS_H
#define CUSTOMGRAPHICS_H

/**
 * 自定義多邊形控件 做者:趙彥博(QQ:408815041 zyb920@hotmail.com) 2019-3-28
 * 1:自定義隨意繪製多邊形
 * 2:產生閉合形狀後可單擊選中移動整個多邊形
 * 3:可拉動某個點
 * 4:支持多個多邊形
 * 5:鼠標右鍵退出繪製
 * 6:可設置各類顏色
 */

#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 CustomGraphics : public QWidget
#else
class CustomGraphics : public QWidget
#endif

{
    Q_OBJECT
    Q_PROPERTY(bool selectDotVisible READ getSelectDotVisible WRITE setSelectDotVisible)
    Q_PROPERTY(int dotRadius READ getDotRadius WRITE setDotRadius)
    Q_PROPERTY(int lineWidth READ getLineWidth WRITE setLineWidth)

    Q_PROPERTY(QColor dotColor READ getDotColor WRITE setDotColor)
    Q_PROPERTY(QColor lineColor READ getLineColor WRITE setLineColor)
    Q_PROPERTY(QColor polygonColor READ getPolygonColor WRITE setPolygonColor)
    Q_PROPERTY(QColor selectColor READ getSelectColor WRITE setSelectColor)

public:
    typedef struct {
        QVector<QPoint> pos;
        bool selected;
    } Polygon;

    explicit CustomGraphics(QWidget *parent = 0);

protected:
    void mousePressEvent(QMouseEvent *e);
    void mouseMoveEvent(QMouseEvent *e);
    void mouseReleaseEvent(QMouseEvent *e);
    void paintEvent(QPaintEvent *);
    void drawPolygon(QPainter *p, const Polygon &v);
    void drawLines(QPainter *p, const QList<QPoint> &list, bool isFirst = true);

private:
    bool selectDotVisible;      //選中點可見
    int dotRadius;              //點的半徑
    int lineWidth;              //線條寬度

    QColor dotColor;            //點的顏色
    QColor lineColor;           //線條顏色
    QColor polygonColor;        //多邊形顏色
    QColor selectColor;         //選中顏色

    QPoint tempPoint;           //臨時點
    QList<QPoint> tempPoints;   //點集合
    QList<Polygon> tempPolygons;//多邊形集合

    bool pressed;               //鼠標是否按下
    QPoint lastPoint;           //鼠標按下處的座標
    QPoint ellipsePos;          //保存按下點的座標
    int selectedEllipseIndex;   //選中點的index
    Polygon pressedPolygon;     //保存按下時多邊形的原始座標
    int selectedIndex;          //選中多邊形的index

private:
    //計算兩點間的距離
    double length(const QPoint &p1, const QPoint &p2);
    //檢測是否選中多邊形
    bool checkPoint(const QVector<QPoint> &points, int x, int y);

public:
    bool getSelectDotVisible()  const;
    int getDotRadius()          const;
    int getLineWidth()          const;

    QColor getDotColor()        const;
    QColor getLineColor()       const;
    QColor getPolygonColor()    const;
    QColor getSelectColor()     const;

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

public Q_SLOTS:
    void setSelectDotVisible(bool selectDotVisible);
    void setDotRadius(int dotRadius);
    void setLineWidth(int lineWidth);

    void setDotColor(const QColor &dotColor);
    void setLineColor(const QColor &lineColor);
    void setPolygonColor(const QColor &polygonColor);
    void setSelectColor(const QColor &selectColor);

    //清除臨時繪製的
    void clearTemp();
    //清除全部
    void clearAll();
};

#endif // CUSTOMGRAPHICS_H

核心代碼

void CustomGraphics::mousePressEvent(QMouseEvent *e)
{
    QPoint p = e->pos();
    pressed = true;
    lastPoint = this->mapToGlobal(p);

    //連線模式下不選中
    if(tempPoints.isEmpty()) {
        //若是選中了,檢測是否點到點上
        bool selectedPot = false;
        selectedEllipseIndex = -1;
        if (selectedIndex != -1) {
            for(int i = tempPolygons.at(selectedIndex).pos.size() - 1; i >= 0; --i) {
                if(length(p, tempPolygons.at(selectedIndex).pos[i]) <= 36) {
                    selectedPot = true;
                    selectedEllipseIndex = i;
                    ellipsePos = tempPolygons.at(selectedIndex).pos[i];
                    break;
                }
            }
        }

        //當前選中了點則不用重繪
        if(selectedPot) {
            return;
        }

        //判斷是否選中一個
        selectedIndex = -1;
        for(int i = tempPolygons.size() - 1; i >= 0; --i) {
            tempPolygons[i].selected = checkPoint(tempPolygons.at(i).pos, p.x(), p.y());
            if(tempPolygons.at(i).selected) {
                //防止重疊部分
                if(selectedIndex == -1) {
                    selectedIndex = i;
                    pressedPolygon = tempPolygons.at(i);
                } else {
                    tempPolygons[i].selected = false;
                }
            }
        }

        this->update();
    }
}

void CustomGraphics::mouseMoveEvent(QMouseEvent *e)
{
    tempPoint = e->pos();
    if(pressed && selectedIndex != -1) {
        //總體偏移座標
        QPoint delta = this->mapToGlobal(tempPoint) - lastPoint;
        int len = tempPolygons.at(selectedIndex).pos.size();

        if(selectedEllipseIndex != -1) { //移動點
            tempPolygons[selectedIndex].pos[selectedEllipseIndex] = ellipsePos + delta;
        } else if(selectedIndex != -1) { //移動面
            for(int i = 0; i < len; ++i) {
                tempPolygons[selectedIndex].pos[i] = pressedPolygon.pos.at(i) + delta;
            }
        }
    }

    this->update();
}

void CustomGraphics::mouseReleaseEvent(QMouseEvent *e)
{
    //鼠標右鍵清空臨時的
    if (e->button() == Qt::RightButton) {
        clearTemp();
        return;
    }

    //檢測再次點擊與最後個點 - 還沒寫
    pressed = false;
    if(selectedIndex != -1) {
        return;
    }

    QPoint point = e->pos();
    if(tempPoints.count() > 0) {
        qreal len = (qPow(tempPoints.first().x() - point.x() , 2.0) + qPow(tempPoints.first().y() - point.y() , 2.0) );
        if(len < 100) {
            //完成一個多邊形
            if(tempPoints.size() >= 3) {
                Polygon pol;
                pol.pos = tempPoints.toVector();
                pol.selected = false;
                tempPolygons.append(pol);
            }

            tempPoints.clear();
            this->update();
            return;
        }
    }

    tempPoints.append(point);
    this->update();
}

void CustomGraphics::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing, true);

    //繪製多邊形
    foreach(const Polygon &p, tempPolygons) {
        drawPolygon(&painter, p);
    }

    //繪製點集合
    drawLines(&painter, tempPoints, false);
}

void CustomGraphics::drawPolygon(QPainter *p, const Polygon &v)
{
    p->save();

    //繪製多邊形
    p->setPen(QPen(lineColor, lineWidth));
    v.selected ? p->setBrush(selectColor) : p->setBrush(polygonColor);
    p->drawPolygon(v.pos.data(), v.pos.size());

    //繪製圓點
    if(selectDotVisible && v.selected) {
        p->setPen(Qt::NoPen);
        p->setBrush(dotColor);
        foreach(const QPoint &point, v.pos) {
            p->drawEllipse(point, dotRadius, dotRadius);
        }
    }

    p->restore();
}

void CustomGraphics::drawLines(QPainter *p, const QList<QPoint> &list, bool isFirst)
{
    p->save();

    int count = list.count();
    if (count > 0) {
        //繪製點集合
        p->setPen(Qt::NoPen);
        p->setBrush(dotColor);
        for(int i = 0; i < count; ++i) {
            p->drawEllipse(list.at(i), dotRadius, dotRadius);
        }

        //繪製線條集合
        p->setPen(QPen(lineColor, lineWidth));
        p->setBrush(Qt::NoBrush);
        for(int i = 0; i < count - 1; ++i) {
            p->drawLine(list.at(i), list.at(i + 1));
        }

        //繪製最後一條線條
        p->drawLine(list.last(), isFirst ? list.first() : tempPoint);
    }

    p->restore();
}

double CustomGraphics::length(const QPoint &p1, const QPoint &p2)
{
    //平方和
    return qPow(p1.x() - p2.x(), 2.0) + qPow(p1.y() - p2.y(), 2.0);
}

bool CustomGraphics::checkPoint(const QVector<QPoint> &points, int testx, int testy)
{
    //最少保證3個點
    const int count = points.size();
    if(count < 3) {
        return false;
    }

    QList<int> vertx, verty;
    for(int i = 0; i < count; ++i) {
        vertx << points.at(i).x();
        verty << points.at(i).y();
    }

    //核心算法,計算座標是否在多邊形內部
    int i = 0, j, c = 0;
    for (i = 0, j = count - 1; i < count; j = i++) {
        bool b1 = (verty.at(i) > testy) != (verty.at(j) > testy);
        bool b2 = (testx < (vertx.at(j) - vertx.at(i)) * (testy - verty.at(i)) / (verty.at(j) - verty.at(i)) + vertx.at(i));
        if (b1 && b2) {
            c = !c;
        }
    }

    return c;
}

控件介紹

  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使用(永久免費),無任何後門和限制,請放心使用。
  • 不按期增長控件和完善控件,不按期更新SDK,歡迎各位提出建議,謝謝!
  • widget版本(QQ:517216493)qml版本(QQ:373955953)三峯駝(QQ:278969898)。

相關文章
相關標籤/搜索