在QT编程中,我发现画线的处理最复杂了。如果只是用QPaint的drawLine来做,自然简单。可是,实际项目中会希望线段可以拖拽、缩放。而拖拽功能,有QGraphicsLineItem已经为我们实现了,所以,我们会用这个类来创建线段。可惜,这个类没有为我们实现缩放功能,网上找到文章:《QGraphicsItem选中后,出现边框,可以拉伸》,首先感谢这篇文章的作者“LiuZnHi”,帮我解决了拖拽的算法。
在按照上述文章的代码,整理出了我自己的代码,并改进了其中的问题。
我现在将代码贴出来,供大家参考。
因为直线的缩放其实没有必要,只要修改两端点的位置即可。另外,还支持了按住shift键缩放时会将形状按相等宽高处理。
备注:直线要支持拖拽改变大小,那么shape必须修改为一个矩形,否则线段的轮廓要考虑额外添加的小矩形,会很困难。
依赖的eresizefocus在最后面。
#ifndef MYGRAPHICSLINEITEM_H
#define MYGRAPHICSLINEITEM_H
#include <QGraphicsLineItem>
#include <QGraphicsRectItem>
#include <QFocusEvent>
#include <QList>
#include <QPointF>
#include <QPainter>
#include <QGraphicsScene>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include "eresizefocus.h"
class MyGraphicsLineItem : public QGraphicsLineItem
{
public:
enum { Type = UserType + 101 };
enum MouseMode{MOVE, RESIZE};
MyGraphicsLineItem(qreal x1, qreal x2, qreal y1, qreal y2,
QGraphicsItem *parent = nullptr,
QGraphicsView* window = nullptr);
virtual int type() const {return Type;}
QRectF boundingRect() const;
QPainterPath shape() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void showResizeFocus(bool visible);
private:
QGraphicsView* parentWindow;
MouseMode myMode;
QColor myColor;
QPointF lastPoint;
qreal width;
qreal height;
qreal margin;
QList<EResizeFocus*> resizeFocus;
EResizeFocus *curResizeFocus;
QGraphicsLineItem *dashLine;
QPointF getToPoints(QGraphicsSceneMouseEvent *event, QPointF fromPoint); // 根据是否按了shift键,获取鼠标终点位置
void checkLinePointsAndPos(qreal &width, qreal &height,
qreal& x1, qreal& y1,
qreal& x2, qreal& y2,
qreal& posX, qreal& posY);
void createResizeFocus();
void updateResizeFocus();
};
#endif // MYGRAPHICSLINEITEM_H
#include "mygraphicslineitem.h"
/**
* @brief MyGraphicsLineItem 构造函数,继承于QGraphicsLineItem,但要扩展缩放。
* @param x1 鼠标在画布上按下时确定的X点,距离画布左边的长度
* @param y1 鼠标在画布上按下时确定的X点,距离画布顶端的长度
* @param x2 鼠标在画布上松开时确定的X点,距离画布左边的长度
* @param y2 鼠标在画布上松开时确定的Y点,距离画布顶端的长度
* @param parent 父图形指针,可以为NULL。
* @param window 父窗口指针,可以为NULL,我这里传入了父窗口指针,为了调用父窗口的update,解决拖影问题。
*/
MyGraphicsLineItem::MyGraphicsLineItem(qreal x1, qreal y1, qreal x2, qreal y2,
QGraphicsItem *parent, QGraphicsView *window):
QGraphicsLineItem(parent),
parentWindow(window),
margin(EResizeFocus::getXyWidth())
{
qreal posX = x1, posY = y1;
// 因为画线方式各异,要将起始位置确定,并确定线段两端点坐标的相对位置
checkLinePointsAndPos(width, height, x1, y1, x2, y2, posX, posY);
setLine(x1, y1, x2, y2);
setPos(QPointF(posX, posY));
setAcceptDrops(true);
setAcceptHoverEvents(true);
createResizeFocus();
}
void MyGraphicsLineItem::checkLinePointsAndPos(qreal& width, qreal& height,
qreal& x1, qreal& y1,
qreal& x2, qreal& y2,
qreal& posX, qreal& posY)
{
width = qAbs(x2 - x1);
height = qAbs(y2 - y1);
if (x1 > x2) {
// 如果起点(鼠标按下)比终点(鼠标松开)更靠右,交换起点和终点
// 交换后,x1,y1一定是左边那个点
std::swap(x1, x2);
std::swap(y1, y2);
}
// 计算图形的位置坐标
posX = x1; // 左边的那个点(无论在另一个点上方还是下方)是起点的x
posY = y1 <= y2 ? y1 : y2; // 上面那个点的y值是起点的y
// 计算相两个点对于pos的坐标位置
x1 = 0;
x2 = width;
y1 = y1 <= y2 ? 0 : height;
y2 = y1 <=1e-6 ? height : 0; // y2和y1总有一个是height(注意浮点数判断为0的方法)
}
QRectF MyGraphicsLineItem::boundingRect() const
{
qreal penWidth = pen().width();
return QRectF(0 - penWidth / 2 - margin / 2, 0 - penWidth / 2 - margin / 2,
width + margin + penWidth, height + margin + penWidth);
}
QPainterPath MyGraphicsLineItem::shape() const
{
QPainterPath path;
qreal penWidth = pen().width();
path.addRect(0 - penWidth / 2 - margin / 2, 0 - penWidth / 2 - margin / 2,
width + margin + penWidth, height + margin + penWidth);
return path;
}
void MyGraphicsLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(widget)
painter->setPen(pen());
painter->drawLine(line());
static int count = 0;
count++;
if(option->state & QStyle::State_Selected) {
showResizeFocus(true);
} else {
showResizeFocus(false);
}
if (parentWindow) {
parentWindow->update();
}
}
void MyGraphicsLineItem::createResizeFocus()
{
QLineF line = this->line();
qreal y1 = line.y1(),
y2 = line.y2();
if (y1 >= y2) { // x1 <= x2是肯定成立的,在构造函数里已经调整好了
EResizeFocus *north_east = new EResizeFocus(EResizeFocus::NORTH_EAST, this);
resizeFocus.append(north_east);
EResizeFocus *south_west = new EResizeFocus(EResizeFocus::SOUTH_WEST, this);
resizeFocus.append(south_west);
} else {
EResizeFocus *north_west = new EResizeFocus(EResizeFocus::NORTH_WEST, this);
resizeFocus.append(north_west);
EResizeFocus *south_east = new EResizeFocus(EResizeFocus::SOUTH_EAST, this);
resizeFocus.append(south_east);
}
}
void MyGraphicsLineItem::showResizeFocus(bool visible)
{
qreal penWidth = pen().width();
for(int i = 0; i < resizeFocus.count(); i++) {
resizeFocus.at(i)->locateInHost(penWidth);
resizeFocus.at(i)->setVisible(visible);
}
}
void MyGraphicsLineItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QPointF posScene = event->scenePos();
QTransform transform;
transform.rotate(+0.0);
curResizeFocus = qgraphicsitem_cast<EResizeFocus*>(scene()->itemAt(posScene, transform));
if(curResizeFocus) {
myMode = RESIZE;
lastPoint = posScene;
dashLine = new QGraphicsLineItem(line(), this);
dashLine->setPen(QPen(Qt::DashLine));
QGraphicsLineItem::mousePressEvent(event);
} else {
myMode = MOVE;
QGraphicsLineItem::mousePressEvent(event);
}
}
QPointF MyGraphicsLineItem::getToPoints(QGraphicsSceneMouseEvent *event, QPointF fromPoint)
{
QPointF newPoint = event->scenePos() - this->pos();
if (event->modifiers() & Qt::ShiftModifier) {
qreal negativeFlagX = newPoint.x() - fromPoint.x() > 0 ? 1 : -1;
qreal negativeFlagY = newPoint.y() - fromPoint.y() > 0 ? 1 : -1;
qreal width = qAbs(newPoint.x() - fromPoint.x());
qreal height = qAbs(newPoint.y() - fromPoint.y());
qreal rate = qAbs(2 * (width - height) / (width + height));
if (rate < 0.2) { // 45°
qreal maxLength = std::max(width, height);
newPoint = fromPoint + QPointF(negativeFlagX*maxLength, negativeFlagY*maxLength);
} else {
if (width > height) {
newPoint = fromPoint + QPointF(negativeFlagX*width, 0);
} else {
newPoint = fromPoint + QPointF(0, negativeFlagY*height);
}
}
}
return newPoint;
}
void MyGraphicsLineItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(myMode == MOVE) {
return QGraphicsLineItem::mouseMoveEvent(event);
}
EResizeFocus::PosInHost pos = curResizeFocus->getInHost();
QPointF newPoint;
if (pos == EResizeFocus::NORTH_EAST || pos == EResizeFocus::SOUTH_EAST) {
newPoint = getToPoints(event, dashLine->line().p1());
dashLine->setLine(dashLine->line().p1().x(), dashLine->line().p1().y(),
newPoint.x(), newPoint.y());
} else {
newPoint = getToPoints(event, this->line().p2());
dashLine->setLine(newPoint.x(), newPoint.y(),
dashLine->line().p2().x(), dashLine->line().p2().y());
}
}
void MyGraphicsLineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(myMode == MOVE) {
return QGraphicsLineItem::mouseReleaseEvent(event);
} else {
QLineF lineDash = dashLine->line();
qreal x1 = lineDash.x1(), y1 = lineDash.y1(),
x2 = lineDash.x2(), y2 = lineDash.y2();
qreal posX, posY;
// 这里posX, posY计算出来的是一个相对图形的pos的坐标
checkLinePointsAndPos(width, height, x1, y1, x2, y2, posX, posY);
posX += pos().x();
posY += pos().y();
setPos(QPointF(posX, posY));
setLine(x1, y1, x2, y2);
delete dashLine;
}
}
#ifndef ERESIZEFOCUS_H
#define ERESIZEFOCUS_H
#include <QGraphicsRectItem>
#include <QCursor>
class EResizeFocus: public QGraphicsRectItem
{
public:
enum PosInHost{NORTH_MIDDLE,NORTH_EAST,EAST_MIDDLE,SOUTH_EAST,SOUTH_MIDDLE,SOUTH_WEST,WEST_MIDDLE,NORTH_WEST};
enum { Type = UserType + 1 };
EResizeFocus(PosInHost pos, QGraphicsItem *parent);
~EResizeFocus();
int type() const {return Type;}
void setInHost(PosInHost pos){posInHost = pos;}
PosInHost getInHost(){return posInHost;}
void locateInHost(qreal penWidth = 1);
static qreal getXyWidth(){return xyWidth;}
protected:
void hoverEnterEvent ( QGraphicsSceneHoverEvent * event ) ;
void hoverLeaveEvent ( QGraphicsSceneHoverEvent * event ) ;
private:
PosInHost posInHost;
static qreal xyWidth;
};
#endif // ERESIZEFOCUS_H
#include "eresizefocus.h"
qreal EResizeFocus::xyWidth = 6;
EResizeFocus::EResizeFocus(PosInHost pos, QGraphicsItem *parent) :
QGraphicsRectItem(0, 0, xyWidth, xyWidth, parent),
posInHost(pos)
{
setAcceptHoverEvents(true);
setVisible(false);
}
EResizeFocus::~EResizeFocus()
{
}
void EResizeFocus::hoverEnterEvent ( QGraphicsSceneHoverEvent * event )
{
switch(posInHost){
case NORTH_MIDDLE:
setCursor(Qt::SizeVerCursor);
break;
case SOUTH_MIDDLE:
setCursor(Qt::SizeVerCursor);
break;
case EAST_MIDDLE:
setCursor(Qt::SizeHorCursor);
break;
case WEST_MIDDLE:
setCursor(Qt::SizeHorCursor);
break;
case NORTH_WEST:
setCursor(Qt::SizeFDiagCursor);
break;
case SOUTH_EAST:
setCursor(Qt::SizeFDiagCursor);
break;
case NORTH_EAST:
setCursor(Qt::SizeBDiagCursor);
break;
case SOUTH_WEST:
setCursor(Qt::SizeBDiagCursor);
break;
}
QGraphicsRectItem::hoverEnterEvent(event);
}
void EResizeFocus::hoverLeaveEvent ( QGraphicsSceneHoverEvent * event )
{
QGraphicsRectItem::hoverLeaveEvent(event);
}
void EResizeFocus::locateInHost(qreal penWidth)
{
const QRectF parentRect = this->parentItem()->boundingRect();
const qreal parentWidth = parentRect.width() - xyWidth;
const qreal parentHeight = parentRect.height() - xyWidth ;
qreal x = 0, y = 0;
switch(posInHost){
case NORTH_MIDDLE:
x = parentWidth / 2 - xyWidth / 2;
y = -xyWidth / 2;
break;
case SOUTH_MIDDLE:
x = parentWidth / 2 - xyWidth / 2;
y = parentHeight - xyWidth / 2;
break;
case EAST_MIDDLE:
x = parentWidth - xyWidth / 2;
y = parentHeight / 2 - xyWidth / 2;
break;
case WEST_MIDDLE:
x = -xyWidth / 2;
y = parentHeight / 2 - xyWidth / 2;
break;
case NORTH_WEST:
x = -xyWidth / 2;
y = -xyWidth / 2;
break;
case SOUTH_EAST:
x = parentWidth - xyWidth / 2;
y = parentHeight - xyWidth / 2;
break;
case NORTH_EAST:
x = parentWidth - xyWidth / 2;
y = -xyWidth / 2;
break;
case SOUTH_WEST:
x = -xyWidth / 2;
y = parentHeight - xyWidth / 2;
break;
}
setPos(x - penWidth / 2, y - penWidth / 2);
}