利用QT 的 Graphics View 系统实现一个 简易的 Graph Editor

这篇具有很好参考价值的文章主要介绍了利用QT 的 Graphics View 系统实现一个 简易的 Graph Editor。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

QT 中的 Graphics View 系统. 是一个相对成熟的渲染引擎的上层框架,通常也可以会叫做 Scene - View。

通常会有 QGraphicsView, QGraphicsScene, QGraphicsItem 这几个类构成。

view是视口(viewport);scene是一个场景,负责容纳各种item;而item就是可见的这些元件。

一般来说,绘图可以使用 QPainter直接在重绘事件中进行绘制,但是,当我们想要选择绘制的图形的时候,就犯难了。我们的painter是直接在屏幕上写写画画,没有人来管理,在当前的mouse事件中也不知道如何处理这些项。

这个时候,Graphics View 就解决了这个问题,通过scene来管理各种图元item项。item在scene上绘制,scene在view上显示。

本文,就是利用Graphics View 系统来实现了一个简单的 有向图/无向图 编辑器。

编辑的图输出效果如下:

一个简单的绘图工具,具有世界地图、标尺和标题,使用qgraphicsview和qt中的qgraphi,札记,qt,ui,开发语言


绘制点和绘制线是一个图元,那么就是一个 QGraphicsItem,继承自 QGraphicsItem,然后去重写绘制方法

在绘制点和线的时候,需要重写QGraphicsItem的绘制函数,也就是 paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)

如何绘制点

graphNode类的设计:

class graphNode : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    graphNode(QPointF point, int r = 10, QString str = "0");

    // QGraphicsItem interface
public:
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

    QPointF getPoint() const;
    int getR() const;
    void setR(int newR);
    const QString &getText() const;
    void setText(const QString &newText);
    const QColor &getFrontColor() const;
    void setFrontColor(const QColor &newFrontColor);
    const QColor &getBackColor() const;
    void setBackColor(const QColor &newBackColor);

    int getRoundWidth() const;
    void setRoundWidth(int newRoundWidth);

private:
    QPointF point;          // 绘制的初始点
    int r;                  // 半径
    QString text;           // 点的文字
    QColor frontColor;      // 前景色Ⅰ
    QColor backColor;       // 背景色Ⅰ
    int roundWidth;         // 圆的宽Ⅰ
};

在这个类中,我自定义了一些属性,方便配置点的颜色,大小等等。

核心还是在于paint函数,其余都是辅助功能

下面是paint函数的实现:

void graphNode::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPen pen;
    pen.setWidth(roundWidth);

    painter->setRenderHint(QPainter::HighQualityAntialiasing);
    if (option->state & QStyle::State_Selected)
        pen.setColor(QColor((frontColor.red() + 125) % 255,
                            (frontColor.green() ) % 255,
                            (frontColor.blue() + 125) % 255)); // 选中时颜色变化
    else pen.setColor(frontColor);

    painter->setPen(pen);
    painter->drawEllipse(QRectF(point.x() - r, point.y() - r, r * 2, r * 2));


    QPainterPath path;
    path.addEllipse(QRectF(point.x() - r, point.y() - r, r * 2, r * 2));
    painter->fillPath(path, QBrush(backColor));


    painter->drawText(boundingRect(),
                      Qt::AlignHCenter |
                      Qt::AlignVCenter, text);

}

paint一共做了两件事情,第一件事情绘制一个圆,第二件事情就是绘制一个标识文字。

一个简单的绘图工具,具有世界地图、标尺和标题,使用qgraphicsview和qt中的qgraphi,札记,qt,ui,开发语言 其中的A就是标识文字

如何绘制线

graphLine类设计如下

class graphLine : public QObject, public QGraphicsLineItem
{
    Q_OBJECT

public:
    enum LineType {
        LeftToRight, // ==>
        RightToLeft, // <==
        TwoWayArrow, // <=>
        NoArrow,     // <=>
    };
    explicit graphLine(graphNode *begin,
                       graphNode *end,
                       LineType type = NoArrow,
                       QObject *parent = nullptr);
private:
    graphNode *begin;
    graphNode *end;
    int length;
    QColor color;
    LineType lineType;
private:
    void paintArrow(graphNode* begin, graphNode* end, QPainter* painter);

public:
    QPainterPath shape() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    const QColor &getColor() const;
    void setColor(const QColor &newColor);
    LineType getLineType() const;
    void setLineType(LineType newType);
    graphNode *getBegin() const;
    graphNode *getEnd() const;
};

其中paintArrow用来绘制箭头

绘制线的线有两种,一种是不带箭头的,一种是带方向箭头的。

不带箭头

