Qt编写自定义控件7-自定义可拖动多边形
前言
自定义可拖动多边形控件,原创作者是赵彦博(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)。


Qt编写自定义控件7-自定义可拖动多边形的更多相关文章
- Qt编写自定义控件二动画按钮
		现在的web发展越来越快,很多流行的布局样式,都是从web开始的,写惯了Qt widgets 项目,很多时候想改进一下现有的人机交互,尤其是在现有的按钮上加一些动画的效果,例如鼠标移上去变大,移开还原 ... 
- Qt编写自定义控件67-通用无边框
		一.前言 在之前的一篇文章中写过一个通用的移动控件,作用就是用来传入任意的widget控件,可以在父类容器中自由移动.本篇文章要写的是一个通用的无边框类,确切的说这不叫控件应该叫组件才对,控件是要看得 ... 
- Qt编写自定义控件38-高亮按钮
		一.前言 高亮按钮控件,既可以作为类似于交通指示灯使用,也可以作为设备状态指示灯使用,控件内置多套颜色风格,还可以自己设置颜色风格,按钮可以增加文字显示,非常适合需要在状态设备上显示小量的文字展示,按 ... 
- Qt编写自定义控件插件开放动态库dll使用(永久免费)
		这套控件陆陆续续完善了四年多,目前共133个控件,除了十几个控件参考网友开源的代码写的,其余全部原创,在发布之初就有打算将动态库开放出来永久免费使用,在控件比较完善的今天抽了半天时间编译了多个qt版本 ... 
- Qt编写自定义控件11-设备防区按钮控件
		前言 在很多项目应用中,需要根据数据动态生成对象显示在地图上,比如地图标注,同时还需要可拖动对象到指定位置显示,能有多种状态指示,安防领域一般用来表示防区或者设备,可以直接显示防区号,有多种状态颜色指 ... 
- Qt编写自定义控件6-指南针仪表盘
		前言 指南针仪表盘,主要用来指示东南西北四个方位,双向对称两个指针旋转,其实就是360度打转,功能属于简单型,可能指针的绘制稍微难一点,需要计算多个点构成多边形,本系列控件文章将会连续发100+篇,一 ... 
- Qt编写自定义控件2-进度条标尺
		前言 进度条标尺控件的应用场景一般是需要手动拉动进度,上面有标尺可以看到当前进度,类似于qslider控件,其实就是qslider+qprogressbar的杂交版本,不过我才用的是纯qpainter ... 
- Qt编写自定义控件72-提示进度条
		一.前言 我们在很多的安装包中,在安装过程中,经常可以在底部看到一个漂亮的进度条,上面悬浮着显示对应的进度,然后底部进度多种颜色渐变展示,Qt自带的进度条或者操作系统的进度条样式,不够炫,这次索性直接 ... 
- Qt编写自定义控件61-通用移动
		一.前言 通用移动类,目标就是为了实现放入任意的控件以后,支持鼠标拖动,在容器中或者父类中拖动,这个应用场景非常多,比如在地图上放置的设备,需要用户自行按下拖动到指定的合适的位置,然后保存设备的位置坐 ... 
随机推荐
- Codeforces Round #419 (Div. 2) ABC
			python 2.7,用来熟悉Python 由于都是智障题,所以我也不讲述题意和题解,直接贴代码了-- A import sys h,m = map(int,raw_input().split(&qu ... 
- linux 下通过xhost进入图形界面,经常会出现报错“unable to  open display”
			linux 下通过xhost进入图形界面,经常会出现报错“unable to open display” linux下的操作步骤如下: [root@localhost ~]# vncserver N ... 
- GMA Round 1 相交
			传送门 相交 在实数范围内,设抛物线$C_1:y^2=2x$,双曲线:$C_2:\frac{y^2}{b^2}-\frac{x^2}{a^2}=1$(a,b为参数). 假如a和b都在(0,16)这个区 ... 
- 28、初识socket(subprocess模块)
			经过近一个半月的学习我们已经度过了python基础的阶段,今天我们开始学习python网络编程,没有难以理解的逻辑,更注重的是记忆. 本篇导航: 客户端/服务器架构 scoket与网络协议 套接字 基 ... 
- java使用httpclient封装post请求和get的请求
			在我们程序员生涯中,经常要复用代码,所以我们应该养成时常整理代码的好习惯,以下是我之前封装的httpclient的post和get请求所用的代码: package com.marco.common; ... 
- Spring Boot中使用Swagger2自动构建API文档
			由于Spring Boot能够快速开发.便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API.而我们构建RESTful API的目的通常都是由于多终端的原因,这 ... 
- SpringBoot(十三):springboot2.0.2定时任务
			使用定义任务: 第一步:启用定时任务第二步:配置定时器资源等第三步:定义定时任务并指定触发规则 1)启动类启用定时任务 在springboot入口类上添加注解@EnableScheduling即可. ... 
- 树莓派raspberry pi配置无线路由器AP
			raspi-config进入系统配置面板 进行 Expand Filesystem 扩展文件系统否则备份系统的时候备份文件会急速膨胀. (1)配置网络环境nano /etc/network/inte ... 
- 数据库的范式,第一、二、三、四、五范式、BC范式
			数据库的规范化(上一篇博客有写到)的程度不同,便有了这么多种范式.数据库范式是数据库设计必不可少的知识,没有对范式的理解,就无法设计出高效率.优雅的数据库,甚至设计出错误误的数据库.课本中的定义比较抽 ... 
- git reflog
			http://www.softwhy.com/article-8573-1.html https://www.cnblogs.com/irocker/p/git-reflog.html https:/ ... 
