这一次将介绍如何使用Graphics View来实现前面所说的画板。前面说了很多有关Graphics View的好话,但是没有具体的实例很难说究竟好在哪里。现在我们就把前面的内容使用Graphics View重新实现一下,大家可以对比一下看有什么区别。
 
同前面相似的内容就不再叙述了,我们从上次代码的基础上进行修改,以便符合我们的需要。首先来看MainWindow的代码:
 
mainwindow.cpp
InBlock.gif#include  "mainwindow.h" 
InBlock.gif 
InBlock.gifMainWindow::MainWindow(QWidget *parent) 
InBlock.gif        : QMainWindow(parent) 
InBlock.gif
InBlock.gif        QToolBar *bar =  this->addToolBar( "Tools"); 
InBlock.gif        QActionGroup *group =  new QActionGroup(bar); 
InBlock.gif 
InBlock.gif        QAction *drawLineAction =  new QAction( "Line", bar); 
InBlock.gif        drawLineAction->setIcon(QIcon( ":/line.png")); 
InBlock.gif        drawLineAction->setToolTip(tr( "Draw a line.")); 
InBlock.gif        drawLineAction->setStatusTip(tr( "Draw a line.")); 
InBlock.gif        drawLineAction->setCheckable( true); 
InBlock.gif        drawLineAction->setChecked( true); 
InBlock.gif        group->addAction(drawLineAction); 
InBlock.gif 
InBlock.gif        bar->addAction(drawLineAction); 
InBlock.gif        QAction *drawRectAction =  new QAction( "Rectangle", bar); 
InBlock.gif        drawRectAction->setIcon(QIcon( ":/rect.png")); 
InBlock.gif        drawRectAction->setToolTip(tr( "Draw a rectangle.")); 
InBlock.gif        drawRectAction->setStatusTip(tr( "Draw a rectangle.")); 
InBlock.gif        drawRectAction->setCheckable( true); 
InBlock.gif        group->addAction(drawRectAction); 
InBlock.gif        bar->addAction(drawRectAction); 
InBlock.gif 
InBlock.gif        QLabel *statusMsg =  new QLabel; 
InBlock.gif        statusBar()->addWidget(statusMsg); 
InBlock.gif 
InBlock.gif        PaintWidget *paintWidget =  new PaintWidget( this); 
InBlock.gif        QGraphicsView *view =  new QGraphicsView(paintWidget,  this); 
InBlock.gif        setCentralWidget(view); 
InBlock.gif 
InBlock.gif        connect(drawLineAction, SIGNAL(triggered()), 
InBlock.gif                         this, SLOT(drawLineActionTriggered())); 
InBlock.gif        connect(drawRectAction, SIGNAL(triggered()), 
InBlock.gif                         this, SLOT(drawRectActionTriggered())); 
InBlock.gif        connect( this, SIGNAL(changeCurrentShape(Shape::Code)), 
InBlock.gif                        paintWidget, SLOT(setCurrentShape(Shape::Code))); 
InBlock.gif
InBlock.gif 
InBlock.gif void MainWindow::drawLineActionTriggered() 
InBlock.gif
InBlock.gif        emit changeCurrentShape(Shape::Line); 
InBlock.gif
InBlock.gif 
InBlock.gif void MainWindow::drawRectActionTriggered() 
InBlock.gif
InBlock.gif        emit changeCurrentShape(Shape::Rect); 
InBlock.gif
 
由于mainwindow.h的代码与前文相同,这里就不再贴出。而cpp文件里面只有少数几行与前文不同。由于我们使用Graphics View,所以,我们必须把item添加到QGprahicsScene里面。这里,我们创建了scene的对象,而scene对象需要通过view进行观察,因此,我们需要再使用一个QGraphcisView对象,并且把这个view添加到MainWindow里面。
 
我们把PaintWidget当做一个scene,因此PaintWidget现在是继承QGraphicsScene,而不是前面的QWidget。
 
paintwidget.h
InBlock.gif#ifndef PAINTWIDGET_H 
InBlock.gif#define PAINTWIDGET_H 
InBlock.gif 
InBlock.gif#include <QtGui> 
InBlock.gif#include <QDebug> 
InBlock.gif 
InBlock.gif#include  "shape.h" 
InBlock.gif#include  "line.h" 
InBlock.gif#include  "rect.h" 
InBlock.gif 
InBlock.gif class PaintWidget :  public QGraphicsScene 
InBlock.gif
InBlock.gif        Q_OBJECT 
InBlock.gif 
InBlock.gif public
InBlock.gif        PaintWidget(QWidget *parent = 0); 
InBlock.gif 
InBlock.gif public slots: 
InBlock.gif         void setCurrentShape(Shape::Code s) 
InBlock.gif        { 
InBlock.gif                 if(s != currShapeCode) { 
InBlock.gif                        currShapeCode = s; 
InBlock.gif                } 
InBlock.gif        } 
InBlock.gif 
InBlock.gif protected
InBlock.gif         void mousePressEvent(QGraphicsSceneMouseEvent * event);
InBlock.gif         void mouseMoveEvent(QGraphicsSceneMouseEvent * event);
InBlock.gif         void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);
InBlock.gif 
InBlock.gif private
InBlock.gif        Shape::Code currShapeCode; 
InBlock.gif        Shape *currItem; 
InBlock.gif         bool perm; 
InBlock.gif}; 
InBlock.gif 
InBlock.gif#endif  // PAINTWIDGET_H
 
paintwidget.cpp
InBlock.gif#include  "paintwidget.h" 
InBlock.gif 
InBlock.gifPaintWidget::PaintWidget(QWidget *parent) 
InBlock.gif        : QGraphicsScene(parent), currShapeCode(Shape::Line), currItem(NULL), perm( false
InBlock.gif
InBlock.gif 
InBlock.gif
InBlock.gif 
InBlock.gif void PaintWidget::mousePressEvent(QGraphicsSceneMouseEvent * event
InBlock.gif
InBlock.gif         switch(currShapeCode) 
InBlock.gif        { 
InBlock.gif         case Shape::Line: 
InBlock.gif                { 
InBlock.gif                        Line *line =  new Line; 
InBlock.gif                        currItem = line; 
InBlock.gif                        addItem(line); 
InBlock.gif                         break
InBlock.gif                } 
InBlock.gif         case Shape::Rect: 
InBlock.gif                { 
InBlock.gif                        Rect *rect =  new Rect; 
InBlock.gif                        currItem = rect; 
InBlock.gif                        addItem(rect); 
InBlock.gif                         break
InBlock.gif                } 
InBlock.gif        } 
InBlock.gif         if(currItem) { 
InBlock.gif                currItem->startDraw( event); 
InBlock.gif                perm =  false
InBlock.gif        } 
InBlock.gif        QGraphicsScene::mousePressEvent( event); 
InBlock.gif
InBlock.gif 
InBlock.gif void PaintWidget::mouseMoveEvent(QGraphicsSceneMouseEvent * event
InBlock.gif
InBlock.gif         if(currItem && !perm) { 
InBlock.gif                currItem->drawing( event); 
InBlock.gif        } 
InBlock.gif        QGraphicsScene::mouseMoveEvent( event); 
InBlock.gif
InBlock.gif 
InBlock.gif void PaintWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent * event
InBlock.gif
InBlock.gif        perm =  true
InBlock.gif        QGraphicsScene::mouseReleaseEvent( event); 
InBlock.gif}
 
我们把继承自QWidget改成继承自QGraphicsScene,同样也会有鼠标事件,只不过在这里我们把鼠标事件全部转发给具体的item进行处理。这个我们会在下面的代码中看到。另外一点是,每一个鼠标处理函数都包含了调用其父类函数的语句。
 
shape.h
InBlock.gif#ifndef SHAPE_H 
InBlock.gif#define SHAPE_H 
InBlock.gif 
InBlock.gif#include <QtGui> 
InBlock.gif 
InBlock.gif class Shape 
InBlock.gif
InBlock.gif public
InBlock.gif 
InBlock.gif         enum Code { 
InBlock.gif                Line, 
InBlock.gif                Rect 
InBlock.gif        }; 
InBlock.gif 
InBlock.gif        Shape(); 
InBlock.gif 
InBlock.gif         virtual  void startDraw(QGraphicsSceneMouseEvent *  event) = 0; 
InBlock.gif         virtual  void drawing(QGraphicsSceneMouseEvent *  event) = 0; 
InBlock.gif}; 
InBlock.gif 
InBlock.gif#endif  // SHAPE_H
 
shape.cpp
InBlock.gif#include  "shape.h" 
InBlock.gif 
InBlock.gifShape::Shape() 
InBlock.gif
InBlock.gif
 
Shape类也有了变化:还记得我们曾经说过,Qt内置了很多item,因此我们不必全部重写这个item。所以,我们要使用Qt提供的类,就不需要在我们的类里面添加新的数据成员了。这样,我们就有了不带有额外的数据成员的Shape。那么,为什么还要提供Shape呢?因为我们在scene的鼠标事件中需要修改这些数据成员,如果没有这个父类,我们就需要按照Code写一个长长的switch来判断是那一个图形,这样是很麻烦的。所以我们依然创建了一个公共的父类,只要调用这个父类的draw函数即可。
 
line.h
InBlock.gif#ifndef LINE_H 
InBlock.gif#define LINE_H 
InBlock.gif 
InBlock.gif#include <QGraphicsLineItem> 
InBlock.gif#include  "shape.h" 
InBlock.gif 
InBlock.gif class Line :  public Shape,  public QGraphicsLineItem 
InBlock.gif
InBlock.gif public
InBlock.gif        Line(); 
InBlock.gif 
InBlock.gif         void startDraw(QGraphicsSceneMouseEvent *  event); 
InBlock.gif         void drawing(QGraphicsSceneMouseEvent *  event); 
InBlock.gif}; 
InBlock.gif 
InBlock.gif#endif  // LINE_H
 
