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度之间。

代码下载

以上!

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,这是一个比较复杂的问题,需要详细地解释一下。 首先,我们需要在 QML 中创建一个 Canvas 元素,用于绘制线段。然后,我们需要创建一个触发式按钮,当用户按下按钮时,我们会开始捕捉鼠标移动事件,并在 Canvas 上绘制线段。 下面是实现的大致步骤: 1. 创建 Canvas 元素 在 QML 中创建一个 Canvas 元素,用于绘制线段。可以使用以下代码: ``` Canvas { id: canvas width: parent.width height: parent.height onPaint: { var ctx = getContext("2d") // 绘制线段的代码 } } ``` 2. 创建触发式按钮 在 QML 中创建一个按钮,用于触发绘制线段的功能。可以使用以下代码: ``` Button { text: "绘制线段" onPressed: { // 开始绘制线段的代码 } } ``` 3. 捕捉鼠标移动事件 当用户按下按钮时,我们需要开始捕捉鼠标移动事件。可以在 Canvas 中使用以下代码: ``` onMouseAreaPressed: { canvas.mouseX = mouse.x canvas.mouseY = mouse.y canvas.pressed = true } onMouseAreaMoved: { if (canvas.pressed) { var ctx = canvas.getContext("2d") ctx.beginPath() ctx.moveTo(canvas.mouseX, canvas.mouseY) ctx.lineTo(mouse.x, mouse.y) ctx.stroke() canvas.mouseX = mouse.x canvas.mouseY = mouse.y } } onMouseAreaReleased: { canvas.pressed = false } ``` 4. 绘制线段 在捕捉到鼠标移动事件后,我们需要在 Canvas 上绘制线段。可以使用以下代码: ``` var ctx = canvas.getContext("2d") ctx.beginPath() ctx.moveTo(canvas.mouseX, canvas.mouseY) ctx.lineTo(mouse.x, mouse.y) ctx.stroke() canvas.mouseX = mouse.x canvas.mouseY = mouse.y ``` 完整的代码如下: ``` import QtQuick 2.0 Rectangle { width: 640 height: 480 Canvas { id: canvas width: parent.width height: parent.height onPaint: { var ctx = getContext("2d") // 绘制线段的代码 } property int mouseX: 0 property int mouseY: 0 property bool pressed: false onMouseAreaPressed: { canvas.mouseX = mouse.x canvas.mouseY = mouse.y canvas.pressed = true } onMouseAreaMoved: { if (canvas.pressed) { var ctx = canvas.getContext("2d") ctx.beginPath() ctx.moveTo(canvas.mouseX, canvas.mouseY) ctx.lineTo(mouse.x, mouse.y) ctx.stroke() canvas.mouseX = mouse.x canvas.mouseY = mouse.y } } onMouseAreaReleased: { canvas.pressed = false } } Button { text: "绘制线段" onPressed: { // 开始绘制线段的代码 } } } ``` 注意:以上代码仅为示例,可能需要根据实际情况进行修改。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值