前言:
我们需要先由一个 QGraphicsView,这个是UI显示的地方,也就是装满可见原色的Scene,然后需要一个QGraphicsScene 用来管理所有可见的界面元素,要实现UI功能,我们需要用各种从QGraphicsItem拼装成UI控件,并赋予他控件的逻辑。
简单的说,QGraphicsView代表视图,QGraphicsScene 代表场景,QGraphicsItem代表图形项,QGraphicsItem放在QGraphicsScene 中,QGraphicsScene 放在QGraphicsView中。
1、坐标关系
这是QGraphicsView,始终是以左上角为(0, 0)点,单位为像素
创建场景QGraphicsScene ,并放入视图中:
QGraphicsScene* _scene = new QGraphicsScene(); ui.graphicsView->setScene(_scene);
注意:如果设置scene的坐标和宽高则不能放在构造里,不然不起效果,设置图形项宽高和坐标也不能在构造设置。
scene的坐标怎么看?
先忽略坐标看scene宽高,如果scene宽高小于view宽高,scene默认放到view正中央,
如果scene宽高大于view宽高,view视窗自动出现滚轮,保证view宽高和scene宽高一致
如果scene宽高没有设置,scene中没放图形项前宽高为0,放入图形项后scene宽高会和图形项宽高保持一致且scene宽高和左上角坐标会随着图像同步变化,scene就等同于图像,scene宽高没有设置,scene默认左上角坐标为(0, 0),原点也在左上角。scene宽高没有设置,图像左上角坐标始终与scene左上角坐标重合。
然后看scene的(x, y)坐标,(x, y)坐标为scene左上角坐标,有点特别要注意,不管scene左上角坐标如何变化,scene整体始终处于view正中央。
如果scene的(x, y)为(0, 0),scene的原点也为(0, 0)
如果scene的(x, y)为(-scene->width() / 2, -scene->height() / 2),scene的原点在view正中央。
图像坐标系也是一个独立的坐标系,图像左上角默认为(0, 0)点,把图像放入scene,图像左上角坐标默认在scene的原点,设置图像左上角坐标
pixmapItem->setPos(-scene->width() / 2, -scene->height() / 2);
这样图像左上角和scene左上角重合。
下面草图中继续说明三者坐标关系:
2、view,scene和item三者坐标转换
上图中,图片和矩形都放在QGraphicsItem里,红点为图形项零点(0, 0),因为一旦有了QGraphicsItem,scene会和item重合,所以红点处也为scene零点坐标(0, 0),但区别在于红点往左边一点,sence依然有坐标比如(-1, 0),但item直接返回空,无法计算item坐标,view视图坐标一直都是view显示框左上角为零点。
通过鼠标事件可以拿到视图view的坐标,比如触发mousePressEvent事件:
视图view坐标:
QPoint nViewPoint = event->pos();
通过视图坐标转场景scene坐标:
QPointF fScenePoint = this->mapToScene(nViewPoint);
通过场景坐标转图形项item坐标:
QGraphicsItem* pItem = this->scene()->itemAt(fScenePoint, QTransform()); //图形项指针 if (NULL != pItem) { QPointF fItemPoint = pItem->mapFromScene(fScenePoint); //获取图形项坐标 }
注意:鼠标在不同的item上比如图片,画的多边形点击一下不仅可以拿到该图形项Item坐标,而且通过pItem拿到对应的item图形项指针!!!!!
获取场景scene中所有Item图形项指针:
QList<QGraphicsItem*> allItems = scene->items();
每个图形项都有一个type标识,可以根据这个标识判断该图形项是图片,矩形,圆等等:
if(allItems[0]->type() == QGraphicsLineItem::Type) //直线 else if(allItems[0]->type() == QGraphicsRectItem::Type) // 矩形 else if(allItems[0]->type() == QGraphicsSimpleTextItem::Type) // 文本 //......
3、在场景scene中绘制不同图形项
有时候我们会遇到这样一个问题:
当我们重新继承一个图形项的类比如QGraphicsRectItem,在自定义类中添加更多的关于矩形的功能,创建矩形对象时却一直画不出矩形?
需要把自定义类强转为QGraphicsRectItem类在设置绘制参数即可画出矩形,如下所示。
通过如上方式画矩形,QGraphicsRectItem会调用自己的paint事件画出矩形,当drawRect继承QGraphicsRectItem,同时又重写了paint方法,假如在drawRect的paint事件里直接return,用上面方法画矩形后会调用drawRect的paint方法然后直接return,当然无法画出矩形。如果重写paint方法又想在重写的paint方法中调用QGraphicsRectItem的paint方法,可以在paint方法中加上
QGraphicsRectItem::paint(painter, option, widget);
这样可以在return之前调用父类的绘画方法画出矩形。
如果在drawRect类中重写paint方法,当我实现了用鼠标画矩形的功能,画出来的矩形用鼠标在矩形上点一下却拿不到矩形的item指针(如下)
原因在于drawRect继承了QGraphicsRectItem,想要显示矩形并能拿到item指针,需要在paint中把drawRect强转为QGraphicsRectItem指针,再把更新的矩形数据QRect设置到QGraphicsRectItem中,那么paint中最后面应该加上如下代码:
另一种原因看不到绘制的图形项:
这里先创建一个矩形设置到sence中,然后再把图片设置到sence,运行后只看到图片看不到画的矩形原因在于sence中的items会根据additem的顺序展示出来,运行程序后先展示矩形,然后展示图片,但图片会覆盖矩形,所以只能看到图片,并不是矩形没有了,如果先additem图片在additem矩形,这样先展示图片,然后会在图片上画出矩形,这样可以正确显示。
4、其他一些补充:
(1)界面变化触发resizeEvent事件,想要图像实时在scene正中央需要在resizeEvent中设置:
m_Scene->setSceneRect(m_PixmapItem->boundingRect());
(2)如果在构造中创建矩形或图片并显示(图片显示m_PixmapItem->setPixmap(pixmap);),运行程序后会显示在view的左上角,这时sence和item的(0,0)点在view的左上角。
如果先创建一个矩形不设置坐标和宽高先创建出来additem到sence,然后等界面运行后通过点击按钮item->setRect(0,0,100,100),这样矩形会显示在view正中间。
(3)对一个item图元可设置焦点,设置可选中,设置可移动
item->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable );
(4)如果图片很大直接用QPixmap加载有可能在界面上显示不出来,可以先把图片加载成QImage类型,然后再把QImage转为QPixmap即可显示。或者设置图片缓存为0,QPixmapCache::setCacheLimit(0)这个方法有待验证。
(5)图形项的各种事件
void mousePressEvent(QGraphicsSceneMouseEvent* event) void mouseMoveEvent(QGraphicsSceneMouseEvent* event); void hoverEnterEvent(QGraphicsSceneHoverEvent* event); //鼠标移入图形项 void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); //鼠标移出图形项 void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); //右击菜单事件
需要注意的是图形项中的鼠标移动事件mouseMoveEvent必须鼠标在图形项上按下移动才能触发,这里没有setMouseTrack设置。使用鼠标在图形项上移入移出事件,需要在构造中加入setAcceptHoverEvents(true);
还有一点需要注意:想要图形项中的事件有相应,必须在继承GraphicsView类中写好对应的事件并在事件中调用父事件使事件透传到item,比如在界面画了一个矩形,想要在矩形上右键进入contextMenuEvent事件,必须在继承的GraphicsView类中重写contextMenuEvent事件,并在事件中调用父类事件QGraphicsView::contextMenuEvent(event);