代码在 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;
}
后面还有旋转工具,多边形工具等,就不一一贴代码了。