QGraphicsView 框架学习(一)、图形元素的编辑
代码在 http://download.csdn.net/detail/firebolt2002/8782273
一、给图形对象加控制点,用户通过鼠标来操作控制点来编辑图形,参考MFC drawcli的实现。
很多人通过QGraphicsItem的派生类,然后重载几个函数来处理鼠标消息:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) Q_DECL_OVERRIDE;void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) Q_DECL_OVERRIDE;void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) Q_DECL_OVERRIDE;void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvet) Q_DECL_OVERRIDE;
这种在图元对象内部处理鼠标消息的方法我使用过,感觉不太好用,主要是鼠标消息有时候收不到,操作比方麻烦。所以我现在通过派生一个QGraphicsScene对象,重载它的鼠标处理函数来操作图元对象,这样感觉更合理一些。
先来看看控制点对象,我从QtCreator里抄的:
下面看看图元对象。
//控制手柄的大小
enum {
SELECTION_HANDLE_SIZE = 6, SELECTION_MARGIN = 10 };
enum SelectionHandleState {
SelectionHandleOff, SelectionHandleInactive, SelectionHandleActive };//通过QGraphicsRectItem派生,它本来就是个矩形嘛。
class SizeHandleRect :public QGraphicsRectItem
{
//控制点的操作方向。enum Direction {
None = -1 , LeftTop , Top, RightTop, Right, RightBottom, Bottom, LeftBottom, Left , Extra };SizeHandleRect(QGraphicsItem* parent , Direction d, QGraphicsItem *resizable);Direction dir() const {
return m_dir; }void updateCursor();void setState(SelectionHandleState st);bool hitTest( const QPointF & point );void move(qreal x, qreal y );protected:void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);private:const Direction m_dir;QGraphicsItem *m_resizable;SelectionHandleState m_state;QColor borderColor;
};
<pre name="code" class="cpp">//手柄的绘制部分,挺简单,就是画个6x6的小矩形。
void SizeHandleRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
//不知道为啥,打开反走样后,如果加框,会很难看。painter->setPen(Qt::NoPen);painter->setBrush(QBrush(borderColor));if ( m_dir >= Extend ){
// painter->setBrush(QBrush("lightseagreen"));//如果是额外的控制点,就画圈。painter->drawEllipse(rect());}elsepainter->drawRect(rect());}//父对象状态改变,根据状态决定是否可见。
void SizeHandleRect::setState(SelectionHandleState st)
{
if (st == m_state)return;switch (st) {
case SelectionHandleOff:hide();break;case SelectionHandleInactive:case SelectionHandleActive:show();break;}m_state = st;
}
//检查是否选中。
bool SizeHandleRect::hitTest(const QPointF &point)
{
QPointF pt = mapFromScene(point);bool result = rect().contains(pt);return result;
}
//移动到指定的位置。
void SizeHandleRect::move(qreal x, qreal y)
{
setPos(x,y);
}//使用QGraphicsObject做父类,主要是想利用一下它的元属性,这样可以做一个通用的属性编辑器,后来发现很鸡肋,QPen,QBrush居然不支持,还得自己写,用它就
不划算了。
class GraphicsBasicItem : public QGraphicsObject
{
Q_OBJECTQ_PROPERTY(QColor pen READ penColor WRITE setPen )Q_PROPERTY(QColor brush READ brush WRITE setBrush )
public:explicit GraphicsBasicItem(QGraphicsItem * parent);explicit GraphicsBasicItem(const QString &name ,QGraphicsItem *parent );virtual ~GraphicsBasicItem();QColor brush() const {
return m_brush.color();}QPen pen() const {
return m_pen;}QColor penColor() const {
return m_pen.color();}void setPen(const QPen & pen ) {
m_pen = pen;}void setBrush( const QBrush & brush ) {
m_brush = brush ; }
protected:QBrush m_brush;QPen m_pen ;
};//这个才图元对象。
class GraphicsItem : public GraphicsBasicItem
{
Q_OBJECT
public:GraphicsItem(QGraphicsItem * parent );enum {
Type = UserType+1};int type() const {
return Type; }//返回选中的控制点virtual SizeHandleRect::Direction hitTest( const QPointF & point ) const;//根据控制点当前的位置改变对象的大小。virtual void resizeTo(SizeHandleRect::Direction dir, const QPointF & point );//返回控制点的光标<span style="white-space:pre"> </span>virtual Qt::CursorShape getCursor(SizeHandleRect::Direction dir );//返回本地坐标virtual QRectF rect() const {
return m_localRect;}//当释放鼠标完成size操作后,重建本地坐标。 virtual void changeSize () {
}</span>virtual void move( const QPointF & point ){
}int getHandleCount() const {
return m_handles.count();}
signals:void selectedChange(QGraphicsItem *item);
protected:virtual void updateGeometry();void setState(SelectionHandleState st);void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);QVariant itemChange(GraphicsItemChange change, const QVariant &value);typedef QVector<SizeHandleRect*> Handles;Handles m_handles;QRectF m_localRect;
};
创建手柄的过程,和改变手柄状态的方法。
创建手柄的过程可以在每个具体对象中实现,比如矩形有8个控制点,线有2个控制点,多边形控制点就是实际的点数。
GraphicsRectItem::GraphicsRectItem(const QRect & rect ,QGraphicsItem *parent):GraphicsItem(parent),m_width(rect.width()),m_height(rect.height())
{
// handlesm_handles.reserve(SizeHandleRect::None);for (int i = SizeHandleRect::LeftTop; i <= SizeHandleRect::Left; ++i) {
SizeHandleRect *shr = new SizeHandleRect(this, static_cast<SizeHandleRect::Direction>(i), this);m_handles.push_back(shr);}updateGeometry();setFlag(QGraphicsItem::ItemIsMovable, true);setFlag(QGraphicsItem::ItemIsSelectable, true);setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);//至少需要一个图元对象打开次方法,否则QGraphicsScene不返还鼠标事件.this->setAcceptHoverEvents(true);
}
//移动手柄位置。
void GraphicsItem::updateGeometry()
{
const QRectF &geom = this->boundingRect();const int w = SELECTION_HANDLE_SIZE;const int h = SELECTION_HANDLE_SIZE;const Handles::iterator hend = m_handles.end();for (Handles::iterator it = m_handles.begin(); it != hend; ++it) {
SizeHandleRect *hndl = *it;;switch (hndl->dir()) {
case SizeHandleRect::LeftTop:hndl->move(geom.x() - w / 2, geom.y() - h / 2);break;case SizeHandleRect::Top:hndl->move(geom.x() + geom.width() / 2 - w / 2, geom.y() - h / 2);break;case SizeHandleRect::RightTop:hndl->move(geom.x() + geom.width() - w / 2, geom.y() - h / 2);break;case SizeHandleRect::Right:hndl->move(geom.x() + geom.width() - w / 2, geom.y() + geom.height() / 2 - h / 2);break;case SizeHandleRect::RightBottom:hndl->move(geom.x() + geom.width() - w / 2, geom.y() + geom.height() - h / 2);break;case SizeHandleRect::Bottom:hndl->move(geom.x() + geom.width() / 2 - w / 2, geom.y() + geom.height() - h / 2);break;case SizeHandleRect::LeftBottom:hndl->move(geom.x() - w / 2, geom.y() + geom.height() - h / 2);break;case SizeHandleRect::Left:hndl->move(geom.x() - w / 2, geom.y() + geom.height() / 2 - h / 2);break;default:break;}}
}
这个类里有两个方法比较重要,一个就是resizeTo,另外一个就是changeSize(),resizeTo就是改变对象的大小,为啥还要有个changeSize方法呢。这个问题后面说。
先看看这两个函数的具体实现。
void GraphicsRectItem::resizeTo(SizeHandleRect::Direction dir, const QPointF &point)
{
// 将场景坐标映射为本地坐标。QPointF local = mapFromParent(point);QString dirName;// 临时对象,记录改变后的大小。QRect delta = this->rect().toRect();switch (dir) {
case SizeHandleRect::Right:dirName = "Rigth";delta.setRight(local.x());break;case SizeHandleRect::RightTop:dirName = "RightTop";delta.setTopRight(local.toPoint());break;case SizeHandleRect::RightBottom:dirName = "RightBottom";delta.setBottomRight(local.toPoint());break;case SizeHandleRect::LeftBottom:dirName = "LeftBottom";delta.setBottomLeft(local.toPoint());break;case SizeHandleRect::Bottom:dirName = "Bottom";delta.setBottom(local.y());break;case SizeHandleRect::LeftTop:dirName = "LeftTop";delta.setTopLeft(local.toPoint());break;case SizeHandleRect::Left:dirName = "Left";delta.setLeft(local.x());break;case SizeHandleRect::Top:dirName = "Top";delta.setTop(local.y());break;default:break;}// 改变矩形的大小。prepareGeometryChange();m_width = delta.width(); m_height = delta.height();//改变本地坐标系,这里本地坐标系已经被破坏了,如果进行旋转操作等其他变换后,坐标系会出问题。//还需要调用changeSize来重建本地坐标。m_localRect = delta;updateGeometry();
}
重建本地坐标。为啥要重建本地坐标系呢,前面已经说了,如果改变图元的尺寸,本地坐标系就会跟着改变。本地坐标又是啥样子呢,它是用户自己的坐标系,不管视图坐标,还是场景坐标他们的改变不会影响到本地坐标,通常情况下,我们会将本地坐标定义为原点在中心位置的坐标系,这个坐标比较好操作,也可以定义为原点在左上角位置。如果加入旋转等变换,最好还是把原点放到中心位置。当我们改变对象的大小后,本地坐标原点发生了改变,我们要调整一下坐标,使它的原点重新回到中心位置。
void GraphicsRectItem::changeSize()
{
QPointF pt1,pt2,delta;//获得当前原点的场景坐标pt1 = mapToScene(transformOriginPoint());//获得改变后坐标系的中心位置,的场景坐标。pt2 = mapToScene(boundingRect().center());//计算位移delta = pt1 - pt2;prepareGeometryChange();//重建本地坐标系,使原点处于中心位置。m_localRect = QRectF(-m_width/2,-m_height/2,m_width,m_height);//移动图元对象的坐标原点。setTransform(transform().translate(delta.x(),delta.y()));//将当前图形原点移动到新的坐标系中心位置。setTransformOriginPoint(boundingRect().center());//将图形的pos移动到原点上。moveBy(-delta.x(),-delta.y());<span style="font-family: Arial, Helvetica, sans-serif;"> </span>//还原图形坐标原点 setTransform(transform().translate(-delta.x(),-delta.y()));qDebug()<<"changeOrigin: "<< delta << mapFromScene( pos()) << mapFromScene(pt2)<<m_localRect;updateGeometry();}
这里多边形重新本地坐标过程有些不一样,需要对每个点的坐标进行重建。
void GraphicsPolygonItem::changeSize()
{
QPointF pt1,pt2,delta;//把原来的点映射到场景坐标QPolygonF pts = mapToScene(m_points);pt1 = mapToScene(transformOriginPoint());pt2 = mapToScene(boundingRect().center());delta = pt1 - pt2;//对每个点进行移动,重建坐标系for (int i = 0; i < pts.count() ; ++i )pts[i]+=delta;//还原本地坐标m_points = mapFromScene(pts);setTransform(transform().translate(delta.x(),delta.y()));setTransformOriginPoint(boundingRect().center());moveBy(-delta.x(),-delta.y());setTransform(transform().translate(-delta.x(),-delta.y()));prepareGeometryChange();qDebug()<<"changeOrigin: "<< delta << mapFromScene( pos()) << transformOriginPoint()<<boundingRect();updateGeometry();}
//场景类,这里传递鼠标消息给绘图工具。
class DrawScene : public QGraphicsScene
{
Q_OBJECT
public:explicit DrawScene(QObject *parent = 0);void setView(QGraphicsView * view ) {
m_view = view ; }<pre name="code" class="cpp">class DrawTool
{
public:DrawTool( DrawShape shape );virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event ,DrawScene *scene );DrawShape m_drawShape;bool m_hoverSizer;static DrawTool * findTool( DrawShape drawShape );static QList<DrawTool*> c_tools;static QPointF c_down;static quint32 c_nDownFlags;static QPointF c_last;static DrawShape c_drawShape;
};//绘图工具类,参考drawcli
class SelectTool : public DrawTool
{
public:SelectTool();virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );QSizeF m_lastSize;QPointF initialPositions;QGraphicsPathItem * dashRect;
};class RotationTool : public DrawTool
{
public:RotationTool();virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );qreal lastAngle;QGraphicsPathItem * dashRect;
};class RectTool : public DrawTool
{
public:RectTool(DrawShape drawShape);virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );GraphicsItem * item;
};class PolygonTool : public DrawTool
{
public:PolygonTool(DrawShape shape );virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event ,DrawScene *scene );GraphicsPolygonItem * item;QPointF initialPositions;};
<pre name="code" class="cpp"><pre name="code" class="cpp">class DrawScene : public QGraphicsScene
{
Q_OBJECT
public:explicit DrawScene(QObject *parent = 0);void setView(QGraphicsView * view ) {
m_view = view ; }QGraphicsView * view() {
return m_view; };void mouseEvent(QGraphicsSceneMouseEvent *mouseEvent );signals:void itemSelected(QGraphicsItem *item);protected:void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) Q_DECL_OVERRIDE;void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) Q_DECL_OVERRIDE;void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) Q_DECL_OVERRIDE;void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvet) Q_DECL_OVERRIDE;QGraphicsView * m_view;};
//场景类的具体实现
<pre name="code" class="cpp">DrawScene::DrawScene(QObject *parent):QGraphicsScene(parent)
{
m_view = NULL;}
//有些消息需要继续传递给QGraphicsScene,可以通过这个函数传回来。
void DrawScene::mouseEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
switch( mouseEvent->type() ){
case QEvent::GraphicsSceneMousePress:QGraphicsScene::mousePressEvent(mouseEvent);break;case QEvent::GraphicsSceneMouseMove:QGraphicsScene::mouseMoveEvent(mouseEvent);break;case QEvent::GraphicsSceneMouseRelease:QGraphicsScene::mouseReleaseEvent(mouseEvent);break;}
}void DrawScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
DrawTool * tool = DrawTool::findTool( DrawTool::c_drawShape );if ( tool )tool->mousePressEvent(mouseEvent,this);
}void DrawScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
DrawTool * tool = DrawTool::findTool( DrawTool::c_drawShape );if ( tool )tool->mouseMoveEvent(mouseEvent,this);
}void DrawScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
DrawTool * tool = DrawTool::findTool( DrawTool::c_drawShape );if ( tool )tool->mouseReleaseEvent(mouseEvent,this);
}void DrawScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvet)
{
DrawTool * tool = DrawTool::findTool( DrawTool::c_drawShape );if ( tool )tool->mouseDoubleClickEvent(mouseEvet,this);}
//选择工具的具体实现
<pre name="code" class="cpp">SelectTool::SelectTool():DrawTool(selection)
{
m_lastSize.setHeight(0);m_lastSize.setWidth(0);dashRect = 0;
}void SelectTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
DrawTool::mousePressEvent(event,scene);QPointF itemPoint;if (!m_hoverSizer)scene->mouseEvent(event);selectMode = none;QList<QGraphicsItem *> items = scene->selectedItems();GraphicsItem *item = 0;if ( items.count() == 1 )item = qgraphicsitem_cast<GraphicsItem*>(items.first());if ( item != 0 ){
nDragHandle = item->hitTest(event->scenePos());if ( nDragHandle !=SizeHandleRect::None)selectMode = size;elseselectMode = move;m_lastSize = item->boundingRect().size();itemPoint = item->mapFromScene(c_down);setCursor(scene,Qt::ClosedHandCursor);}if( selectMode == none ){
selectMode = netSelect;if ( scene->view() ){
QGraphicsView * view = scene->view();view->setDragMode(QGraphicsView::RubberBandDrag);}}if ( selectMode == move && items.count() == 1 ){
if (dashRect ){
scene->removeItem(dashRect);delete dashRect;dashRect = 0;}dashRect = new QGraphicsPathItem(item->shape());dashRect->setPen(Qt::DashLine);dashRect->setPos(item->pos());dashRect->setTransformOriginPoint(item->transformOriginPoint());dashRect->setTransform(item->transform());dashRect->setRotation(item->rotation());dashRect->setScale(item->scale());initialPositions = dashRect->pos();scene->addItem(dashRect);}
}void SelectTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
DrawTool::mouseMoveEvent(event,scene);QList<QGraphicsItem *> items = scene->selectedItems();GraphicsItem * item = 0;if ( items.count() == 1 ){
item = qgraphicsitem_cast<GraphicsItem*>(items.first());if ( item != 0 ){
if ( nDragHandle != SizeHandleRect::None && selectMode == size ){
QSizeF delta(c_last.x() - c_down.x() , c_last.y() - c_down.y());item->resizeTo(nDragHandle,c_last);}else if(nDragHandle == SizeHandleRect::None && selectMode == selection ){
SizeHandleRect::Direction handle = item->hitTest(event->scenePos());if ( handle != SizeHandleRect::None){
setCursor(scene,Qt::OpenHandCursor/*item->getCursor(handle)*/);m_hoverSizer = true;}else{
setCursor(scene,Qt::ArrowCursor);m_hoverSizer = false;}}}}if ( selectMode == move ){
if ( dashRect ){
dashRect->setPos(initialPositions + c_last - c_down);}}if ( selectMode != size && items.count() > 1){
scene->mouseEvent(event);}}void SelectTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
DrawTool::mouseReleaseEvent(event,scene);QList<QGraphicsItem *> items = scene->selectedItems();if ( items.count() == 1 ){
GraphicsItem * item = qgraphicsitem_cast<GraphicsItem*>(items.first());if ( item != 0 && selectMode == move && c_last != c_down ){
item->setPos(initialPositions + c_last - c_down);qDebug()<<"move to :" << item->mapFromScene(item->pos());}else if ( item !=0 && selectMode == size && c_last != c_down ){
item->changeSize();}}if (selectMode == netSelect ){
if ( scene->view() ){
QGraphicsView * view = scene->view();view->setDragMode(QGraphicsView::NoDrag);}}if (dashRect ){
scene->removeItem(dashRect);delete dashRect;dashRect = 0;}selectMode = none;nDragHandle = SizeHandleRect::None;m_hoverSizer = false;scene->mouseEvent(event);}
创建矩形工具
RectTool::RectTool(DrawShape drawShape):DrawTool(drawShape)
{
item = 0;
}void RectTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
DrawTool::mousePressEvent(event,scene);scene->clearSelection();switch ( c_drawShape ){
case rectangle:item = new GraphicsRectItem(QRect(0,0,0,0),NULL);break;case roundrect:item = new GraphicsRoundRectItem(QRect(0,0,0,0),NULL);break;case ellipse:item = new GraphicsEllipseItem(QRect(0,0,0,0),NULL);break;case line:item = new GraphicsLineItem(0);break;case arc:item = new GraphicsArcItem(0);break;}if ( item == 0) return;item->setPos(event->scenePos());scene->addItem(item);scene->connect(item, SIGNAL(selectedChange(QGraphicsItem*)),scene, SIGNAL(itemSelected(QGraphicsItem*)));item->setSelected(true);//设置当前状态为改变大小selectMode = size;//设置当前控制点为右下角。nDragHandle = SizeHandleRect::RightBottom;}void RectTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
setCursor(scene,Qt::CrossCursor);//传递消息给选择工具selectTool.mouseMoveEvent(event,scene);
}void RectTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
if ( event->scenePos() == c_down ){
if ( item != 0){
item->setSelected(false);scene->removeItem(item);delete item ;item = 0;}selectTool.mousePressEvent(event,scene);qDebug()<<"RectTool removeItem:";}selectTool.mouseReleaseEvent(event,scene);c_drawShape = selection;
}
后面还有旋转工具,多边形工具等,就不一一贴代码了。