Qt 绘制可以用鼠标拖动的线段 Qt 绘制可以用鼠标拖动的线段(一)

Qt 绘制可以用鼠标拖动的线段(一)

  • 一、环境
    VS2013 + QT5.7.0
  • 二、效果
    1.可以创建任意多条线段;
    2.鼠标在靠近到线段时产生吸附效果;
    3.可以拖动任意一条线段的任意部位(线段的两个端点或者整条线段)。
    效果图:
    这里写图片描述
  • 三、说明
    1.创建线段的定义:

线段具有起始点和终止点。

//点
struct PointEx {
    double x;
    double y;
    PointEx(double a = 0, double b = 0) {
        x = a;
        y = b;
    }
};

//线段
struct LineSegment {
    PointEx startPoint;
    PointEx endPoint;
    LineSegment(PointEx a, PointEx b) {
        startPoint = a;
        endPoint = b;
    }
    LineSegment() {
    }
};
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2.定义线段显示的定义:

    struct LINESEG {
        bool bDraw;//是否绘制

        bool bSelLine;//是否选中线
        bool bSelStartPt;//是否选中线段起点
        bool bSelEndPt;//是否选线段终点
        LineSegment* seg;
        LINESEG() {
            bDraw = false;
            bSelLine = false;
            bSelStartPt = false;
            bSelEndPt = false;
            seg = new LineSegment;
        }
    };
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3.把创建的线段使用,vector保存起来

std::vector<LINESEG*> lineSegs;//线段列表
  
  
  • 1

4.剩余的就是在鼠标点击、移动、松开时的逻辑控制了

//按下鼠标
void MyGraphCal::mousePressEvent(QMouseEvent *event) {
    switch(event->button()) {
    case Qt::LeftButton:
    bLBtnDown = true;
    selectLineSeg = nullptr;
    selectLineSeg = getSeleled();
    if(selectLineSeg != nullptr){//选中线段
        selectLineSeg->bDraw = false;

        if(selectLineSeg->bSelStartPt) {//选中起点
            startPoint.setX(selectLineSeg->seg->endPoint.x);
            startPoint.setY(selectLineSeg->seg->endPoint.y);

            endPoint.setX(selectLineSeg->seg->startPoint.x);
            endPoint.setY(selectLineSeg->seg->startPoint.y);
        } else if(selectLineSeg->bSelEndPt) {//选中终点
            startPoint.setX(selectLineSeg->seg->startPoint.x);
            startPoint.setY(selectLineSeg->seg->startPoint.y);

            endPoint.setX(selectLineSeg->seg->endPoint.x);
            endPoint.setY(selectLineSeg->seg->endPoint.y);
        } else if(selectLineSeg->bSelLine){//选中线段
            movePoint = event->pos();

            startPoint.setX(selectLineSeg->seg->startPoint.x);
            startPoint.setY(selectLineSeg->seg->startPoint.y);

            endPoint.setX(selectLineSeg->seg->endPoint.x);
            endPoint.setY(selectLineSeg->seg->endPoint.y);
        }
        update();
    } else {//未选中
        startPoint = event->pos();
        endPoint = startPoint;

        tempLine = new LINESEG;
        tempLine->seg->startPoint.x = startPoint.x();
        tempLine->seg->startPoint.y = startPoint.y();
    }
    break;
    default:
    break;
    }
}

//移动鼠标
void MyGraphCal::mouseMoveEvent(QMouseEvent *event) {
    QPointF movePt = event->pos();
    if (selectLineSeg != nullptr){//选中线段
        if(bLBtnDown) {//鼠标按下
            if(selectLineSeg->bSelStartPt || selectLineSeg->bSelEndPt) {//选中起点或者终点
                endPoint = movePt;
            } else if(selectLineSeg->bSelLine) {//选中线段
                double disX = movePt.x() - movePoint.x();
                double disY = movePt.y() - movePoint.y();

                startPoint.setX(startPoint.x() + disX);
                startPoint.setY(startPoint.y() + disY);

                endPoint.setX(endPoint.x() + disX);
                endPoint.setY(endPoint.y() + disY);

                movePoint = movePt;
            }
        }
    } else {//未选中线段
        if(bLBtnDown) {
            endPoint = movePt;
        } else {
            selSeg(movePt);
        }
    }
    update();

}

