【Qt 实现一个画板,基于QWidget,可以绘制直线和矩形】

【Qt 实现一个画板,基于QWidget,可以绘制直线和矩形】

  • 简介
  • 效果展示
  • 源码
    • mainwindow.h
    • mainwindow.cpp
    • painterwidget.h
    • painterwidget.cpp
    • shape.h (管理)
    • line.h
    • line.cpp
    • rect.h
    • rect.cpp


更多内容 点击:Qt 专栏



简介

这里我会只做出一个简单的画板程序,大体上就是能够画直线和矩形吧。这样,我计划分成两种实现,一是使用普通的 QWidget 作为画板,第二则是使用 Graphcis View Framework 来实现。因为前面有朋友说不大明白 Graphics View 的相关内容,所以计划如此。

好了,现在先来看看我们的主体框架。我们的框架还是使用 Qt Creator 创建一个 Gui Application工程。

简单的 main()函数就不再赘述。

效果展示

喜欢的伙伴可以自己扩展
图片由以下软件制作
【Qt 实现一个画板,基于QWidget,可以绘制直线和矩形】_第1张图片

【Qt 实现一个画板,基于QWidget,可以绘制直线和矩形】_第2张图片

【Qt 实现一个画板,基于QWidget,可以绘制直线和矩形】_第3张图片

源码

mainwindow.h

Shape::Code newshape 枚举变量 画直线 画矩形
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include 
#include 
#include 
#include "shape.h"
#include "painterwidget.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();


signals:
    void sig_changeCurrentShape(Shape::Code newshape);
private slots:
    void drawLine_ActionTriggered();
    void drawRect_ActionTriggered();
};
#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 *act_line = new QAction("Line",bar)//创建动作组,工具栏是他爸爸
    act_line->setIcon(QIcon(":/png/line.png")); //设置图标
    act_line->setToolTip(tr("Draw a Line"));//设置工具提示信息
    act_line->setStatusTip(tr("Draw a Line"));//状态栏提示信息

    act_line->setCheckable(true);
    act_line->setChecked(true);

    group->addAction(act_line); //添加进组
    bar->addAction(act_line);   

    QAction *act_rect = new QAction("Rect",bar);
    act_rect->setIcon(QIcon(":/png/rect.png"));
    act_rect->setToolTip(tr("Draw a Rect"));
    act_rect->setStatusTip(tr("Draw a Rect"));

    act_rect->setCheckable(true);

    group->addAction(act_rect);
    bar->addAction(act_rect);

    QLabel *status = new QLabel;
    statusBar()->addWidget(status); //状态栏添加标签

    //创建绘画现象
    PainterWidget *painterwidget = new PainterWidget(this);
    this->setCentralWidget(painterwidget); //添加绘画容器

    connect(act_line,&QAction::triggered,this,&MainWindow::drawLine_ActionTriggered);
    connect(act_rect,&QAction::triggered,this,&MainWindow::drawRect_ActionTriggered);

    connect(this,&MainWindow::sig_changeCurrentShape,painterwidget,&PainterWidget::SLOT_setCurrentShape);
}

MainWindow::~MainWindow()
{

}

//发射 画直线
void MainWindow::drawLine_ActionTriggered()
{
    emit this->sig_changeCurrentShape(Shape::Line);
}

//发射 画矩形
void MainWindow::drawRect_ActionTriggered()
{
    emit this->sig_changeCurrentShape(Shape::Rect);
}


我们在 MainWindow 类里面声明了一个信号,changeCurrentShape(Shape::Code),用于按钮按下后通知画图板。注意,QActio 的triggered()信号是没有参数的,因此,我们需要在 QAction 的槽函数中重新 emit 我们自己定义的信号。构造函数里面创建了两个 QAction,一个是 drawLineAction,一个是 drawRectAction,分别用于绘制直线和矩形。MainWindow 的中心组件是 PainWidget,也就是我们的画图板。下面来看看PaintWidget 类:

painterwidget.h

#ifndef PAINTERWIDGET_H
#define PAINTERWIDGET_H

#include 
#include "QMouseEvent"

#include "QDebug"
#include "line.h"
#include "shape.h"
#include "rect.h"

class PainterWidget : public QWidget
{
    Q_OBJECT
public:
    explicit PainterWidget(QWidget *parent = nullptr);

public slots:
    void  SLOT_setCurrentShape(Shape::Code enumVal) //区分形状
    {
        if (enumVal != this->curenum__value)
             this->curenum__value = enumVal;
    }
protected:
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;

signals:

private:
    Shape::Code curenum__value;
    Shape *shape; //形状指针
    bool perm;
    QList<Shape*>shapeList;  //保存形状
};

#endif // PAINTERWIDGET_H

painterwidget.cpp

设置大小策略:可以拖住放大大小。

 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); //设置策略
#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);  //在本Widget上绘图
        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();  //更新绘图 paintEvent(QPaintEvent *event) 
        } 
} 

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 "QPainter"
#include"QPoint"

class Shape
{
public:
    Shape();

    enum Code{Line,Rect}; //枚举
    void setStart(QPoint point_localstart)
    {
        this->point_start = point_localstart;
    }
    void setEnd(QPoint point_localend)
    {
        this->point_end = point_localend;
    }

    QPoint get_point_start()
    {
        return this->point_start;
    }
    QPoint get_point_end()
    {
        return this->point_end;
    }

    void virtual paint(QPainter &painter) = 0; //写一个纯虚函数,让子类去实现自己功能

protected:
    QPoint point_start;
    QPoint point_end;
};

#endif // SHAPE_H

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) override; //重写父类虚函数
};

#endif // LINE_H

line.cpp

#include "line.h"

Line::Line()
{

}

void Line::paint(QPainter &painter)
{
    painter.drawLine(point_start,point_end);//画直线
}

rect.h

#ifndef RECT_H
#define RECT_H

#include "shape.h"

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

    void paint(QPainter &painter) override;
};

#endif // RECT_H

rect.cpp

#include "rect.h"

Rect::Rect()
{

}

void Rect::paint(QPainter &painter) //画矩形 起始点为左上角 结束点为右下角
{
    painter.drawRect(point_start.x(),point_start.y(),point_end.x()-point_start.x(),point_end.y()-point_start.y());//画矩形
}

使用 paint()函数,根据两个点的数据,Line 和 Rect 都可以绘制出它们自身来。此时就可以看出,我们之所以要建立一个 Shape 作为父类,因为这两个类有几乎完全相似的数据对象,并且从语义上来说,Line、Rect 与 Shape 也完全是一个 is-a 的关系。如果你想要添加颜色等的信息,完全可以在Shape 类进行记录。这也就是类层次结构的好处。

你可能感兴趣的:(QT,界面开发工程师课程,qt,QWidget,QPainter,QGraphcis,View,C/C++)