Qt學習之路(31): 一個簡易畫板的實現(QWidget)

說實話,原本我是沒有打算放一個很大的例子的,一則比較複雜,二來或許須要不少次才能說得完。不過,如今已經說完了繪圖部分,因此計劃仍是上一個這樣的例子。這裏我會只作出一個簡單的畫板程序,大致上就是可以畫直線和矩形吧。這樣,我計劃分紅兩種實現,一是使用普通的QWidget做爲畫板,第二則是使用Graphcis View Framework來實現。由於前面有朋友說不大明白Graphics View的相關內容,因此計劃如此。
 
好了,如今先來看看咱們的主體框架。咱們的框架仍是使用Qt Creator建立一個Gui Application工程。
 
簡單的main()函數就再也不贅述了,這裏首先來看MainWindow。順便說一下,我通常不會使用ui文件,因此這些內容都是手寫的。首先先來看看最終的運行結果:
 
 
或許很簡單,可是至少咱們可以把前面所說的各類知識串連起來,這也就達到目的了。
 
如今先來看看MainWindow的代碼:
 
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui>

#include "shape.h"
#include "paintwidget.h"

class MainWindow : public QMainWindow
{
        Q_OBJECT

public:
        MainWindow(QWidget *parent = 0);

signals:
         void changeCurrentShape(Shape::Code newShape);

private slots:
         void drawLineActionTriggered();
         void drawRectActionTriggered();

};

#endif // MAINWINDOW_H
 
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);
        setCentralWidget(paintWidget);

        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類裏面聲明瞭一個信號,changeCurrentShape(Shape::Code),用於按鈕按下後通知畫圖板。注意,QActio的triggered()信號是沒有參數的,所以,咱們須要在QAction的槽函數中從新emit咱們本身定義的信號。構造函數裏面建立了兩個QAction,一個是drawLineAction,一個是drawRectAction,分別用於繪製直線和矩形。MainWindow的中心組件是PainWidget,也就是咱們的畫圖板。下面來看看PaintWidget類:
 
paintwidget.h
#ifndef PAINTWIDGET_H
#define PAINTWIDGET_H

#include <QtGui>
#include <QDebug>
#include "shape.h"
#include "line.h"
#include "rect.h"

class PaintWidget : public QWidget
{
        Q_OBJECT

public:
        PaintWidget(QWidget *parent = 0);

public slots:
         void setCurrentShape(Shape::Code s)
        {
                 if(s != currShapeCode) {
                        currShapeCode = s;
                }
        }

protected:
         void paintEvent(QPaintEvent * event);
         void mousePressEvent(QMouseEvent * event);
         void mouseMoveEvent(QMouseEvent * event);
         void mouseReleaseEvent(QMouseEvent * event);

private:
        Shape::Code currShapeCode;
        Shape *shape;
         bool perm;
        QList<Shape*> shapeList;
};

#endif // PAINTWIDGET_H
 
paintwidget.cpp
#include "paintwidget.h"

PaintWidget::PaintWidget(QWidget *parent)
        : QWidget(parent), currShapeCode(Shape::Line), shape(NULL), perm( false)
{
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}

void PaintWidget::paintEvent(QPaintEvent * event)
{
        QPainter painter( this);
        painter.setBrush(Qt::white);
        painter.drawRect(0, 0, size().width(), size().height());
         foreach(Shape * shape, shapeList) {
                shape->paint(painter);
        }
         if(shape) {
                shape->paint(painter);
        }
}

void PaintWidget::mousePressEvent(QMouseEvent * event)
{
         switch(currShapeCode)
        {
         case Shape::Line:
                {
                        shape = new Line;
                         break;
                }
         case Shape::Rect:
                {
                        shape = new Rect;
                         break;
                }
        }
         if(shape != NULL) {
                perm = false;
                shapeList<<shape;
                shape->setStart( event->pos());
                shape->setEnd( event->pos());
        }
}

void PaintWidget::mouseMoveEvent(QMouseEvent * event)
{
         if(shape && !perm) {
                shape->setEnd( event->pos());
                update();
        }
}

