直线的自定义子类见我之前的文章:Qt中直线的自定义子类(支持缩放、拖拽,父级场景无拖影)。
支持缩放,支持按住shift键拖动鼠标时保持宽高相同。
#ifndef MYGRAPHICSRECTITEM_H
#define MYGRAPHICSRECTITEM_H
#include <QGraphicsRectItem>
#include <QList>
#include <QPointF>
#include <QPainter>
#include <QGraphicsScene>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include "eresizefocus.h"
class MyGraphicsRectItem : public QGraphicsRectItem
{
public:
enum { Type = UserType + 101 };
enum MouseMode{MOVE, RESIZE};
MyGraphicsRectItem(qreal x1, qreal y1, qreal x2, 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;
QGraphicsRectItem *dashRect;
void checkNewWidthHeight(QGraphicsSceneMouseEvent *event, qreal &width, qreal &height); // 根据是否按了shift键,获取鼠标终点位置
void checkRectPointsAndPos(qreal &width, qreal &height,
qreal& x1, qreal& y1,
qreal& x2, qreal& y2,
qreal& posX, qreal& posY);
void createResizeFocus();
void updateResizeFocus();
};
#endif // MYGRAPHICSRECTITEM_H
#include "mygraphicsrectitem.h"
/**
* @brief MyGraphicsRectItem 构造函数,继承于QGraphicsRectItem,但要扩展缩放。
* @param x1 鼠标在画布上按下时确定的X点,距离画布左边的长度
* @param y1 鼠标在画布上按下时确定的X点,距离画布顶端的长度
* @param x2 鼠标在画布上松开时确定的X点,距离画布左边的长度
* @param y2 鼠标在画布上松开时确定的Y点,距离画布顶端的长度
* @param parent 父图形指针,可以为NULL。
* @param window 父窗口指针,可以为NULL,我这里传入了父窗口指针,为了调用父窗口的update,解决拖影问题。
*/
MyGraphicsRectItem::MyGraphicsRectItem(qreal x1, qreal y1, qreal x2, qreal y2,
QGraphicsItem *parent, QGraphicsView *window):
QGraphicsRectItem(parent),
parentWindow(window),
margin(EResizeFocus::getXyWidth())
{
qreal posX = x1, posY = y1;
// 因为画线方式各异,要将起始位置确定,并确定线段两端点坐标的相对位置
checkRectPointsAndPos(width, height, x1, y1, x2, y2, posX, posY);
qDebug("width x height: %f x %f,pos(%f x %f), x1,y1: %f,%f, x2,y2:%f,%f",
width, height, posX, posY, x1, y1, x2, y2);
setRect(x1, y1, width, height);
setPos(QPointF(posX, posY));
setAcceptDrops(true);
setAcceptHoverEvents(true);
createResizeFocus();
}
void MyGraphicsRectItem::checkRectPointsAndPos(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);
}
if (y1 > y2) {
// 同x,但直线不可这样处理,矩形、椭圆、弧形都必须这样处理,
// 因为画图一定是左上往右下画,不像直线,可以左下往右上画!
std::swap(y1, y2);
}
// 计算图形的位置坐标
posX = x1; // 左边的那个点(无论在另一个点上方还是下方)是起点的x
posY = y1; // 上面那个点的y值是起点的y
// 计算相两个点对于pos的坐标位置
x1 = 0;
x2 = width;
y1 = 0;
y2 = height;
}
QRectF MyGraphicsRectItem::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 MyGraphicsRectItem::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);
//qDebug("shape: %f,%f", width + margin * 2, height + margin * 2);
return path;
}
void MyGraphicsRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(widget)
painter->setPen(pen());
painter->setBrush(brush());
painter->drawRect(rect());
static int count = 0;
count++;
if(option->state & QStyle::State_Selected) {
showResizeFocus(true);
} else {
showResizeFocus(false);
}
if (parentWindow) {
parentWindow->update();
}
}
void MyGraphicsRectItem::createResizeFocus()
{
EResizeFocus *north_middle = new EResizeFocus(EResizeFocus::NORTH_MIDDLE, this);
resizeFocus.append(north_middle);
EResizeFocus *north_east = new EResizeFocus(EResizeFocus::NORTH_EAST, this);
resizeFocus.append(north_east);
EResizeFocus *north_west = new EResizeFocus(EResizeFocus::NORTH_WEST, this);
resizeFocus.append(north_west);
EResizeFocus *south_middle = new EResizeFocus(EResizeFocus::SOUTH_MIDDLE, this);
resizeFocus.append(south_middle);
EResizeFocus *south_east = new EResizeFocus(EResizeFocus::SOUTH_EAST, this);
resizeFocus.append(south_east);
EResizeFocus *south_west = new EResizeFocus(EResizeFocus::SOUTH_WEST, this);
resizeFocus.append(south_west);
EResizeFocus *east_middle = new EResizeFocus(EResizeFocus::EAST_MIDDLE, this);
resizeFocus.append(east_middle);
EResizeFocus *west_middle = new EResizeFocus(EResizeFocus::WEST_MIDDLE, this);
resizeFocus.append(west_middle);
}
void MyGraphicsRectItem::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 MyGraphicsRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QPointF posScene = event->scenePos();
qDebug("posScene: %f, %f", posScene.x(), posScene.y());
QTransform transform;
transform.rotate(+0.0);
curResizeFocus = qgraphicsitem_cast<EResizeFocus*>(scene()->itemAt(posScene, transform));
if(curResizeFocus) {
myMode = RESIZE;
lastPoint = posScene;
dashRect = new QGraphicsRectItem(rect(), this);
dashRect->setPen(QPen(Qt::DashLine));
QGraphicsRectItem::mousePressEvent(event);
} else {
myMode = MOVE;
QGraphicsRectItem::mousePressEvent(event);
}
}
void MyGraphicsRectItem::checkNewWidthHeight(QGraphicsSceneMouseEvent *event, qreal& width, qreal& height)
{
if (event->modifiers() & Qt::ShiftModifier) {
width = height = std::max(width, height);
}
}
void MyGraphicsRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(myMode == MOVE) {
return QGraphicsRectItem::mouseMoveEvent(event);
}
QPointF curPoint(event->scenePos());
qreal widthMoving = curPoint.x() - lastPoint.x(),
heightMoving = curPoint.y() - lastPoint.y();
QRectF rect = this->rect();
qreal x1 = rect.x();
qreal y1 = rect.y();
qreal newWidth = width;
qreal newHeight = height;
/**
* 下面的各个点的移动,可以考虑当widthMoving,
* heightMoving均大于0时,
* posX, posY是图形左上角点对应的位置(所以画图的时候相对位置时左上角是原点,参数是0,0)
* 到底posX, posY, width, height是增加了还是减少了。
* 比如右上角,鼠标往右下角移动的时候,posY增加了,但height减少了,posX不变,且width也增加了。
* 增加了就是加号,否则是减号。
*/
EResizeFocus::PosInHost pos = curResizeFocus->getInHost();
// 经过构造函数的处理,我们能确认:x1 <= x2, y1 <= y2(矩形嘛,更简单)
// 所以下面就不必判断了,东影响x2,南影响y2,西影响x1,北影响y1
switch(pos){
case EResizeFocus::EAST_MIDDLE:
// 正右边
newWidth += widthMoving;
break;
case EResizeFocus::SOUTH_MIDDLE:
// 正下方
newHeight += heightMoving;
break;
case EResizeFocus::WEST_MIDDLE:
// 正左边
x1 += widthMoving;
newWidth -= widthMoving;
break;
case EResizeFocus::NORTH_MIDDLE:
// 正上方
y1 += heightMoving;
newHeight -= heightMoving;
break;
case EResizeFocus::NORTH_EAST:
// 右上方
y1 += heightMoving;
newWidth += widthMoving;
newHeight -= heightMoving;
break;
case EResizeFocus::NORTH_WEST:
// 左上方
x1 += widthMoving;
y1 += heightMoving;
newWidth -= widthMoving;
newHeight -= heightMoving;
break;
case EResizeFocus::SOUTH_EAST:
// 右下方
newWidth += widthMoving;
newHeight += heightMoving;
break;
case EResizeFocus::SOUTH_WEST:
// 左下方
x1 += widthMoving;
newWidth -= widthMoving;
newHeight += heightMoving;
break;
}
if (newWidth < 0 || newHeight < 0) return;
if(newWidth < 20 && newHeight <20) return; // !minimal size
checkNewWidthHeight(event, newWidth, newHeight);
dashRect->setRect(x1, y1, newWidth, newHeight);
}
void MyGraphicsRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(myMode == MOVE) {
return QGraphicsRectItem::mouseReleaseEvent(event);
} else {
QRectF rectDash = dashRect->rect();
qreal x1 = rectDash.x(),
y1 = rectDash.y(),
x2 = rectDash.bottomRight().x(),
y2 = rectDash.bottomRight().y();
qreal posX, posY;
// 这里posX, posY计算出来的是一个相对图形的pos的坐标
checkRectPointsAndPos(width, height, x1, y1, x2, y2, posX, posY);
posX += pos().x();
posY += pos().y();
setPos(QPointF(posX, posY));
setRect(x1, y1, width, height);
delete dashRect;
}
}
#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);
}