Qt學習之路(32): 一個簡易畫板的實現(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的鼠標控制函數,因此這裏的拖動會很粗糙,甚至說是不正確,你須要動動腦筋從新設計咱們的類啦!
相關文章
相關標籤/搜索