Qt (高仿Visio)流程图组件开发(三) 图元基类如何定义,流程图多种图元类型实现


本系列目录

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);
}

  最后,代码完整实现如上,针对其中几个要点进行解释。

  1. setPos(0,0);是为了设置默认位置,对之后的通过代码设置相对位置有很大作用。
  2. setFlag(QGraphicsItem::ItemIsMovable,true);设置运行鼠标拖动,这样就可以使用qt原生的图元移动功能。
  3. setFlag(QGraphicsItem::ItemIsSelectable,true);设置运行被选中,这样就可以通过this->isSelected()的方式来获取该图元是否被选中,以此来绘制自定义的选中态。
  4. setZValue(2);设置图元层级,目前设置这个主要是为了将连线与一般图元区分开,不然会将曲线的选中态遮盖(可以自行试验)。

3、自循环图元

  这里单独把自循环列出是因为其是一个较为典型的特殊图元,自循环图元自带一条指向自己首尾的曲线,这里的实现基本与上文相同,只是多出了一个设置自循环连线的接口,多维护了一个连线对象。

	// 添加自循环连线
	void SetCirculationLink(FlowcharGraphicsLink* _item);
private:
	FlowcharGraphicsLink						*graphics_link_;

  玩家想实现一些特殊的图元也可以通过这种方式扩展。

4、线图元

  也是一个特殊图元,只不过涉及箭头绘制、曲线的计算、特殊接口定义等等,这里将其单独写出,之后会有对该类的详细讲解

总结

本文主要讲解图元基类的定义,及一些简单自定义图元如何去实现。
本文只是经验分享,描述内容并不绝对,如有误差欢迎指正。

如果此文帮助到你( •̀ ω •́ )✧,动动小手点个赞可好O(∩_∩)O。

原创文章,转载请标明本文出处。
  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 基于Qt的复杂流程图控件设计与实现包括以下几个方面。 首先,设计数据结构存储流程图的信息。可以使用节点和连接两种基本对象来表示流程图。节点保存节点类型、位置、大小以及其他自定义属性。连接保存连接的起始节点、终止节点以及其他自定义属性。 其次,设计绘图控件并实现绘图功能。可以使用Qt的绘图类来绘制节点和连接。通过重写绘图事件函数,可以在控件中绘制节点的图形、标签和其他自定义内容。 然后,实现节点和连接的编辑功能。可以通过鼠标交互实现节点和连接的拖拽、调整大小、删除等操作。可以使用鼠标事件和几何计算来实现这些功能。 接下来,实现流程图的布局和自动调整功能。可以采用拖拽布局、栅格布局等方法来调整节点的位置和连接的路径,使流程图看起来更加美观和整齐。 最后,实现流程图的数据交互和保存功能。可以使用信号和槽机制来实现节点和连接的交互。可以使用XML或JSON等格式将流程图保存为文件,并能够读取和加载已保存的流程图。 综上所述,基于Qt的复杂流程图控件的设计和实现需要涉及数据结构、绘图、交互、布局以及数据保存等方面的技术。通过合理组织和实现这些技术,可以实现一个功能强大、易用且美观的流程图控件。 ### 回答2: 基于Qt的复杂流程图控件设计与实现涉及以下几个关键步骤。 首先,需要设计流程图控件的外观和功能。可以考虑流程节点、线条样式、标签显示等方面的设计。可以使用Qt提供的图形引擎和绘制函数,结合自定义的绘制函数,来实现流程图控件的外观设计。 接下来,需要定义流程节点和线条的数据结构。考虑到复杂流程图可能包含多种类型的节点和线条,可以使用继承的方式来定义不同类型的节点和线条类,以便于对它们进行管理和渲染。同时,可以定义属性和方法来保存和处理节点和线条的相关信息。 然后,需要实现流程节点和线条的创建与连接功能。可以通过鼠标事件来捕获用户的交互操作,创建新的节点或者连接已有节点。可以使用容器来保存所有的节点和线条对象,并实现对它们的增加、删除、查找等操作。 接下来,需要实现流程节点和线条的编辑功能。可以考虑对节点和线条的拖拽、缩放、旋转、改变样式等支持,来满足用户对流程图的编辑需求。可以通过重载事件处理函数和绘制函数,来实现这些编辑功能。 最后,可以考虑加入其他的辅助功能,如撤销、重做、导出图像、导入图像等。可以使用Qt提供的信号和槽机制,来实现这些辅助功能,提升流程图控件的易用性和灵活性。 总的来说,基于Qt的复杂流程图控件的设计与实现需要考虑外观设计、数据结构设计、节点和线条的创建与连接、编辑功能的实现以及其他辅助功能的加入。通过合理的设计和实现,可以满足用户对复杂流程图的需求,并提供良好的用户体验。 ### 回答3: 基于Qt的复杂流程图控件的设计与实现需要考虑以下几个关键方面。 首先,需要设计可扩展的图形模型。通过定义节点、连接线等基本图元的数据结构,可以构建起流程图的数据模型。同时,还需要考虑节点属性、连接线的起始节点和目标节点、节点间的运行顺序等概念。 其次,需要设计绘图逻辑。利用Qt的绘图功能,可以根据图形模型中定义的节点和连接线信息,将它们绘制到控件上。可以使用QPainter类进行绘图操作,按照节点和连接线的位置、样式等进行绘制。 进一步,需要实现节点和连接线的交互功能。通过捕捉鼠标事件或者键盘事件,可以实现节点的拖动、缩放、旋转等功能。同时,还可以实现选中、高亮和变色等交互效果,提升用户体验。 另外,需要实现流程图的布局算法。对于复杂的流程图,自动布局是必要的。可以采用层次布局、树形布局等算法,确保节点和连接线在控件中合理排列,并避免节点之间的重叠和遮挡。 最后,还可以考虑添加一些附加功能,例如撤销/重做、导入/导出、文本编辑等。这些功能可以增强流程图控件的实用性。 总之,基于Qt的复杂流程图控件的设计与实现需要通过数据模型、绘图逻辑、交互功能、布局算法等多个方面的设计来完成。通过合理的架构和优化性能的实现方式,可以实现一个功能完备、易用的流程图控件。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦醒梦起

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值