//松开鼠标
void MyGraphCal::mouseReleaseEvent(QMouseEvent *event) {
    switch(event->button()) {
    case Qt::LeftButton:
    bLBtnDown = false;
    if(selectLineSeg != nullptr){

        if(selectLineSeg->bSelStartPt) {//选中起点
            selectLineSeg->seg->startPoint.x = event->pos().x();
            selectLineSeg->seg->startPoint.y = event->pos().y();

        } else if(selectLineSeg->bSelEndPt) {//选中终点
            selectLineSeg->seg->endPoint.x = event->pos().x();
            selectLineSeg->seg->endPoint.y = event->pos().y();
        } else if(selectLineSeg->bSelLine) {//选中线段
            selectLineSeg->seg->startPoint.x = startPoint.x();
            selectLineSeg->seg->startPoint.y = startPoint.y();

            selectLineSeg->seg->endPoint.x = endPoint.x();
            selectLineSeg->seg->endPoint.y = endPoint.y();
        }
        selectLineSeg->bDraw = true;

        selectLineSeg->bSelStartPt = false;
        selectLineSeg->bSelEndPt = false;
        selectLineSeg->bSelLine = false;
        selectLineSeg = nullptr;
    } else {
        tempLine->seg->endPoint.x = event->pos().x();
        tempLine->seg->endPoint.y = event->pos().y();
        tempLine->bDraw = true;
        lineSegs.push_back(tempLine);
    }

    break;
    default:
    break;
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

5.注意要添加上线段鼠标移动的激活操作,否则鼠标只有在按下的时候才会激活mouseMoveEvent

ui.centralWidget->setMouseTracking(true);
setMouseTracking(true);
  
  
  • 1
  • 2

6.如何判断当前鼠标靠近某一条线段呢?

  1. 先确定出当前点到这条线段的所在直线的垂足;
PointEx perpendicular(PointEx p, LineSegment l) {
    double r = relation(p, l);
    PointEx tp;
    tp.x = l.startPoint.x + r*(l.endPoint.x - l.startPoint.x);
    tp.y = l.startPoint.y + r*(l.endPoint.y - l.startPoint.y);
    return tp;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 判断垂足是不是在这条线段上;
  2. 如果不在这条线段上,则判断垂足距离哪个端点比较近,选中选中的端点;
  3. 如果在这条线段上,则选中这条线段
void MyGraphCal::selSeg(QPointF&pt) {
    int num = lineSegs.size();


    for(int i = 0; i < num; i++) {
        LINESEG* oneLine = lineSegs.at(i);
        LineSegment* oneLineDeg = oneLine->seg;

        PointEx ptEx(pt.x(), pt.y());
        PointEx np;//线段上的点
        double dis = pToLinesegDist(ptEx, *oneLineDeg, np);
        if(dis < 5 && dis >= 0.0) {
            double l = relation(np, *oneLineDeg);
            if(abs(l)< EP) {//起点
                oneLine->bSelStartPt = true;
                oneLine->bSelLine = false;
                oneLine->bSelEndPt = false;
            } else if(abs(l - 1.0) < EP) {//终点
                oneLine->bSelEndPt = true;
                oneLine->bSelLine = false;
                oneLine->bSelStartPt = false;
            } else if(l < 1 && l > 0) {//整条线
                oneLine->bSelLine = true;
                oneLine->bSelEndPt = false;
                oneLine->bSelStartPt = false;
            }
        } else {
            oneLine->bSelLine = false;
            oneLine->bSelEndPt = false;
            oneLine->bSelStartPt = false;
        }
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 四、向量
    以上计算过程中用到了向量和向量的点积
    向量的几何意义:一条有方向的线段
    这就是上面定义的线段的来源,定义一点线段要定一它的起始点和终止点,从起始点到终止点的方向就是向量的方向。
    点积的结合意义:向量a、b,r = a*b=|a|*|b|cosα。也就是:向量a的模乘以向量b在向量a上的投影的长度。
    因为α是一个角度,所以可以通过结果r的正负获取两条线段之间的简单关系:
    r>0:两个向量之间的夹角在0-90度之间
    r=0:两个向量互相垂直
    r<0:两个向量之间的夹角在90-180度之间。

代码下载

以上!

发布了8 篇原创文章 · 获赞 3 · 访问量 1935

猜你喜欢

转载自blog.csdn.net/weixin_44017727/article/details/97309370