前言
自定義可拖動多邊形控件,原創作者是趙彥博(QQ:408815041 zyb920@hotmail.com),創作之初主要是為了能夠在視頻區域內用戶自定義可拖動的多個區域,即可用來作為警戒區域,也可用來其他的處理,拿到對應的多邊形坐標集合,本控件的主要難點是如何計算一個點在一個多邊形區域內,何時完成一個多邊形區域,支持多個多邊形。
實現的功能
- 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;
}
控件介紹
- 超過140個精美控件,涵蓋了各種儀表盤、進度條、進度球、指南針、曲線圖、標尺、溫度計、導航條、導航欄,flatui、高亮按鈕、滑動選擇器、農歷等。遠超qwt集成的控件數量。
- 每個類都可以獨立成一個單獨的控件,零耦合,每個控件一個頭文件和一個實現文件,不依賴其他文件,方便單個控件以源碼形式集成到項目中,較少代碼量。qwt的控件類環環相扣,高度耦合,想要使用其中一個控件,必須包含所有的代碼。
- 全部純Qt編寫,QWidget+QPainter繪制,支持Qt4.6到Qt5.12的任何Qt版本,支持mingw、msvc、gcc等編譯器,不亂碼,可直接集成到Qt Creator中,和自帶的控件一樣使用,大部分效果只要設置幾個屬性即可,極為方便。
- 每個控件都有一個對應的單獨的包含該控件源碼的DEMO,方便參考使用。同時還提供一個所有控件使用的集成的DEMO。
- 每個控件的源代碼都有詳細中文注釋,都按照統一設計規范編寫,方便學習自定義控件的編寫。
- 每個控件默認配色和demo對應的配色都非常精美。
- 超過120個可見控件,6個不可見控件。
- 部分控件提供多種樣式風格選擇,多種指示器樣式選擇。
- 所有控件自適應窗體拉伸變化。
- 集成自定義控件屬性設計器,支持拖曳設計,所見即所得,支持導入導出xml格式。
- 自帶activex控件demo,所有控件可以直接運行在ie瀏覽器中。
- 集成fontawesome圖形字體+阿里巴巴iconfont收藏的幾百個圖形字體,享受圖形字體帶來的樂趣。
- 所有控件最后生成一個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)。