本文将对QGraphicsScene, QGraphicsView,QGraphicsItem这三个类进行简单介绍,并通过diagram流程图项目对自定义QGraphicsItem操作进行演示讲解。
程序主界面
添加步骤
步骤删除
步骤连接
一键排序
gitee
https://gitee.com/jiang_bin_yu/qt-diagram-jby
csdn
https://download.csdn.net/download/qq_37373742/87376898
QGraphicsScene, QGraphicsView,QGraphicsItem,分别是场景、视图和图元
1、场景:
QGraphicsScene场景类完成如下功能:
2、视图:
QGraphicsView是视图窗口控件, 它使场景的内容可视化。可以连接几个视图到一个场景,也可以为相同的数据集提供几种不同的视口,
QGraphicsView是可滚动的窗口部件,如果需要使用OpenGL,使用QGrphicsView::setViewPort()将视口设置为QGLWidget。
视图接收键盘和鼠标的输入事件,并把它翻译为场景事件(将坐标转换为场景的坐标),使用变换矩阵函数QGraphicsView::matrix()
可以变换场景的坐标,通过这种方法尅实现场景缩放和旋转。QGraphicsView::mapToScene()和QGraphicsView::mapFromScene()将
视图和场景的坐标进行转换。
3、图元:
QGraphicsItem是图元基类,QGraphics View框架提供了几种标准的图元。
如QGraphicsRectItem、QGraphicsEllipseItem,QGraphicsTextItem等,可以继承QGraphicsItem实现符合自己需要的图元。
QGraphicsItem功能:
要编写自己的图形项,首先要创建QGraphicsItem的子类,然后从实现它的两个纯虚拟公共函数开始:
**boundingRect()**它返回该项绘制的区域的估计值
**paint()**实现实际绘制。
QGraphicsScene基于boundingRect()建立其项索引,QGraphicsView使用它来剔除不可见项,以及确定绘制重叠项时需要重新编译的区域。此外,QGraphicsItem的碰撞检测机制使用boundingRect()提供有效的截止点
QGraphicScene希望所有boundingRect()和shape()项保持不变,除非通知它。如果要以任何方式更改项的几何图形,必须首先调用prepareGeometryChange()以允许QgraphicsScene更新其记账。
图元:arrow 继承父类 QGraphicsLineItem 功能:绘制步骤箭头
图元:DiagramPixmapItem 继承父类 QGraphicsPixmapItem 功能:功能按钮/流程按钮图元
视图:DiagramView 继承父类QGraphicsView
场景:DiagramScene 继承父类 QGraphicsScene 功能:管理场景内图元
实例化功能图元和步骤图元并放在在指定位置
//功能栏
for(int i=0; i<4; i++)
{
setMode(DiagramScene::InsertItem);
DiagramPixmapItem* item = new DiagramPixmapItem(m_itemPix.at(i),m_itemName.at(i),QSize(107,157),true);
QPointF point = QPointF(0+(item->pixmap().width()+27)*i,20);
connect(item,&DiagramPixmapItem::ItemIsPressed,this,&DiagramScene::slot_itemSelect);
addItem(item);
item->setPos(point);
vParentItems.append(item);
}
//步骤栏
for(int i=4; i<7; i++)
{
setMode(DiagramScene::InsertItem);
DiagramPixmapItem* item = new DiagramPixmapItem(m_itemPix.at(i),m_itemName.at(i),QSize(107,157),true);
QPointF point = QPointF(0,(item->pixmap().height()+item->getTextHeight()+27)*(i-2)-77);
connect(item,&DiagramPixmapItem::ItemIsPressed,this,&DiagramScene::slot_itemSelect);
addItem(item);
item->setPos(point);
vParentItems.append(item);
}
设置当前功能
功能包含:
InsertItem 插入步骤图元
InsertLine 插入连接箭头图元
MoveItem 移动图元
DeleteItem 删除图元
enum Mode { InsertItem, InsertLine, MoveItem,DeleteItem };
//设置当前功能
void DiagramScene::setMode(Mode mode)
{
myMode = mode;
}
根据点击功能栏的功能图元执行相应动作
//设置下一次将要添加的步骤
void DiagramScene::slot_itemSelect()
{
currentSelectItem = (DiagramPixmapItem*)sender();
if(currentSelectItem->getMyText() == "连接")
{
setMode(DiagramScene::InsertLine);
}
else if(currentSelectItem->getMyText() == "删除")
{
setMode(DiagramScene::DeleteItem);
}
else if(currentSelectItem->getMyText() == "移动")
{
setMode(DiagramScene::MoveItem);
}
else if(currentSelectItem->getMyText() == "一键排序")
{
qDebug() << "一键排序";
setMode(DiagramScene::MoveItem);
QList<DiagramPixmapItem*> newSortItems; //根据箭头从新排序
DiagramPixmapItem* firstItem = nullptr; //最开始的流程
//找到最开始的流程
for(int i=0; i<vEditItems.size(); i++)
{
if(vEditItems.at(i)->getArrowSize() == 1)
{
Arrow* m_arrow = vEditItems.at(i)->getArrow().at(0);
if(m_arrow->startItem() == vEditItems.at(i))
{
firstItem = vEditItems.at(i);
qDebug() << "开始流程是" << vEditItems.at(i)->getMyText();
}
}
}
if(firstItem != nullptr)
{
SortPixmapItemByArrow(firstItem,&newSortItems); //根据箭头方向按顺序添加item
//将无箭头的item 添加到 newSortItems后面
foreach (DiagramPixmapItem* p, vEditItems) {
if(p->getArrowSize() == 0)
newSortItems.append(p);
}
vEditItems = newSortItems;
}
//根据箭头方向排序
for(int i=0; i<vEditItems.size(); i++)
{
if(i<vPosition.size())
vEditItems.at(i)->setPos(vPosition.at(i));
}
}
else
setMode(DiagramScene::InsertItem);
}
通过鼠标事件完成 图元插入 图元连接 图元删除等功能
void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
//qDebug() << "press pos" << mouseEvent->scenePos();
if (mouseEvent->button() != Qt::LeftButton)
return;
DiagramItem *item;
switch (myMode) {
case InsertItem:{
if(mouseEvent->scenePos().x() < 200)
{
setMode(MoveItem);
qDebug() << "步骤选择区,不能添加步骤";
return QGraphicsScene::mousePressEvent(mouseEvent);
}
DiagramPixmapItem* item = new DiagramPixmapItem(currentSelectItem->getMyPixmapPath(),currentSelectItem->getMyText(),QSize(107,157));
connect(item,&DiagramPixmapItem::ItemIsPressed,this,&DiagramScene::slot_itemSelect);
item->setPos(mouseEvent->scenePos());
addItem(item);
vEditItems.append(item);
if(vEditItems.size() >= 2)
{
line = new QGraphicsLineItem(QLineF(vEditItems.at(vEditItems.size()-2)->scenePos(),
vEditItems.at(vEditItems.size()-1)->scenePos()));
line->setPen(QPen(myLineColor, 2));
}
addItem(line);
autoAddArrow();
//qDebug() << "insert item at: " << mouseEvent->scenePos();
hasItemSelected = itemAt(mouseEvent->scenePos(), QTransform()) != nullptr;
setMode(MoveItem);
clearAllSelectParentItem();
break;
}
case InsertLine:
{
if (itemAt(mouseEvent->scenePos(), QTransform()) == nullptr) break;//绘制线的起始点 没有落在item上则无效
//父类Item 不画线
DiagramPixmapItem *pixItem = qgraphicsitem_cast<DiagramPixmapItem *>(itemAt(mouseEvent->scenePos(), QTransform()));
if(pixItem->isParentFlag())
return QGraphicsScene::mousePressEvent(mouseEvent);
line = new QGraphicsLineItem(QLineF(mouseEvent->scenePos(),
mouseEvent->scenePos()));
line->setPen(QPen(myLineColor, 2));
addItem(line);
break;
}
case DeleteItem:
{
hasItemSelected = itemAt(mouseEvent->scenePos(), QTransform()) != nullptr;
if(hasItemSelected)
{
deleteItems(QList<QGraphicsItem*>() << itemAt(mouseEvent->scenePos(), QTransform()));
}
break;
}
default:
hasItemSelected = itemAt(mouseEvent->scenePos(), QTransform()) != nullptr;
}
QGraphicsScene::mousePressEvent(mouseEvent);
}