鍍金池/ 教程/ C/ Qt 學(xué)習(xí)之路(32): 一個簡易畫板的實現(xiàn)(Graphics View)
Qt 容器和算法拾遺
自定義 model 之一
反走樣
Hello, world!
Qt 容器類之關(guān)聯(lián)存儲容器
QStringListModel
拖放技術(shù)之一
狀態(tài)欄
QTreeWidget
拖放技術(shù)之二
通用算法
event()
Qt 學(xué)習(xí)之路(18): Qt 標準對話框之 QInputDialog
Qt 容器類之遍歷器和隱式數(shù)據(jù)共享
QListWidget
Meta-Object 系統(tǒng)
事件接收與忽略
Qt 學(xué)習(xí)之路(tip): parent 參數(shù)
Qt 標準對話框之 QColorDialog
QPainter(續(xù))
國際化(下)
漸變填充
自定義委托
創(chuàng)建 shared library
model-view 架構(gòu)
Graphics View Framework
自定義拖放數(shù)據(jù)對象
QSortFilterProxyModel
國際化(上)
組件布局
自定義 Model 之三
事件過濾器
QDirModel
Hello, world!(續(xù))
Qt 標準對話框之 QFileDialog
自定義 model 之二
深入了解信號槽
坐標變換
剪貼板操作
QTableWidget
QByteArray 和 QVariant
創(chuàng)建一個對話框(下)
Qt 學(xué)習(xí)之路(32): 一個簡易畫板的實現(xiàn)(Graphics View)
文本文件讀寫
自定義事件
編寫跨平臺的程序
MainWindow
初探信號槽
Qt 學(xué)習(xí)之路(17): Qt 標準對話框之 QMessageBox
繪圖設(shè)備
菜單和工具條(續(xù))
二進制文件讀寫
QString
事件(event)
菜單和工具條
QPainter
Qt 容器類之順序存儲容器
進程間交互
API 文檔的使用
創(chuàng)建一個對話框(上)
一個簡易畫板的實現(xiàn)(QWidget)

Qt 學(xué)習(xí)之路(32): 一個簡易畫板的實現(xiàn)(Graphics View)

這一次將介紹如何使用 Graphics View 來實現(xiàn)前面所說的畫板。前面說了很多有關(guān) Graphics View的好話,但是沒有具體的實例很難說究竟好在哪里?,F(xiàn)在我們就把前面的內(nèi)容使用 Graphics View 重新實現(xiàn)一下,大家可以對比一下看有什么區(qū)別。

同前面相似的內(nèi)容就不再敘述了,我們從上次代碼的基礎(chǔ)上進行修改,以便符合我們的需要。首先來看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 的對象,而 scene 對象需要通過 view 進行觀察,因此,我們需要再使用一個 QGraphcisView 對象,并且把這個 view 添加到 MainWindow 里面。

我們把 PaintWidget 當(dāng)做一個 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,同樣也會有鼠標事件,只不過在這里我們把鼠標事件全部轉(zhuǎn)發(fā)給具體的 item 進行處理。這個我們會在下面的代碼中看到。另外一點是,每一個鼠標處理函數(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 類也有了變化:還記得我們曾經(jīng)說過,Qt 內(nèi)置了很多 item,因此我們不必全部重寫這個 item。所以,我們要使用 Qt 提供的類,就不需要在我們的類里面添加新的數(shù)據(jù)成員了。這樣,我們就有了不帶有額外的數(shù)據(jù)成員的 Shape。那么,為什么還要提供 Shape 呢?因為我們在 scene 的鼠標事件中需要修改這些數(shù)據(jù)成員,如果沒有這個父類,我們就需要按照 Code 寫一個長長的 switch 來判斷是那一個圖形,這樣是很麻煩的。所以我們依然創(chuàng)建了一個公共的父類,只要調(diào)用這個父類的 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++的多繼承機制。這個機制是很危險的,很容易發(fā)生錯誤,但是這里我們的 Shape 并沒有繼承其他的類,只要函數(shù)沒有重名,一般而言是沒有問題的。如果不希望出現(xiàn)不推薦的多繼承(不管怎么說,多繼承雖然危險,但它是符合面向?qū)ο罄碚摰?,那就就想辦法使用組合機制。我們之所以使用多繼承,目的是讓 Line 類同時具有 Shape 和 QGraphicsLineItem 的性質(zhì),從而既可以直接添加到QGraphicsScene 中,又可以調(diào)用 startDraw()等函數(shù)。

同樣的還有 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 對象記錄數(shù)據(jù),而是在 QGraphicsLineItem 中使用 QLineF,在 QGraphicsRectItem 中使用 QRectF 記錄數(shù)據(jù)。這顯然比我們的兩個點的數(shù)據(jù)記錄高級得多。其實,我們也完全可以使用這樣的數(shù)據(jù)結(jié)構(gòu)去重定義前面那些 Line 之類。

這樣,我們的程序就修改完畢了。運行一下你會發(fā)現(xiàn),幾乎和前面的實現(xiàn)沒有區(qū)別。這里說“幾乎”,是在第一個點畫下的時候,scene 會移動一段距離。這是因為 scene 是自動居中的,由于我們把 Line 的第一個點設(shè)置為(0, 0),因此當(dāng)我們把鼠標移動后會有一個偏移。

看到這里或許并沒有顯示出 Graphics View 的優(yōu)勢。不過,建議在 Line 或者 Rect 的構(gòu)造函數(shù)里面加上下面的語句,


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

此時,你的 Line 和 Rect 就已經(jīng)支持選中和拖放了!值得試一試哦!不過,需要注意的是,我們重寫了 scene 的鼠標控制函數(shù),所以這里的拖動會很粗糙,甚至說是不正確,你需要動動腦筋重新設(shè)計我們的類啦!

本文出自 “豆子空間” 博客,請務(wù)必保留此出處 http://devbean.blog.51cto.com/448512/193918

上一篇:自定義事件下一篇:事件(event)