QT编程入门系列文章之三十二——一个简易画板的实现(Graphics View)

在这里插入图片描述
这一次将介绍如何使用Graphics View 来实现前面所说的画板。前面说了很多有关Graphics View的好话,但是没有具体的实例很难说究竟好在哪里。现在我们就把前面的内容使用Graphics View重新实现一下,大家可以对比一下看有什么区别。
  同前面相似的内容就不再叙述了,我们从上次代码的基础上进行修改,以便符合我们的需要。首先来看MainWindow的代码:

mainwindow.cpp
	#include  "mainwindow.h"
	MainWindow::MainWindow(QWidget *parent):QMainWindow(parent)
	{
  	QToolBar *bar =  this->addToolBar("Tools");
    QActionGroup *group = new  QActionGroup(bar);
    QAction *drawLineAction = new  QAction("Line", bar);
    drawLineAction->setIcon(QIcon(":/line.png"));
    drawLineAction->setToolTip(tr("Draw a line."));
    drawLineAction->setStatusTip(tr("Draw a line."));
    drawLineAction->setCheckable(true);
    drawLineAction->setChecked(true);
    group->addAction(drawLineAction);
    bar->addAction(drawLineAction);
    QAction *drawRectAction =  new QAction("Rectangle", bar);
    drawRectAction->setIcon(QIcon(":/rect.png"));
    drawRectAction->setToolTip(tr("Draw a rectangle."));
    drawRectAction->setStatusTip(tr("Draw a rectangle."));
    drawRectAction->setCheckable(true);
    group->addAction(drawRectAction);
    bar->addAction(drawRectAction);
    QLabel *statusMsg = new  QLabel;
    statusBar()->addWidget(statusMsg);
    PaintWidget *paintWidget = new PaintWidget(this);
    QGraphicsView *view = new QGraphicsView(paintWidget,this);
    setCentralWidget(view);
    connect(drawLineAction, SIGNAL(triggered()), this, SLOT(drawLineActionTriggered()));
    connect(drawRectAction,  SIGNAL(triggered()), this, SLOT(drawRectActionTriggered()));
    connect(this,  SIGNAL(changeCurrentShape(Shape::Code)), paintWidget, SLOT(setCurrentShape(Shape::Code)));
	}
	void  MainWindow::drawLineActionTriggered()
	{
    emit changeCurrentShape(Shape::Line);
	}
	void  MainWindow::drawRectActionTriggered()
	{
    emit  changeCurrentShape(Shape::Rect);
	}

由于mainwindow.h的代码与前文相同,这里就不再贴出。而cpp文件里面只有少数几行与前文不同。由于我们使用 Graphics View,所以,我们必须把item添加到QGprahicsScene里面。这里,我们创建了scene的对象,而scene对象需要通过view进行 观察,因此,我
们需要再使用一个QGraphcisView对象,并且把这个view添加到MainWindow里面。
  我们把 PaintWidget当做一个scene,因此PaintWidget现在是继承QGraphicsScene,而不是前面的QWidget。

paintwidget.h
	#ifndef PAINTWIDGET_H
	#define  PAINTWIDGET_H
	#include <QtGui>
	#include <QDebug>
	#include  "shape.h"
	#include "line.h"
	#include "rect.h"
	class  PaintWidget : public QGraphicsScene
	{
    Q_OBJECT
		public:
     PaintWidget(QWidget *parent = 0);
		public slots:
      void setCurrentShape(Shape::Code s)
      {
        if(s  != currShapeCode) {
          currShapeCode = s;
         }
      }
		protected:
      void  mousePressEvent(QGraphicsSceneMouseEvent *event);
      void  mouseMoveEvent(QGraphicsSceneMouseEvent *event);
      void  mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
		private:
       Shape::Code currShapeCode;
      Shape *currItem;
      bool perm;
	};
	#endif // PAINTWIDGET_H
  
	paintwidget.cpp
	#include "paintwidget.h"
	PaintWidget::PaintWidget(QWidget  *parent):QGraphicsScene(parent),currShapeCode(Shape::Line),currItem(NULL), perm(false){
	}
	void PaintWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)
	{
     switch(currShapeCode)
    {
    	 case Shape::Line:
       {
         Line *line = new Line;
          currItem = line;
         addItem(line);
         break;
       }
        case Shape::Rect:
       {
         Rect *rect = new Rect;
         currItem = rect;
          addItem(rect);
         break;
        }
     }
     if(currItem) {
       currItem->startDraw(event);
       perm = false;
     }
     QGraphicsScene::mousePressEvent(event);
	}
	void  PaintWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
	{
     if(currItem && !perm) {
     currItem->drawing(event);
  }
     QGraphicsScene::mouseMoveEvent(event);
	}
	void  PaintWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
	{
     perm = true;
     QGraphicsScene::mouseReleaseEvent(event);
	}