line.cpp
InBlock.gif#include  "line.h" 
InBlock.gif 
InBlock.gifLine::Line() 
InBlock.gif
InBlock.gif
InBlock.gif 
InBlock.gif void Line::startDraw(QGraphicsSceneMouseEvent *  event
InBlock.gif
InBlock.gif        setLine(QLineF( event->scenePos(),  event->scenePos())); 
InBlock.gif
InBlock.gif 
InBlock.gif void Line::drawing(QGraphicsSceneMouseEvent *  event
InBlock.gif
InBlock.gif        QLineF newLine(line().p1(),  event->scenePos()); 
InBlock.gif        setLine(newLine); 
InBlock.gif
 
Line类已经和前面有了变化,我们不仅仅继承了Shape,而且继承了QGraphicsLineItem类。这里我们使用了C++的多继承机制。这个机制是很危险的,很容易发生错误,但是这里我们的Shape并没有继承其他的类,只要函数没有重名,一般而言是没有问题的。如果不希望出现不推荐的多继承(不管怎么说,多继承虽然危险,但它是符合面向对象理论的),那就就想办法使用组合机制。我们之所以使用多继承,目的是让Line类同时具有Shape和QGraphicsLineItem的性质,从而既可以直接添加到QGraphicsScene中,又可以调用startDraw()等函数。
 
同样的还有Rect这个类:
 
rect.h
InBlock.gif#ifndef RECT_H 
InBlock.gif#define RECT_H 
InBlock.gif 
InBlock.gif#include <QGraphicsRectItem> 
InBlock.gif#include  "shape.h" 
InBlock.gif 
InBlock.gif class Rect :  public Shape,  public QGraphicsRectItem 
InBlock.gif
InBlock.gif public
InBlock.gif        Rect(); 
InBlock.gif 
InBlock.gif         void startDraw(QGraphicsSceneMouseEvent *  event); 
InBlock.gif         void drawing(QGraphicsSceneMouseEvent *  event); 
InBlock.gif}; 
InBlock.gif 
InBlock.gif#endif  // RECT_H
 
rect.cpp
InBlock.gif#include  "rect.h" 
InBlock.gif 
InBlock.gifRect::Rect() 
InBlock.gif
InBlock.gif
InBlock.gif 
InBlock.gif void Rect::startDraw(QGraphicsSceneMouseEvent *  event
InBlock.gif
InBlock.gif        setRect(QRectF( event->scenePos(), QSizeF(0, 0))); 
InBlock.gif
InBlock.gif 
InBlock.gif void Rect::drawing(QGraphicsSceneMouseEvent *  event
InBlock.gif
InBlock.gif        QRectF r(rect().topLeft(), 
InBlock.gif                         QSizeF( event->scenePos().x() - rect().topLeft().x(),  event->scenePos().y() - rect().topLeft().y())); 
InBlock.gif        setRect(r); 
InBlock.gif
 
Line和Rect类的逻辑都比较清楚,和前面的基本类似。所不同的是,Qt并没有使用我们前面定义的两个Qpoint对象记录数据,而是在QGraphicsLineItem中使用QLineF,在QGraphicsRectItem中使用QRectF记录数据。这显然比我们的两个点的数据记录高级得多。其实,我们也完全可以使用这样的数据结构去重定义前面那些Line之类。
 
这样,我们的程序就修改完毕了。运行一下你会发现,几乎和前面的实现没有区别。这里说“几乎”,是在第一个点画下的时候,scene会移动一段距离。这是因为scene是自动居中的,由于我们把Line的第一个点设置为(0, 0),因此当我们把鼠标移动后会有一个偏移。
 
看到这里或许并没有显示出Graphics View的优势。不过,建议在Line或者Rect的构造函数里面加上下面的语句,
 
InBlock.gifsetFlag(QGraphicsItem::ItemIsMovable,  true); 
InBlock.gifsetFlag(QGraphicsItem::ItemIsSelectable,  true);
 
此时,你的Line和Rect就已经支持选中和拖放了!值得试一试哦!不过,需要注意的是,我们重写了scene的鼠标控制函数,所以这里的拖动会很粗糙,甚至说是不正确,你需要动动脑筋重新设计我们的类啦!