文章目录
本系列目录
Qt (高仿Visio)流程图组件开发(一) 效果展示及基本开发框架构思
Qt (高仿Visio)流程图组件开发(二) 基本图元绘制 图元间连线绘制
Qt (高仿Visio)流程图组件开发(三) 图元基类如何定义,流程图多种图元类型实现
Qt (高仿Visio)流程图组件开发(四) 流程图 图元对齐 磁吸线功能
Qt (高仿Visio)流程图组件开发(五) 流程图 双击编辑图元内容实现
Qt (高仿Visio)流程图组件开发(六) 流程图 线图元 如何绘制曲线 连接线移除视口后无法显示
Qt (高仿Visio)流程图组件开发(七) 流程图 简单操作界面搭建
Qt (高仿Visio)流程图组件开发(八) 流程图 鼠标拖动图元到场景(QGraphicsScene)创建
Qt (高仿Visio)流程图组件开发(九) 流程图 代码展示
前言
本文主要讲解一些基本流程图图元的实现,例如:流程图元、自循环图元、判断图元、线图元,这里只实现这四种,其他图元类型的实现大同小异。同时也会讲解如何从这些图元中抽离出通用部分,定义图元基类。只是经验分享,描述内容并不绝对,如有误差欢迎指正。
一、图元基类的定义
1、图元信息基类结构体
首先,所有图元都具有一些信息(内容、id、提示信息等等),抽离出通用的部分可以声明在一个结构体基类中,并维护在图元基类中。如下为基类结构体:
enum class ItemType
{
Null = 0, // 空
Nomal, // 代表全局常态
Link, // 连接线
Rect, // 流程
Condition, // 判定
Circulation, // 自循环
};
struct FlowchartItemType
{
ItemType type = ItemType::Null;
};
enum class FlowchartCursor
{
ArrowCursor = 0,
DrawLinkCursor,
SizeAllCurSor,
OpenHandCursor,
ClosedHandCursor,
};
// 图元样式信息
struct FlowchartStyleBase
{
QPen pen_; // 基本画笔样式--背景相关
QBrush brush_; // 基本画刷样式--背景相关
QPen text_pen_; // 文本画笔样式
QFont font_; // 字体样式
FlowchartStyleBase()
{
pen_ = QPen();
pen_.setColor(QColor(65, 113, 156));
pen_.setWidth(1);
brush_ = QBrush(QColor(89, 152, 209));
text_pen_ = QPen();;
text_pen_.setColor(QColor(254, 255, 255));
text_pen_.setWidth(1);
font_ = QFont("Microsoft YaHei", 12, 2);
}
};
// 图元数据信息
struct FlowchartContentBase
{
QString id_; // 图元id
QString content_; // 图元内容
QString tooltip_; // 图元提示信息
FlowchartContentBase()
{
id_ = QUuid::createUuid().toString();
content_ = "Text";
tooltip_ = "Tooltip";
}
};
// 图元父子结构关系信息
struct FlowchartStructuralData
{
FlowchartStructuralData()
{
}
QVector<QString> father_ids_; // 父节点id集合
QVector<QString> children_ids_; // 子节点id集合
};
// 图元结构体基类
struct FlowchartInforBase
{
double position_x_, position_y_, width_, height_;
FlowchartStyleBase item_style_;
FlowchartContentBase item_content_;
FlowchartItemType item_type_;
FlowchartStructuralData item_structural_;
FlowchartInforBase()
{
position_x_ = 0.0;
position_y_ = 0.0;
width_ = 160.0;
height_ = 40.0;
item_style_ = FlowchartStyleBase();
item_content_ = FlowchartContentBase();
item_structural_ = FlowchartStructuralData();
};
FlowchartInforBase(double _x, double _y, double _width = 160.0, double _height = 40.0)
{
position_x_ = _x;
position_y_ = _y;
width_ = _width;
height_ = _height;
item_style_ = FlowchartStyleBase();
item_content_ = FlowchartContentBase();
item_structural_ = FlowchartStructuralData();
};
};
typedef std::vector<FlowchartInforBase*> FlowchartInforBases;
2、图元位置
所有的图元都需要获取其在场景上的位置,用来创建连线、判断弹出框位置等等。
virtual QPointF GetCenterPoint(){ return center_point_; };
virtual QPointF GetLeftPoint(){ return left_point_; };
virtual QPointF GetRightPoint(){ return right_point_; };
virtual QPointF GetTopPoint(){ return top_point_; };
virtual QPointF GetBottomPoint(){ return bottom_point_; };
3、父子对象关系
基类中还应该存有父子对象,相关连线对象,提供添加父子对象节点、清空依赖关系,获取父子对象集合等接口,减少实现一个自定义图元对象时需要实现的内容。接口定义如下:
// 获取图元id
QString GetItemId();
// 获取图元类型
ItemType GetItemType();
// 添加子节点
void AddChild(FlowchartGraphicsItem* _item, FlowcharGraphicsLink* _link_item);
// 删除子节点及其连线
void DelChild(QString _id);
// 添加父节点
void AddFather(FlowchartGraphicsItem* _item, FlowcharGraphicsLink* _link_item);
// 删除父节点连线及其连线
void DelFather(QString _id);
// 清空图元依赖关系
void ItemClear();
// 获取子对象集合
QMap<QString, FlowchartGraphicsItem*> GetChildrenItems();
// 获取父对象集合
QMap<QString, FlowchartGraphicsItem*> GetFatherItems();
二、自定义图元实现
有了图元基类后,所有的自定义图元只需要继承图元基类,实现虚函数接口,绘制需要的图形样式即可。
1、自定义图元基类(FlowchartGraphicsItem)与Qt原生图元基类(QGraphicsRectItem)相互转化关系
因为自定义的图元基类为c++类,所以在调用Qt相关接口时,需要转化为原生基类来使用,转化关系如下:
// 对象转化 qt原生对象->图元基类对象
FlowchartGraphicsItem* QGraphToFlow(QGraphicsItem* _item);
// 对象转化 图元基类对象->qt原生对象
QGraphicsItem* FlowToQGraph(FlowchartGraphicsItem* _item);
// 对象转化 qt原生对象->图元基类对象
FlowchartGraphicsItem* FlowchartScene::QGraphToFlow(QGraphicsItem* _item)
{
if (_item == nullptr)
return nullptr;
FlowchartGraphicsRectItem* item = (FlowchartGraphicsRectItem*)_item;
return (FlowchartGraphicsItem*)item;
}
// 对象转化 图元基类对象->qt原生对象
QGraphicsItem* FlowchartScene::FlowToQGraph(FlowchartGraphicsItem* _item)
{
if (_item == nullptr)
return nullptr;
FlowchartGraphicsRectItem* item = (FlowchartGraphicsRectItem*)_item;
return (QGraphicsItem*)item;
}
2、流程图元、判断图元
流程图元与判断图元示意图如下:
流程图元与判断图元包括之后的自定义流程图实现都大同小异,现已流程图元为例进行详细讲解。讲解内容大都集中在代码注释中。流程图元类(FlowchartGraphicsRectItem)
首先,需要创建一个属于该图元的结构体(FlowchartItemRectInfo),继承之前定义的基类结构体(FlowchartInforBase),如果有特殊的需求可以添加在结构体声明中,这样是为了保证在开发自定义图元类过程中需要保存一些特殊的数据。
// 流程矩形结构体
struct FlowchartItemRectInfo : FlowchartInforBase
{
FlowchartItemRectInfo() :FlowchartInforBase()
{
};
FlowchartItemRectInfo(double _x, double _y, double _width = 160.0, double _height = 40.0)
:FlowchartInforBase(_x, _y, _width, _height)
{
};
};
其次,流程图元类(FlowchartGraphicsRectItem)需要继承QObject、QGraphicsRectItem、FlowchartGraphicsItem,QObject类不必多说,继承QGraphicsRectItem是因为要实现的流程图元为一矩形图元,所以直接继承现有的矩形图元类即可,包括之后的自定义图元也是根据图元需求来继承适合的类,FlowchartGraphicsItem类为我们的自定义结构体基类,类的父子关系、信息等都由该基类维护。
// 流程矩形
class FlowchartGraphicsRectItem
: public QObject
, public QGraphicsRectItem
, public FlowchartGraphicsItem
{
Q_OBJECT
public:
explicit FlowchartGraphicsRectItem(FlowchartItemRectInfo* _infor, QObject *parent = nullptr);
~FlowchartGraphicsRectItem();
// 修改文本内容
virtual void SetText(QString _content, QString _tooltip = "") override;
// 获取界面信息
virtual FlowchartInforBase* GetItemInformation() override;
/****************获取图元点--中心、左、右、上、下*******************/
virtual QPointF GetCenterPoint() override { return mapToScene(center_point_); };
virtual QPointF GetLeftPoint() override{ return mapToScene(left_point_); };
virtual QPointF GetRightPoint() override{ return mapToScene(right_point_); };
virtual QPointF GetTopPoint() override{ return mapToScene(top_point_); };
virtual QPointF GetBottomPoint() override{ return mapToScene(bottom_point_); };
private:
// 绘制文本内容
void DrawItemText(QPainter *_painter);
public:
virtual QRectF boundingRect() const;
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR);
private:
// 流程矩形结构体方便取用
FlowchartItemRectInfo* item_infor_;
};
FlowchartGraphicsRectItem::FlowchartGraphicsRectItem(FlowchartItemRectInfo* _infor, QObject *parent)
: QObject(parent)
, FlowchartGraphicsItem(_infor, ItemType::Rect)
{
// ! [1] 初始化图元位置大小
item_infor_ = _infor;
setRect({ QPointF(-item_infor_->width_ / 2, -item_infor_->height_ / 2), QSizeF(item_infor_->width_, item_infor_->height_) });
setPos(0,0);
center_point_ = pos();
left_point_ = pos() + QPointF(-item_infor_->width_ / 2 - 3, 0);
right_point_ = pos() + QPointF(item_infor_->width_ / 2 + 3, 0);
top_point_ = pos() + QPointF(0, -item_infor_->height_ / 2 - 3);
bottom_point_ = pos() + QPointF(0, item_infor_->height_ / 2 + 3);
setPos(item_infor_->position_x_, item_infor_->position_y_);
// ! [2] 图元相关设置
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
// 设置图元层级
setZValue(2);
// ! [3] 图元信息设置
this->setToolTip(item_infor_->item_content_.tooltip_);
}
FlowchartGraphicsRectItem::~FlowchartGraphicsRectItem()
{
}
void FlowchartGraphicsRectItem::DrawItemText(QPainter *_painter)
{
_painter->save();
/*[] 计算字体宽度 根据item大小*/
_painter->setFont(item_infor_->item_style_.font_);
QFontMetrics font_metrics = _painter->fontMetrics();
QRectF rect = this->rect();
QRectF resize_rect = rect;
QString text = item_infor_->item_content_.content_;
QString tremporart_text;
resize_rect.setHeight(rect.height());
resize_rect.setWidth(rect.width());
tremporart_text = font_metrics.elidedText(text, Qt::ElideRight, resize_rect.width());
_painter->setPen(item_infor_->item_style_.text_pen_);
tremporart_text.replace(text, "");
if (!tremporart_text.isEmpty())
resize_rect.setWidth(rect.width());
resize_rect.moveCenter(rect.center());
_painter->drawText(resize_rect, Qt::AlignCenter | Qt::TextWrapAnywhere, text);
_painter->restore();
}
void FlowchartGraphicsRectItem::SetText(QString _content, QString _tooltip)
{
item_infor_->item_content_.content_ = _content;
if (_tooltip.compare("") == 0)
return;
item_infor_->item_content_.tooltip_ = _tooltip;
this->setToolTip(item_infor_->item_content_.tooltip_);
this->update();
}
FlowchartInforBase* FlowchartGraphicsRectItem::GetItemInformation()
{
// 位置信息更新
item_infor_->position_x_ = this->scenePos().x();
item_infor_->position_y_ = this->scenePos().y();
item_infor_->width_ = this->boundingRect().width();
item_infor_->height_ = this->boundingRect().height();
// 父子关系更新
FlowchartStructuralData structural_data;
QMap<QString, FlowchartGraphicsItem*> father_items = GetFatherItems();
for (QMap<QString, FlowchartGraphicsItem*>::iterator iter = father_items.begin(); iter != father_items.end(); iter++){
structural_data.father_ids_.push_back(iter.key());
}
QMap<QString, FlowchartGraphicsItem*> children_items = GetChildrenItems();
for (QMap<QString, FlowchartGraphicsItem*>::iterator iter = children_items.begin(); iter != children_items.end(); iter++) {
structural_data.children_ids_.push_back(iter.key());
}
item_infor_->item_structural_ = structural_data;
item_base_info_ = (FlowchartInforBase*)item_infor_;
return item_base_info_;
}
QRectF FlowchartGraphicsRectItem::boundingRect() const
{
QRectF rect = QGraphicsRectItem::boundingRect().adjusted(-3, -3, 3, 3);
return rect;
}
void FlowchartGraphicsRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget /*= Q_NULLPTR*/)
{
painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter->save();
painter->setPen(item_infor_->item_style_.pen_);
painter->setBrush(item_infor_->item_style_.brush_);
painter->drawRect(this->boundingRect());
painter->restore();
/*[] 绘制选中外框*/
if (this->isSelected()) {
/*[1]: 绘制外围的矩形框 */
QPen selected_pen(QColor(147, 147, 147));
selected_pen.setWidth(0.5);
selected_pen.setStyle(Qt::DashLine);
painter->setPen(selected_pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(this->boundingRect());
const auto left_center_rect = QRectF((left_point_ - QPointF(2, 2)), QSizeF(5, 5));
const auto right_center_rect = QRectF((right_point_ - QPointF(3, 3)), QSizeF(5, 5));
const auto top_center_rect = QRectF((top_point_ - QPointF(2, 2)), QSizeF(5, 5));
const auto bottom_center_rect = QRectF((bottom_point_ - QPointF(3, 3)), QSizeF(5, 5));
/*[2]: 绘制四个方向的连接点 */
painter->setPen(QColor(147, 147, 147));
painter->setBrush(QColor(255, 255, 255));
painter->drawRect(left_center_rect);
painter->drawRect(right_center_rect);
painter->drawRect(top_center_rect);
painter->drawRect(bottom_center_rect);
}
DrawItemText(painter);
}
最后,代码完整实现如上,针对其中几个要点进行解释。
- setPos(0,0);是为了设置默认位置,对之后的通过代码设置相对位置有很大作用。
- setFlag(QGraphicsItem::ItemIsMovable,true);设置运行鼠标拖动,这样就可以使用qt原生的图元移动功能。
- setFlag(QGraphicsItem::ItemIsSelectable,true);设置运行被选中,这样就可以通过this->isSelected()的方式来获取该图元是否被选中,以此来绘制自定义的选中态。
- setZValue(2);设置图元层级,目前设置这个主要是为了将连线与一般图元区分开,不然会将曲线的选中态遮盖(可以自行试验)。
3、自循环图元
这里单独把自循环列出是因为其是一个较为典型的特殊图元,自循环图元自带一条指向自己首尾的曲线,这里的实现基本与上文相同,只是多出了一个设置自循环连线的接口,多维护了一个连线对象。
// 添加自循环连线
void SetCirculationLink(FlowcharGraphicsLink* _item);
private:
FlowcharGraphicsLink *graphics_link_;
玩家想实现一些特殊的图元也可以通过这种方式扩展。
4、线图元
也是一个特殊图元,只不过涉及箭头绘制、曲线的计算、特殊接口定义等等,这里将其单独写出,之后会有对该类的详细讲解
总结
本文主要讲解图元基类的定义,及一些简单自定义图元如何去实现。
本文只是经验分享,描述内容并不绝对,如有误差欢迎指正。
如果此文帮助到你( •̀ ω •́ )✧,动动小手点个赞可好O(∩_∩)O。
原创文章,转载请标明本文出处。