這一次將介紹如何使用 Graphics View 來實(shí)現(xiàn)前面所說的畫板。前面說了很多有關(guān) Graphics View的好話,但是沒有具體的實(shí)例很難說究竟好在哪里。現(xiàn)在我們就把前面的內(nèi)容使用 Graphics View 重新實(shí)現(xiàn)一下,大家可以對(duì)比一下看有什么區(qū)別。
同前面相似的內(nèi)容就不再敘述了,我們從上次代碼的基礎(chǔ)上進(jìn)行修改,以便符合我們的需要。首先來看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 文件里面只有少數(shù)幾行與前文不同。由于我們使用 Graphics View,所以,我們必須把 item 添加到 QGprahicsScene 里面。這里,我們創(chuàng)建了 scene 的對(duì)象,而 scene 對(duì)象需要通過 view 進(jìn)行觀察,因此,我們需要再使用一個(gè) QGraphcisView 對(duì)象,并且把這個(gè) view 添加到 MainWindow 里面。
我們把 PaintWidget 當(dāng)做一個(gè) scene,因此 PaintWidget 現(xiàn)在是繼承 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,同樣也會(huì)有鼠標(biāo)事件,只不過在這里我們把鼠標(biāo)事件全部轉(zhuǎn)發(fā)給具體的 item 進(jìn)行處理。這個(gè)我們會(huì)在下面的代碼中看到。另外一點(diǎn)是,每一個(gè)鼠標(biāo)處理函數(shù)都包含了調(diào)用其父類函數(shù)的語句。
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 類也有了變化:還記得我們?cè)?jīng)說過,Qt 內(nèi)置了很多 item,因此我們不必全部重寫這個(gè) item。所以,我們要使用 Qt 提供的類,就不需要在我們的類里面添加新的數(shù)據(jù)成員了。這樣,我們就有了不帶有額外的數(shù)據(jù)成員的 Shape。那么,為什么還要提供 Shape 呢?因?yàn)槲覀冊(cè)?scene 的鼠標(biāo)事件中需要修改這些數(shù)據(jù)成員,如果沒有這個(gè)父類,我們就需要按照 Code 寫一個(gè)長(zhǎng)長(zhǎng)的 switch 來判斷是那一個(gè)圖形,這樣是很麻煩的。所以我們依然創(chuàng)建了一個(gè)公共的父類,只要調(diào)用這個(gè)父類的 draw 函數(shù)即可。
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 類已經(jīng)和前面有了變化,我們不僅僅繼承了 Shape,而且繼承了 QGraphicsLineItem 類。這里我們使用了 C++的多繼承機(jī)制。這個(gè)機(jī)制是很危險(xiǎn)的,很容易發(fā)生錯(cuò)誤,但是這里我們的 Shape 并沒有繼承其他的類,只要函數(shù)沒有重名,一般而言是沒有問題的。如果不希望出現(xiàn)不推薦的多繼承(不管怎么說,多繼承雖然危險(xiǎn),但它是符合面向?qū)ο罄碚摰?,那就就想辦法使用組合機(jī)制。我們之所以使用多繼承,目的是讓 Line 類同時(shí)具有 Shape 和 QGraphicsLineItem 的性質(zhì),從而既可以直接添加到QGraphicsScene 中,又可以調(diào)用 startDraw()等函數(shù)。
同樣的還有 Rect 這個(gè)類:
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 并沒有使用我們前面定義的兩個(gè) Qpoint 對(duì)象記錄數(shù)據(jù),而是在 QGraphicsLineItem 中使用 QLineF,在 QGraphicsRectItem 中使用 QRectF 記錄數(shù)據(jù)。這顯然比我們的兩個(gè)點(diǎn)的數(shù)據(jù)記錄高級(jí)得多。其實(shí),我們也完全可以使用這樣的數(shù)據(jù)結(jié)構(gòu)去重定義前面那些 Line 之類。
這樣,我們的程序就修改完畢了。運(yùn)行一下你會(huì)發(fā)現(xiàn),幾乎和前面的實(shí)現(xiàn)沒有區(qū)別。這里說“幾乎”,是在第一個(gè)點(diǎn)畫下的時(shí)候,scene 會(huì)移動(dòng)一段距離。這是因?yàn)?scene 是自動(dòng)居中的,由于我們把 Line 的第一個(gè)點(diǎn)設(shè)置為(0, 0),因此當(dāng)我們把鼠標(biāo)移動(dòng)后會(huì)有一個(gè)偏移。
看到這里或許并沒有顯示出 Graphics View 的優(yōu)勢(shì)。不過,建議在 Line 或者 Rect 的構(gòu)造函數(shù)里面加上下面的語句,
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
此時(shí),你的 Line 和 Rect 就已經(jīng)支持選中和拖放了!值得試一試哦!不過,需要注意的是,我們重寫了 scene 的鼠標(biāo)控制函數(shù),所以這里的拖動(dòng)會(huì)很粗糙,甚至說是不正確,你需要?jiǎng)觿?dòng)腦筋重新設(shè)計(jì)我們的類啦!
本文出自 “豆子空間” 博客,請(qǐng)務(wù)必保留此出處 http://devbean.blog.51cto.com/448512/193918
更多建議: