自定义QGraphicsItem 绘制线段并进行交互

效果如下图所示,可在场景中手动添加线段,可对整体拖拽,可对两个端点拖拽

对QGraphicsItem 自定义绘图本身很简单,由于项目需求,我需要对某一个端点拖拽,另一个端点在场景的坐标保持不变,另外要保持线段整体在场景的坐标居中(防止边缘矩形错乱),这就比较麻烦

另外是线段的形状函数确定,我没有采用对矩形的旋转,而是两个端点为中心生成一个小矩形,确定好连线的方向,根据两个小矩形相应端点连接成一个多边形即为刚好包含线段的形状,为形状函数。

线段的边界矩形就是包含两个端点的最小矩形然后稍微外扩一点(为了端点的显示效果的不超出边界)

要实现上述需求需要对线段的坐标和场景坐标需要很透彻,首先是item的坐标,始终保持两个端点为中心对称,即(point1 = - point2),边界矩形也是如此,中心点(QPOINTF(0,0))即为场景坐标,每次对端点拖拽都需要更新场景坐标,不然边界矩形就会错乱。

花了一下午搞定的,不知道自己实力如何,之后还会自定义绘制矩形,绘制自定义网格,绘制文本框。

源码如下图所示,使用方式非常简单,在场景中直接添加即可,只需要传入点击的场景坐标。

line.h

#ifndef LINE_H
#define LINE_H

#include <QGraphicsItem>
#include <QPainter>
#include <QObject>
#include <QMouseEvent>

using namespace std;

class line  : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    line(QPointF start_point,bool found);
    ~line();

    //画笔
    QPen mypen;

    QPointF m_p1,m_p2 ;
    QPointF m_pos;              // 场景坐标点
    QPointF m_pressedPos;       // 场景坐标点击的点

    //两点
    QPointF point1;
    QPointF point2;

    int press_state = -1;
    bool flag_found = false;
    int size = 5;
    QPainterPath shape()const override;
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
    double distance(QPointF point1,QPointF point2) const;
    int judge_touch(QPointF point);
    bool judge_diagonal(QPointF point1,QPointF point2) const;
    QPolygonF get_shape() const;
    QRectF get_rect() const;
    void set_pen();

protected:
    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;

};

#endif // LINE_H

line.cpp

#include "line.h"

line::line(QPointF start_point, bool found)
{
    this->setFlag(QGraphicsItem::ItemIsMovable, true);
    this->setFlag(QGraphicsItem::ItemIsSelectable, true);
    this->setAcceptHoverEvents(true);
    this->setAcceptDrops(true);

    setPos(start_point);
    set_pen();
    flag_found = found; //正在创建

}

line::~line()
{

}

//设置画笔
void line::set_pen()
{
    mypen.setWidth(2); //线宽
    mypen.setColor(QColor(0,0,0)); //划线颜色
    mypen.setStyle(Qt::SolidLine);//线的类型,实线、虚线等
    mypen.setCapStyle(Qt::FlatCap);//线端点样式
    mypen.setJoinStyle(Qt::BevelJoin);//线的连接点样式
}

//鼠标双击
void line::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    //左键双击
    if (event->button() == Qt::LeftButton)
    {

    }
}

//判断触碰
int line::judge_touch(QPointF point)
{
    int count = -1;
    QRectF rect = get_rect();
    QPointF key_point1,key_point2;
    //获取两个关键点
    if(!judge_diagonal(point1,point2)) //主对角线
    {
        key_point1 = rect.bottomLeft() + QPointF(size,-size);
        key_point2 = rect.topRight() + QPointF(-size,size);
    }
    else
    {
        key_point1 = rect.topLeft() + QPointF(size,size);
        key_point2 = rect.bottomRight() + QPointF(-size,-size);
    }

    //根据关键点确认点击的是point1还是point2
    if(distance(point,key_point1) < size)
    {
        if(distance(point1,key_point1) < 3)
            count = 1;
        else if(distance(point2,key_point1) < 3)
            count = 2;
    }
    else if(distance(point,key_point2) < size)
    {
        if(distance(point1,key_point2) < 3)
            count = 1;
        else if(distance(point2,key_point2) < 3)
            count = 2;
    }
    else if(get_shape().containsPoint(point,Qt::WindingFill))  count = 0;

    qDebug() << "判断触碰" << count;
    return count;
}

//鼠标按下
void line::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    qDebug() << "item鼠标按下";
    if (event->button() == Qt::LeftButton)
    {
        m_pressedPos = event->scenePos();
        m_pos = this->pos();
        m_p1 = point1 + m_pos;
        m_p2 = point2 + m_pos;
        this->setSelected(true);
        //if(!press_ctrl()) emit sendSelected(quid); //如果按下crtl,不取消别的item的选中
        //判断按下的点
        press_state = judge_touch(event->pos());
    }
    else if (event->button() == Qt::RightButton)  //右键菜单
    {
    }
    this->update();
}

//鼠标移动
void line::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    qDebug() << "鼠标移动";
    // 获取场景坐标和本地坐标
    QPointF scenePos = event->scenePos();
    //获取偏移量
    qreal xInterval = scenePos.x() - m_pressedPos.x();
    qreal yInterval = scenePos.y() - m_pressedPos.y();

    if(flag_found) //如果正在创建
    {
        QPointF no_move = m_p2;
        this->setPos((scenePos + no_move)/2);
        point1 = QPointF((scenePos.rx() - no_move.rx())/2,(scenePos.ry() - no_move.ry())/2);
        point2 = - point1;
    }
    else if(press_state == 0)
    {
        m_pressedPos = scenePos;
        m_pos =  this->pos();
        this->setPos(this->pos() + QPointF(xInterval,yInterval));
    }
    else
    {
        if(press_state == 1)
        {
            this->setPos((scenePos + m_p2)/2);
            point1 = QPointF((m_p2.rx() - scenePos.rx())/2,(m_p2.ry() - scenePos.ry())/2);
            point2 = - point1;
        }
        else if(press_state == 2)
        {
            this->setPos((scenePos + m_p1)/2);
            point2 = QPointF((m_p1.rx() - scenePos.rx())/2,(m_p1.ry() - scenePos.ry())/2);
            point1 = - point2;
        }
    }
    this->update();
}

//鼠标释放
void line::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    qDebug() << "item鼠标释放";
    flag_found = false;
    press_state = -1;
    this->update();
}

//判断线段的两点是否在主对角线
bool line::judge_diagonal(QPointF point1, QPointF point2) const
{
    //判断point1与point2的连线是否在主对角线
    bool flag1,flag2;
    if(point1.x() <= point2.x()) flag1 = true;
    else flag1 = false;

    if(point1.y() <= point2.y()) flag2 = true;
    else flag2 = false;

    bool out;
    if(flag1 == flag2) out = true;
    else out = false;

    return out;
}

//得到边界矩形
QRectF line::get_rect() const
{
    double wide = abs(point1.x() - point2.x());
    double height = abs(point1.y() - point2.y());
    return QRectF(-wide/2 - size,-height/2 - size,wide + size*2,height + size*2);
}

//得到形状
QPolygonF line::get_shape() const
{
    QRectF rect = get_rect();
    QPolygonF poly;
    int cur_size = size*2;
    if(!judge_diagonal(point1,point2)) //主对角线
    {
        poly.append(rect.bottomLeft());
        poly.append(rect.bottomLeft() - QPointF(0,cur_size));
        poly.append(rect.topRight() - QPointF(cur_size,0));
        poly.append(rect.topRight());
        poly.append(rect.topRight() + QPointF(0,cur_size));
        poly.append(rect.bottomLeft() + QPointF(cur_size,0));
    }
    else
    {
        poly.append(rect.topLeft());
        poly.append(rect.topLeft() + QPointF(cur_size,0));
        poly.append(rect.bottomRight() - QPointF(0,cur_size));
        poly.append(rect.bottomRight());
        poly.append(rect.bottomRight() - QPointF(cur_size,0));
        poly.append(rect.topLeft() + QPointF(0,cur_size));
    }
    return poly;
}


//形状函数
QPainterPath line::shape() const
{
    QPainterPath path;
    path.addPolygon(get_shape());
    return path;
}

//边界矩形
QRectF line::boundingRect() const
{
    return get_rect();
}

//判断两点距离
double line::distance(QPointF point1,QPointF point2) const
{
    return sqrt(qPow(point1.rx() - point2.rx(),2) + qPow(point1.ry() - point2.ry(),2));
}


//重绘
void line::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
    painter->setRenderHint(QPainter::TextAntialiasing, true);


    painter->setPen(mypen);
    painter->setBrush(QBrush(QColor(255,255,255)));

    //测试绘制形状
    //painter->drawPolygon(get_shape());

    //绘制线段
    painter->drawLine(point1,point2);

    if(press_state == 0) //绘制蒙版
    {
        QPen pen = mypen;
        pen.setColor(QColor(200,200,200));
        painter->setPen(pen);
        painter->drawLine(point1,point2);
        painter->setPen(mypen);
    }
    if(this->isSelected())//选中效果
    {
        QPoint point_wide(size,size);
        painter->drawEllipse(QRectF(point1 - point_wide,point1 + point_wide));
        painter->drawEllipse(QRectF(point2 - point_wide,point2 + point_wide));
    }
}

在QGraphicsScene中使用方式,这里的found标志位是我需要的,不需要的话直接删除或者设为真

line *item = new line(cur_sence_pos,found);
    this->addItem(item);

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自定义QGraphicsItem,可以继承QGraphicsItem类,并在派生类实现paint()和boundingRect()函数。 paint()函数用于绘制图形元素,您可以使用QPainter类绘制自己的形状、文本、图像等。boundingRect()函数返回该图形元素的边界矩形,它用于定义该图形元素的碰撞检测区域。 以下是一个简单的例子: ```python from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * class CustomItem(QGraphicsItem): def __init__(self, parent=None): super().__init__(parent) def boundingRect(self): return QRectF(-50, -50, 100, 100) def paint(self, painter, option, widget): painter.setBrush(QColor(255, 0, 0)) painter.drawEllipse(-50, -50, 100, 100) ``` 在这个例子,我们继承了QGraphicsItem类,并实现了boundingRect()和paint()函数。boundingRect()函数返回一个100x100的矩形,paint()函数绘制了一个红色的圆形。 然后,您可以将这个自定义图形元素添加到 QGraphicsScene ,并在 QGraphicsView 显示它: ```python from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * class CustomItem(QGraphicsItem): def __init__(self, parent=None): super().__init__(parent) def boundingRect(self): return QRectF(-50, -50, 100, 100) def paint(self, painter, option, widget): painter.setBrush(QColor(255, 0, 0)) painter.drawEllipse(-50, -50, 100, 100) if __name__ == '__main__': app = QApplication([]) scene = QGraphicsScene() item = CustomItem() scene.addItem(item) view = QGraphicsView(scene) view.show() app.exec_() ``` 这个例子创建了一个 QGraphicsScene,然后创建了一个CustomItem对象,并将其添加到场景。最后,创建了一个 QGraphicsView 对象,并将场景设置为其父级。运行这个程序,您将看到一个红色圆形在视图心。 这只是自定义QGraphicsItem的基础,您可以使用更复杂的绘图和交互技术来实现更高级的图形元素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值