void PaintWidget::mouseReleaseEvent(QMouseEvent * event)
{
        perm = true;
}
 
PaintWidget類定義了一個slot,用於接收改變後的新的ShapeCode。最主要的是,PaintWidget重定義了三個關於鼠標的事件:mousePressEvent,mouseMoveEvent和mouseReleaseEvent。
 
咱們來想象一下如何繪製一個圖形:圖形的繪製與鼠標操做息息相關。以畫直線爲例,首先咱們須要按下鼠標,肯定直線的第一個點,因此在mousePressEvent裏面,咱們讓shape保存下start點。而後在鼠標按下的狀態下移動鼠標,此時,直線就會發生變化,其實是直線的終止點在隨着鼠標移動,因此在mouseMoveEvent中咱們讓shape保存下end點,而後調用update()函數,這個函數會自動調用paintEvent()函數,顯示出咱們繪製的內容。最後,當鼠標鬆開時,圖形繪製完畢,咱們將一個標誌位置爲true,此時說明這個圖形繪製完畢。
 
爲了保存咱們曾經畫下的圖形,咱們使用了一個List。每次按下鼠標時,都會把圖形存入這個List。能夠看到,咱們在paintEvent()函數中使用了foreach遍歷了這個List,繪製出歷史圖形。foreach是Qt提供的一個宏,用於遍歷集合中的元素。
 
最後咱們來看看Shape類。
 
shape.h
#ifndef SHAPE_H
#define SHAPE_H

#include <QtGui>

class Shape
{
public:

         enum Code {
                Line,
                Rect
        };

        Shape();

         void setStart(QPoint s)
        {
                start = s;
        }

         void setEnd(QPoint e)
        {
                end = e;
        }

        QPoint startPoint()
        {
                 return start;
        }

        QPoint endPoint()
        {
                 return end;
        }

         void virtual paint(QPainter & painter) = 0;

protected:
        QPoint start;
        QPoint end;
};

#endif // SHAPE_H
 
shape.cpp
#include "shape.h"

Shape::Shape()
{
}
 
Shape類最重要的就是保存了start和end兩個點。爲何只要這兩個點呢?由於咱們要繪製的是直線和矩形。對於直線來講,有了兩個點就能夠肯定這條直線,對於矩形來講,有了兩個點做爲左上角的點和右下角的點也能夠肯定這個矩形,所以咱們只要保存兩個點,就足夠保存這兩種圖形的位置和大小的信息。paint()函數是Shape類的一個純虛函數,子類都必須實現這個函數。咱們如今有兩個子類:Line和Rect,分別定義以下:
 
line.h
#ifndef LINE_H
#define LINE_H

#include "shape.h"

class Line : public Shape
{
public:
        Line();

         void paint(QPainter &painter);
};

#endif // LINE_H
 
line.cpp
#include "line.h"

Line::Line()
{
}

void Line::paint(QPainter &painter)
{
        painter.drawLine(start, end);
}
 
rect.h
#ifndef RECT_H
#define RECT_H

#include "shape.h"

class Rect : public Shape
{
public:
        Rect();

         void paint(QPainter &painter);
};

#endif // RECT_H
 
rect.cpp
#include "rect.h"

Rect::Rect()
{
}

void Rect::paint(QPainter &painter)
{
        painter.drawRect(start.x(), start.y(),
                                         end.x() - start.x(), end.y() - start.y());
}
 
使用paint()函數,根據兩個點的數據,Line和Rect均可以繪製出它們自身來。此時就能夠看出,咱們之因此要創建一個Shape做爲父類,由於這兩個類有幾乎徹底類似的數據對象,而且從語義上來講,Line、Rect與Shape也徹底是一個is-a的關係。若是你想要添加顏色等的信息,徹底能夠在Shape類進行記錄。這也就是類層次結構的好處。
 
代碼不少也會比較亂,附件裏面是整個工程的文件,有興趣的朋友不妨看看哦!
相關文章
相關標籤/搜索