1 需求描述
基于Qt图形视图框架开发一个网络拓扑模块,用于可视化展示、控制HUB(类似于交换机)与NODE(类似于连接到交换机上的设备)的关系网路。
2 设计思路
先来看个图:
这里将图形项分为了以下几种类型:
- 连接点类型(TopologyPointItem):作为连接线的起点和终点,仅此而已;
- 节点类型(TopologyNodeItem):节点包含1个连接点,连接点作为节点的子项;
- 集线器类型(TopologyHubItem):集线器包含16个连接点,连接点作为集线器的子项;
- 连接线类型(TopologyArrowItem):连接线负责连接不同的连接点,会记录起始连接点和结束连接点,当节点或集线器图形项移动后,连接线自动实时刷新。
这里部分功能也是参考了Qt的示例程序Diagram Scene Example,有兴趣的朋友可以看下。
3 代码实现
3.1 TopologyBaseItem
节点类型和集线器类型除了连接点个数不同,其他都差不多,这里抽象一个基类出来,实现一些共性。
#ifndef TOPOLOGYBASEITEM_H
#define TOPOLOGYBASEITEM_H
#include <QGraphicsObject>
class TopologyArrowItem;
class TopologyBaseItem : public QGraphicsObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
explicit TopologyBaseItem(QGraphicsItem *parent = nullptr);
QString name() const
{
return m_name;
}
/**
* @brief addArrow 添加关联的连接线
* @param arrow 连接线
*/
void addArrow(TopologyArrowItem *arrow);
QRectF boundingRect() const override;
public slots:
/**
* @brief setName 设置显示名称
* @param name 名称
*/
void setName(QString name);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
signals:
void nameChanged(QString name);
private:
QList<TopologyArrowItem *> m_arrows;
QString m_name;
};
#endif // TOPOLOGYBASEITEM_H
#include "topologybaseitem.h"
#include "topologyarrowitem.h"
#include <QPainter>
#include <QPen>
TopologyBaseItem::TopologyBaseItem(QGraphicsItem *parent) : QGraphicsObject(parent)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);
}
void TopologyBaseItem::setName(QString name)
{
if (m_name == name)
{
return;
}
m_name = name;
emit nameChanged(m_name);
}
void TopologyBaseItem::addArrow(TopologyArrowItem *arrow)
{
m_arrows.append(arrow);
}
QVariant TopologyBaseItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemPositionChange)
{
foreach (TopologyArrowItem *arrow, m_arrows)
{
arrow->updatePosition(); //节点移动后,自动刷新连接线
}
}
return value;
}
QRectF TopologyBaseItem::boundingRect() const
{
qreal penWidth = 1;
qreal radius = 30;
qreal diameter = 60;
return QRectF(-radius - penWidth / 2, -radius - penWidth / 2,
diameter + penWidth, diameter + penWidth);
}
void TopologyBaseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
//绘制名称
QPen pen;
pen.setWidth(2);
pen.setJoinStyle(Qt::MiterJoin);
painter->setPen(pen);
QFont font;
font.setFamily("System");
font.setPixelSize(9);
painter->setFont(font);
QRectF boundingRect = this->boundingRect();
painter->drawRect(boundingRect);
painter->drawText(boundingRect, Qt::AlignCenter, m_name);
}
void TopologyBaseItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
foreach (TopologyArrowItem *arrow, m_arrows)
{
arrow->updatePosition();
}
QGraphicsObject::mouseReleaseEvent(event);
}
3.2 TopologyNodeItem
节点类型需要添加一个连接点作为子项,实现如下:
#ifndef TOPOLOGYNODEITEM_H
#define TOPOLOGYNODEITEM_H
#include "topologybaseitem.h"
class TopologyArrowItem;
class TopologyNodeItem : public TopologyBaseItem
{
Q_OBJECT
public:
enum { Type = UserType + 2 };
explicit TopologyNodeItem(QGraphicsItem *parent = nullptr);
int type() const override { return Type; }
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
};
#endif // TOPOLOGYNODEITEM_H
#include "topologynodeitem.h"
#include "topologypointitem.h"
#include "topologyarrowitem.h"
#include <QPainter>
#include <QPen>
#include <QFont>
#include <QDebug>
#define CONNECTOR_LENGTH (5)
TopologyNodeItem::TopologyNodeItem(QGraphicsItem *parent) : TopologyBaseItem(parent)
{
TopologyPointItem *item = new TopologyPointItem(this);
item->setPos(0, boundingRect().height()/2 + CONNECTOR_LENGTH + item->boundingRect().height()/2);
item->setPointNum(-1);
}
void TopologyNodeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
TopologyBaseItem::paint(painter, option, widget);
QRectF boundingRect = this->boundingRect();
//画连接点中间线
painter->save();
painter->translate(boundingRect.bottomRight());
painter->rotate(180);
painter->drawLine(QPointF(boundingRect.width()/2, 0), QPointF(boundingRect.width()/2, -CONNECTOR_LENGTH));
painter->restore();
}
3.3 TopologyHubItem
集线器类型需要16个连接点作为子项,实现如下:
#ifndef TOPOLOGYHUBITEM_H
#define TOPOLOGYHUBITEM_H
#include "topologybaseitem.h"
class TopologyHubItem : public TopologyBaseItem
{
Q_OBJECT
public:
enum { Type = UserType + 1 };
explicit TopologyHubItem(QGraphicsItem *parent = nullptr);
int type() const override { return Type; }
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
};
#endif // TOPOLOGYHUBITEM_H
#include "topologyhubitem.h"
#include "topologypointitem.h"
#include <QPainter>
#include <QPen>
#include <QDebug>
#define CONNECTOR_SPACING (5)
TopologyHubItem::TopologyHubItem(QGraphicsItem *parent) : TopologyBaseItem(parent)
{
int pointNum = 1; //连接点编号(1-16)
QRectF boundingRect = this->boundingRect();
for (int i = 0; i < 4; ++i)
{
TopologyPointItem *item = new TopologyPointItem(this);
item->setPointNum(pointNum++);
item->setPos(boundingRect.topLeft());
item->moveBy(boundingRect.height()/5 * (i + 1), -CONNECTOR_SPACING - item->boundingRect().height()/2);
}
for (int i = 0; i < 4; ++i)
{
TopologyPointItem *item = new TopologyPointItem(this);
item->setPointNum(pointNum++);
item->setPos(boundingRect.topRight());
item->moveBy(CONNECTOR_SPACING + item->boundingRect().height()/2, boundingRect.height()/5 * (i + 1));
}
for (int i = 0; i < 4; ++i)
{
TopologyPointItem *item = new TopologyPointItem(this);
item->setPointNum(pointNum++);
item->setPos(boundingRect.bottomRight());
item->moveBy(-boundingRect.height()/5 * (i + 1), CONNECTOR_SPACING + item->boundingRect().height()/2);
}
for (int i = 0; i < 4; ++i)
{
TopologyPointItem *item = new TopologyPointItem(this);
item->setPointNum(pointNum++);
item->setPos(boundingRect.bottomLeft());
item->moveBy(-CONNECTOR_SPACING - item->boundingRect().height()/2, -boundingRect.height()/5 * (i + 1));
}
}
void TopologyHubItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
TopologyBaseItem::paint(painter, option, widget);
QRectF boundingRect = this->boundingRect();
//绘制连接点中间线
painter->save();
painter->translate(boundingRect.topLeft());
for (int i = 0; i < 4; ++i)
{
painter->drawLine(QPointF(boundingRect.width()/5 * (i + 1), 0), QPointF(boundingRect.width()/5 * (i + 1), -CONNECTOR_SPACING));
}
painter->restore();
painter->save();
painter->translate(boundingRect.topRight());
painter->rotate(90);
for (int i = 0; i < 4; ++i)
{
painter->drawLine(QPointF(boundingRect.width()/5 * (i + 1), 0), QPointF(boundingRect.width()/5 * (i + 1), -CONNECTOR_SPACING));
}
painter->restore();
painter->save();
painter->translate(boundingRect.bottomRight());
painter->rotate(180);
for (int i = 0; i < 4; ++i)
{
painter->drawLine(QPointF(boundingRect.width()/5 * (i + 1), 0), QPointF(boundingRect.width()/5 * (i + 1), -CONNECTOR_SPACING));
}
painter->restore();
painter->save();
painter->translate(boundingRect.bottomLeft());
painter->rotate(270);
for (int i = 0; i < 4; ++i)
{
painter->drawLine(QPointF(boundingRect.width()/5 * (i + 1), 0), QPointF(boundingRect.width()/5 * (i + 1), -CONNECTOR_SPACING));
}
painter->restore();
}
3.4 TopologyArrowItem
最后就是连接线了,连接线会根据起始连接点和结束连接点进行绘制,代码如下:
#ifndef TOPOLOGYARROWITEM_H
#define TOPOLOGYARROWITEM_H
#include <QGraphicsPathItem>
class TopologyPointItem;
class TopologyArrowItem : public QGraphicsPathItem
{
public:
enum { Type = UserType + 4 };
explicit TopologyArrowItem(TopologyPointItem *startItem,
TopologyPointItem *endItem,
QGraphicsItem *parent = nullptr);
int type() const override { return Type; }
QPainterPath shape() const override;
void updatePosition();
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private:
TopologyPointItem *m_pStartItem = nullptr;
TopologyPointItem *m_pEndItem = nullptr;
};
#endif // TOPOLOGYARROWITEM_H
#include "topologyarrowitem.h"
#include "topologypointitem.h"
#include <QPainterPath>
#include <QPainterPathStroker>
#include <QPen>
#include <QDebug>
#include <QCursor>
#include <QAction>
TopologyArrowItem::TopologyArrowItem(TopologyPointItem *startItem,
TopologyPointItem *endItem,
QGraphicsItem *parent) : QGraphicsPathItem(parent)
{
m_pStartItem = startItem;
m_pEndItem = endItem;
setCursor(QCursor(Qt::ArrowCursor));
// setAcceptHoverEvents(true);
setPen(QPen(QColor("black"), 2));
}
QPainterPath TopologyArrowItem::shape() const
{
QPainterPathStroker pps;
pps.setWidth(2);
return pps.createStroke(this->path());
}
void TopologyArrowItem::updatePosition()
{
QPointF startPoint = mapFromItem(m_pStartItem, 0, 0);
QPointF endPoint = mapFromItem(m_pEndItem, 0, 0);
QPainterPath path(startPoint);
QPointF breakPointFirst(startPoint + m_pStartItem->recommendedBreakPoint());
QPointF breakPointLast(endPoint + m_pEndItem->recommendedBreakPoint());
path.lineTo(breakPointFirst);
//NODE连接到HUB上方
if (m_pStartItem->recommendedBreakPoint().y() > 0 && m_pEndItem->recommendedBreakPoint().y() < 0)
{
if (breakPointFirst.y() >= breakPointLast.y())
{
QPointF breakPoint1(breakPointFirst.x() - (breakPointFirst.x() - breakPointLast.x())/2, breakPointFirst.y());
QPointF breakPoint2(breakPointLast.x() + (breakPointFirst.x() - breakPointLast.x())/2, breakPointLast.y());
path.lineTo(breakPoint1);
path.lineTo(breakPoint2);
}
else
{
QPointF breakPoint1(breakPointFirst.x(), breakPointFirst.y() + (breakPointLast.y() - breakPointFirst.y())/2);
QPointF breakPoint2(breakPointLast.x(), breakPoint1.y());
path.lineTo(breakPoint1);
path.lineTo(breakPoint2);
}
}
//NODE连接到HUB右方
if (m_pStartItem->recommendedBreakPoint().y() > 0 && m_pEndItem->recommendedBreakPoint().x() > 0)
{
if (breakPointFirst.y() >= breakPointLast.y())
{
if (breakPointFirst.x() >= breakPointLast.x())
{
QPointF breakPoint1(breakPointFirst.x() - (breakPointFirst.x() - breakPointLast.x())/2, breakPointFirst.y());
QPointF breakPoint2(breakPoint1.x(), breakPointLast.y());
path.lineTo(breakPoint1);
path.lineTo(breakPoint2);
}
else
{
//todo
}
}
else
{
QPointF breakPoint1(breakPointFirst.x(), breakPointLast.y());
path.lineTo(breakPoint1);
}
}
path.lineTo(breakPointLast);
path.lineTo(endPoint);
setPath(path);
}
void TopologyArrowItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
setPen(QPen(QColor("black"), 2));
setZValue(0);
QGraphicsPathItem::hoverEnterEvent(event);
}
void TopologyArrowItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
setPen(QPen(QColor("darkgrey"), 2));
setZValue(-1);
QGraphicsPathItem::hoverLeaveEvent(event);
}
4 总结
使用图形视图框架实现网络拓扑可谓事半功倍,其实这里所做的只是UI层面的东西,完全可以和一些业务逻辑进行绑定,只需要稍加扩展即可;
上面的实现过程也并没有用到什么高级的设计技巧,都是一些常规思路,只需要一点点的面向对象编程思想就行了,把类设计好,然后无非就是重写一些事件函数以及一些相关的虚函数,这些函数什么时候调用,Qt框架已经处理好啦,我们只需实现,仔细想想,是不是这样?这个就是框架的魔力。
5 下载
这个demo属于半残品,很多细节还未处理好,就不发了,如果确实需要参考,可以评论留下邮箱,我看到了会通过邮件的形式发送。
标签:15,Qt,boundingRect,void,网络拓扑,item,QPointF,include,painter From: https://www.cnblogs.com/luoxiang/p/16735509.html