Qt 繪製可以用滑鼠拖動的線段
阿新 • • 發佈:2018-12-10
- 一、環境 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.如何判斷當前滑鼠靠近某一條線段呢?
- 先確定出當前點到這條線段的所在直線的垂足;
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;
}
- 判斷垂足是不是在這條線段上;
- 如果不在這條線段上,則判斷垂足距離哪個端點比較近,選中選中的端點;
- 如果在這條線段上,則選中這條線段
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度之間。 以上!