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

//手柄的绘制部分,挺简单,就是画个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());
    }else
        painter->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_OBJECT
    Q_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 );
    //返回控制点的光标	
    virtual Qt::CursorShape getCursor(SizeHandleRect::Direction dir );
    //返回本地坐标
    virtual QRectF  rect() const { return m_localRect;}
    //当释放鼠标完成size操作后,重建本地坐标。 
    virtual void changeSize () {}
    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 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())
{

    // handles
    m_handles.reserve(SizeHandleRect::None);
    for (int i = SizeHandleRect::LeftTop; i <= SizeHandleRect::Left; ++i) {
        SizeHandleRect *shr = new SizeHandleRect(this, static_cast(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()); 
   //还原图形坐标原点 
    setTransform(transform().translate(-delta.x(),-delta.y()));
    qDebug()<<"changeOrigin: "<< delta << mapFromScene( pos()) << mapFromScene(pt2)<
这里多边形重新本地坐标过程有些不一样,需要对每个点的坐标进行重建。
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()<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 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;

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

};
 
  
 
  
 
  
 
  
 //场景类的具体实现 
  
 
  
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);

}


 //选择工具的具体实现 
  
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 items = scene->selectedItems();
    GraphicsItem *item = 0;

    if ( items.count() == 1 )
        item = qgraphicsitem_cast(items.first());

    if ( item != 0 ){

        nDragHandle = item->hitTest(event->scenePos());
        if ( nDragHandle !=SizeHandleRect::None)
             selectMode = size;
        else
            selectMode =  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 items = scene->selectedItems();
    GraphicsItem * item = 0;
    if ( items.count() == 1 ){
        item = qgraphicsitem_cast(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 items = scene->selectedItems();
    if ( items.count() == 1 ){
        GraphicsItem * item = qgraphicsitem_cast(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;
}


 
  
 
  
后面还有旋转工具,多边形工具等,就不一一贴代码了。

 
  



你可能感兴趣的:(我的备忘录,工作笔记)