不带箭头的比较好绘制,计算一下起点和终点的坐标,画一条线就是。

    auto r_begin = begin->getPoint() + begin->pos();
    auto r_end = end->getPoint() + end->pos();
    QLineF lines(r_begin, r_end);
    setLine(lines);

    QPen pen;
    pen.setWidth(2);

    painter->setPen(pen);
    painter->setRenderHint(QPainter::HighQualityAntialiasing);
    painter->drawLine(line());

带箭头

绘制箭头可能需要一些计算,不过由于我们这里这个图形选择的圆,其实还是比较容易计算的。

如果是多边形,要麻烦一点。

在这里,我们想要的效果是箭头始终紧贴着其指向的圆。

比如这种效果:

一个简单的绘图工具,具有世界地图、标尺和标题,使用qgraphicsview和qt中的qgraphi,札记,qt,ui,开发语言

我们知道 起点 a 和 终点 b的坐标,知道圆的半径,其实就很容易的推导出 圆和这条直线的交点是多少了。

大概是这样:

点 a 坐标为 ( x 1 , y 1 ) , 点 b 坐标为 ( x 2 , y 2 ) 现在, a b 的距离 = ( x 2 − x 1 ) 2 + ( y 2 − y 1 ) 2 直线 a b 的斜率为 k = ( y 2 − y 1 ) / ( x 2 − x 1 ) 现在点 c ( x , y ) 在 a b 上,若与 a 的距离为 c 的话。则有: { ( x − x 1 ) 2 + ( y − y 1 ) 2 = c 2 ( y − y 1 ) / ( x − x 1 ) = k 点 a 已知,距离 c 已知,斜率 k 已知 联立方程可以解得: { x = ± c 1 + k 2 + x 1 y = ± c k 1 + k 2 + y 1 点 a 坐标为 (x_1, y_1), 点b坐标为(x_2, y_2) \\ 现在,a b的距离 = (x_2 - x_1)^2 +(y_2 - y_1)^2 \\ 直线 ab的斜率为 k = (y_2-y_1) / (x_2-x_1) \\ 现在点c(x, y)在ab上,若与a的距离为c的话。则有:\\ \begin{cases} (x - x_1)^2 +(y - y_1)^2 = c^2 \\ (y-y_1) / (x-x_1) = k \end{cases} 点a已知,距离c已知,斜率k已知 \\ 联立方程可以解得:\\ \begin{cases} x = \pm \frac{c}{\sqrt{1 + k^2}} +x_1\\ y = \pm \frac{c k}{\sqrt{1 + k^2}} +y_1 \end{cases} a坐标为(x1,y1),b坐标为(x2,y2)现在,ab的距离=(x2x1)2+(y2y1)2直线ab的斜率为k=(y2y1)/(x2x1)现在点c(x,y)ab上,若与a的距离为c的话。则有:{(xx1)2+(yy1)2=c2(yy1)/(xx1)=ka已知,距离c已知,斜率k已知联立方程可以解得:{x=±1+k2 c+x1y=±1+k2 ck+y1

直线和圆相交的点圆两个,只有一个是合法的,这里只需要判断一下即可

bool __graphLine__containsLine(QPointF begin, QPointF end, QPointF now) {
    QLineF a(begin, end);
    QLineF b(begin, now);
    QLineF c(now, end);
    if (fabs(a.length() - b.length() - c.length()) < 1e-6) return true;
    return false;
}

计算出圆与直线的交点之后,绘制两根直线,分别向上和向下偏移30°来充当箭头即可。

void graphLine::paintArrow(graphNode* begin, graphNode* end, QPainter* painter)
{
    auto r_begin = begin->getPoint() + begin->pos();
    auto r_end = end->getPoint() + end->pos();
    QLineF lines(r_begin, r_end);
    auto length = end->getR() + end->getRoundWidth() / 2;
    // 宽度是内圈外圈各渲染一部分
    qreal dx, dy;
    if (fabs(lines.dx()) < 1e-6) {
        dx = 0;
        dy = length;
    } else {
        auto k = lines.dy() / lines.dx();
        qreal base = sqrt(k * k + 1);
        dx = length / base;
        dy = length * k / base;
    }

    QPointF dis(dx, dy);
    QPointF now;
    if (__graphLine__containsLine(r_begin, r_end, QPointF(r_end + dis))) {
        now = QPointF(r_end + dis);
    } else {
        now = QPointF(r_end - dis);
    }

    QLineF arrowHead(now, r_begin);
    arrowHead.setLength(10 + end->getRoundWidth());

    arrowHead.setAngle(arrowHead.angle() - 30); // 上方
    painter->drawLine(arrowHead);

    arrowHead.setAngle(arrowHead.angle() + 60); // 下方
    painter->drawLine(arrowHead);
}

知道如何绘制箭头之后,和绘制直线组合起来,就可以了;
paint完整代码

void graphLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    auto r_begin = begin->getPoint() + begin->pos();
    auto r_end = end->getPoint() + end->pos();
    QLineF lines(r_begin, r_end);
    setLine(lines);

    QPen pen;
    pen.setWidth(2);

    if (isSelected())
    {
        pen.setColor(QColor((color.red() + 125) % 255,
                            (color.green() ) % 255,
                            (color.blue() + 125) % 255));
    }
    else
    {
        pen.setColor(color);
    }

    painter->setPen(pen);
    painter->setRenderHint(QPainter::HighQualityAntialiasing);
    painter->drawLine(line());
    switch (lineType) {
    case LeftToRight: paintArrow(begin, end, painter); break;
    case RightToLeft: paintArrow(end, begin, painter); break;
    case TwoWayArrow: paintArrow(begin, end, painter);
                      paintArrow(end, begin, painter); break;
    case NoArrow: ;
    default:;
    }
}

在这里,添加点我选择使用右键单击添加,连接点是选择两个点就自动添加一根线

这些处理将直接在 view类里面进行处理,因此,我自定义了一个graph类

class graph : public QGraphicsView
{
    Q_OBJECT
public:
    enum SelectItemMode {
        Line,
        Node,

        None = 10086,
    };
    explicit graph(QWidget *parent = nullptr);
    QList<graphLine*> Lines();
    QList<graphNode*> Nodes();
    void setMode(SelectItemMode);
private:
    SelectItemMode selectItemMode;
    QSet<graphLine*> graphLines;
    QSet<graphNode*> graphNodes;
    QHash<graphNode*, QSet<graphNode*>> graphMap;
private:
    void mouseLButtonClick(QMouseEvent *event);
    void mouseRButtonClick(QMouseEvent *event);
protected:
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
signals:
    void mouseClickEvent(QPoint point);
    void mouseMoveEvent(QPoint point);
    void selectItem(QGraphicsItem *);
    // QWidget interface
protected:
    void resizeEvent(QResizeEvent *event) override;
    void paintEvent(QPaintEvent *event) override;

private slots:
    void on_scene_select_change();
    void on_selection_change(QGraphicsItem *, QGraphicsItem *, Qt::FocusReason);
};

添加点

graph类重写 mousePressEvent 方法。

void graph::mousePressEvent(QMouseEvent *event)
{
    switch (event->button()) {
    case Qt::MouseButton::RightButton: mouseRButtonClick(event); break;
    default:
        QGraphicsView::mousePressEvent(event);
    }
}

然后在mouseRButtonClick中处理右键事件

void graph::mouseRButtonClick(QMouseEvent *event)
{
    auto pointScene = mapToScene(event->pos());
    auto item = new graphNode(pointScene, 20, QString("A")));
    item->setFlag(QGraphicsItem::ItemIsMovable, true);
    if (selectItemMode == Node) {
        item->setFlags( item->flags() |
                        QGraphicsItem::ItemIsFocusable |
                        QGraphicsItem::ItemIsSelectable);
    }

    scene()->addItem(item);
    graphNodes.insert(item);
}

添加线

添加线需要通过处理 selectionChanged

connect(scene(), SIGNAL(selectionChanged()), this, SLOT(on_scene_select_change()));

当选择的item为2时,则连接一条直线

void graph::on_scene_select_change()
{   // mode select graphNode
    auto list = scene()->selectedItems();

    if (selectItemMode == Node)
    {
        static decltype(list) old_list;
        if (list.size() > 2) {
            scene()->clearSelection();
            return;
        }
        if (list.size() == 2) {
            auto a{dynamic_cast<graphNode*>(list[0])},
            b{dynamic_cast<graphNode*>(list[1])};
            if (old_list[0] != list[0]) std::swap(a, b);
            if (graphMap[a].contains(b)) return; // 两点之间有线不需要连接Ⅰ
            graphMap[a].insert(b);
            graphMap[b].insert(a);
            auto now = new graphLine(a, b);

            if (selectItemMode == Line) {
                now->setFlags( now->flags() |
                               QGraphicsItem::ItemIsFocusable |
                               QGraphicsItem::ItemIsSelectable);
            }
            scene()->addItem(now);
            graphLines.insert(now);
        }
        old_list = list;
    }
    else if (selectItemMode == Line) {
        if (list.size() > 1) {
            scene()->clearSelection();
            return;
        }
    }
    auto item = scene()->mouseGrabberItem();
    emit selectItem(item);
}

到这里,基本上,核心的东西就完成了,剩下的是ui界面了。

我的ui界面比较丑,大概长这样:

一个简单的绘图工具,具有世界地图、标尺和标题,使用qgraphicsview和qt中的qgraphi,札记,qt,ui,开发语言

一个简单的绘图工具,具有世界地图、标尺和标题,使用qgraphicsview和qt中的qgraphi,札记,qt,ui,开发语言

这就是一个最基本的 图 编辑器了

{来自 amjieker }文章来源地址https://www.toymoban.com/news/detail-788438.html

到了这里,关于利用QT 的 Graphics View 系统实现一个 简易的 Graph Editor的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【QT学习】Graphics View框架(进阶篇)- 派生QGraphicsItem类创建自定义图元item

    📢欢迎各位读者:点赞 👍 收藏 ⭐留言 📝 📢博客主页:https://blog.csdn.net/qq_59134387😀 📢原创不易,转载请标明出处;如有错误,敬请指正批评!💦 📢我不去想是否能够成功,既然选择了远方,便只顾风雨兼程!✨    在上一篇《Graphics View框架(基础篇)- 图元、场景

    2023年04月25日
    浏览(27)
  • Android 自定义View实战—制作一个简易输入框

    这次我们来做一个简易输入框,可以用于密码输入和验证码输入。 依然在EasyView中进行创建,在 com.easy.view 下新建一个 EasyEditText ,继承自 View ,实现里面的构造方法。 ① 构造方法 然后我们继承自 View ,重写里面的构造方法,代码如下: 下面就可以增加样式了。 ② XML样式

    2024年02月10日
    浏览(26)
  • 利用Graphics的CopyFromScreen实现简陋版的打印(C#)

                前段时间,在做一个打印的需求,需要把Winform界面的控件及内容全部打印出来,但有一个比较坑的地方是,公司提供的打印API打印单选框,打印单选框时发现选框和内容总是有那么一点点不对齐,看着很别扭。不过客户也没有过多的为难,就按照这样交付了。

    2024年02月15日
    浏览(32)
  • ubuntu下 利用QT 实现嵌入另外一个程序到当前窗口

    查看当前应用窗口名称

    2024年02月11日
    浏览(42)
  • 如何用Java实现一个简易的图书管理系统

    目录 确立对象 确立这些对象的基本信息和功能 书 书架 管理员和普通用户 各对象之间进行交互 既然是Java实现,那么就应该从面向对象的思想入手。首先需要确立有哪些对象,这些对象的功能又是什么,然后通过这些对象的交互实现这样一个建议的图书管理系统。 要实现图

    2024年02月04日
    浏览(76)
  • Qt 制作一个简易的计算器

    1.通过UI界面封装出计算器的大致模型 进入设计页面后,左侧会有各种控件,可以将他们拖拽到你想编辑的窗口中,我们要做的是计算器,所以只用到很少几个控件,我们最主要用到Push Button这个控件来做我们计算器的按钮,lineEdit显示数字,我们可以将它拖拽到窗口,然后就

    2024年02月05日
    浏览(114)
  • 用QT/C++写一个简易文本编辑器

    学习QT的小练习,先看一下目前实现的效果。   功能: 编辑文本保存为txt。 打开一个txt文本文件,可编辑可保存。 文本编辑功能:剪切,复制,粘贴,加粗,斜体,下划线,设置颜色,字体。 要点: QT Designer的UI可视化设计:基本控件布局,资源导入,菜单动作,信号槽的

    2024年02月05日
    浏览(47)
  • Python Qt学习(十)一个简易的POP3邮件客户端

    公司把126这类的邮箱网站都封了,正好现在无事,加之,算是一个对这俩周学习Qt的一个总结吧。遂写了这么一个简易的通过POP3协议接收126邮件的客户端。 源代码: 截图:

    2024年02月09日
    浏览(30)
  • unity 利用Graphics.Blit来制作图片效果

    c# 的代码 source可以是当前相机的RenderTexture也可以是准备好的一张图,然后利用material提供的效果将效果输出到renderTexture,第三个参数是使用哪个pass 0表示是使用第一个 下面是例子对应的shader,是一个模糊效果

    2024年01月21日
    浏览(31)
  • unity 利用Scroll View实现滑动翻页效果

    1.在Hierarchy视图右键创建UI-Scroll View。 Scrollbar可根据自己需求选择是否删除,我这里制作的翻页日历用不上我就删除了。 connect节点挂上Grid Layout Group组件,参数属性可参考unity API。 下面是具体实现代码  onLeft和onRight绑定左右翻页按钮事件  

    2024年01月25日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包