我们把继承自QWidget改成继承自QGraphicsScene,同样也会有鼠标事件,只不过在这里我们把鼠标事件全部转发给具体的 item进行处理。这个我们会在下面的代码中看到。另外一点是,每一个鼠标处理函数都包含了调用其父类函数的语句。

shape.h
	#ifndef  SHAPE_H
	#define SHAPE_H
	#include <QtGui>
	class Shape
	{
		public:
      enum Code {
       Line,
       Rect
      };
    Shape();
    virtual void startDraw(QGraphicsSceneMouseEvent * event)=0;
    virtual void drawing(QGraphicsSceneMouseEvent * event)=0;
	};
	#endif //  SHAPE_H
 
	shape.cpp
	#include "shape.h"
	Shape::Shape()
	{
	}

Shape类也有了变化:还记得我们曾经说过,Qt内置了很多item,因此我们不必全部重写这个item。所以,我们要使用Qt提供的类,就不需要在我们的类里面添加新的数据成员了。这样,我们就有了不带有额外的数据成员的Shape。那么,为什么还要提供Shape呢?因为我们在scene 的鼠标事件中需要修改这些数据成员,如果没有这个父类,我们就需要按照Code写一个长长的switch来判断是那一个图形,这样是很麻烦的。所以我们依然创建
了一个公共的父类,只要调用这个父类的draw函数即可。

line.h
	#ifndef LINE_H
	#define  LINE_H
	#include <QGraphicsLineItem>
	#include "shape.h"
	class  Line : public Shape, public QGraphicsLineItem
	{
		public:
      Line();
      void startDraw(QGraphicsSceneMouseEvent * event);
       void drawing(QGraphicsSceneMouseEvent * event);
	};
	#endif  // LINE_H
 
	line.cpp
	#include "line.h"
	Line::Line()
	{
	}
	void  Line::startDraw(QGraphicsSceneMouseEvent * event)
	{
    setLine(QLineF(event->scenePos(), event->scenePos()));
	}
	void  Line::drawing(QGraphicsSceneMouseEvent * event)
	{
    QLineF  newLine(line().p1(), event->scenePos());
    setLine(newLine);
	}

Line 类已经和前面有了变化, 我们不仅仅继承了Shape , 而且继承了QGraphicsLineItem类。这里我们使用了C++的 多继承机制。这个机制是很危险的,很容易发生错误,但是这里我们的Shape并没有继承其他的类,只要函数没有重名,一般而言是没有
问题的。如果不希望出 现不推荐的多继承(不管怎么说,多继承虽然危险,但它是符合面向对象理论的),那就就想办法使用组合机制。我们之所以使用多继承,目的是让Line类同时具有 Shape和QGraphicsLineItem的性质,从而既可以直接添加到QGraphicsScene中,又可以调用startDraw()等函数。
  同样的还有Rect这个类:
rect.h

#ifndef RECT_H
	#define  RECT_H
	#include <QGraphicsRectItem>
	#include "shape.h"
	class  Rect : public Shape, public QGraphicsRectItem
	{
		public:
      Rect();
      void startDraw(QGraphicsSceneMouseEvent * event);
       void drawing(QGraphicsSceneMouseEvent * event);
	};
	#endif  // RECT_H

rect.cpp

#include "rect.h"
	Rect::Rect()
	{
	}
	void  Rect::startDraw(QGraphicsSceneMouseEvent * event)
	{
    setRect(QRectF(event->scenePos(), QSizeF(0, 0)));
	}
	void  Rect::drawing(QGraphicsSceneMouseEvent * event)
	{
    QRectF  r(rect().topLeft(),QSizeF(event->scenePos().x()-rect().topLeft().x(), event->scenePos().y()-rect().topLeft().y()));
    setRect(r);
	}

Line和Rect类的逻辑都比较清楚,和前面的基本类似。所不同的是,Qt并没有使 用我们前面定义的两个Qpoint 对象记录数据,而是在 QGraphicsLineItem 中使用QLineF,在QGraphicsRectItem中使用QRectF记录数据。这显然比我们的两个点的数据 记录高级得多。其实,我们也完全可以使用这样的数据结构去重定义前面那些Line之类。
  这样,我们的程序就修改完毕了。运行一下你会发 现,几乎和前面的实现没有区别。这里说“几乎”,是在第一个点画下的时候,scene会移动一段距离。这是因为scene是自动居中的,由于我们把 Line的第一个点设置为(0, 0),因此当我们把鼠标移动后会有一个偏移。
  看到这里或许并没有显示出Graphics View的优势。不过,建议在Line或者Rect的构造函数里面加上下面的语句,

	setFlag(QGraphicsItem::ItemIsMovable,  true);
	setFlag(QGraphicsItem::ItemIsSelectable, true);

此时,你的Line和Rect就已经支持选中和拖放了!值得试一试哦!不过,需要注意的是,我们重写了scene的鼠标控制函数,所以这里的拖动会很粗 糙,甚至说是不正确,你需要动动脑筋重新设计我们的类啦!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习成长分享快乐

您的鼓励是我前进的不竭动力~~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值