1. 程式人生 > >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() {
    }
};

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度之間。 以上!