程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> Qt學習之路(32):一個簡易畫板的實現(Graphics View)

Qt學習之路(32):一個簡易畫板的實現(Graphics View)

編輯:關於C++

本文配套源碼

這一次將介紹如何使用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的鼠標控制函數,所以這裡的拖動會很粗糙,甚至說是不正確,你需要動動腦筋重新設計我們的類啦!

出處:http://devbean.blog.51cto.com/448512/244181

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved