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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wb175208/article/details/82722129
  • 一、环境
    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() {
    }
};

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;
        }
    };

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

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

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;
    }
}

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

ui.centralWidget->setMouseTracking(true);
setMouseTracking(true);

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. 如果在这条线段上,则选中这条线段
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;
        }
    }
}
  • 四、向量
    以上计算过程中用到了向量和向量的点积
    向量的几何意义:一条有方向的线段
    这就是上面定义的线段的来源,定义一点线段要定一它的起始点和终止点,从起始点到终止点的方向就是向量的方向。
    点积的结合意义:向量a、b,r = a*b=|a|*|b|cosα。也就是:向量a的模乘以向量b在向量a上的投影的长度。
    因为α是一个角度,所以可以通过结果r的正负获取两条线段之间的简单关系:
    r>0:两个向量之间的夹角在0-90度之间
    r=0:两个向量互相垂直
    r<0:两个向量之间的夹角在90-180度之间。

代码下载

以上!

展开阅读全文

没有更多推荐了,返回首页