基于QPainter的简易绘图板设计

时间:2021-12-27 22:39:45
——功能需求
  • *图形绘制
  • 基本图形绘制
  • 选择图形绘制颜色
类似于Windows自带的绘图程序

——界面解决方案
  • 以QWidget为基类创建绘图主窗口
  • 使用QGroupBox创建图形设置区域
  • 使用单选按钮QRadioButton实现目标图形的选择
  • 使用QComboBox实现绘图颜色的选择

效果:

基于QPainter的简易绘图板设计


问题分析:
1、如何实现*绘图?
*绘图的本质即跟踪鼠标的移动,所以必须考虑鼠标何时开始,何时结束,以及如何记录鼠标的移动。
从绘图参数的角度,可以将已经绘制结束的图形与正在绘制的图形进行分开处理。
*绘图时,必须记录鼠标移动时经过的所有的点的坐标,因此,绘图参数必须有能力保存多个坐标的值(容器存储)。
*绘图解决方案:
通过鼠标事件来处理坐标点,以及paintEvent函数来绘制图形
    void mouseMoveEvent(QMouseEvent* evt);//记录鼠标移动时经过的坐标
    void mousePressEvent(QMouseEvent* evt);//鼠标按下时,开始记录坐标
    void mouseReleaseEvent(QMouseEvent* evt);//鼠标释放时结束,记录结束坐标
    void paintEvent(QPaintEvent* );//按照记录,绘制相邻点直接的“直线”,因为相邻点与点之间很近
2、如何实现基础图形绘制?
基础图形的目标是固定的,但是开始点与结束点的不同会导致图形之间的差异。因此,通过鼠标移动时的当前坐标实时绘图,当鼠标松开时确定最终图形。
基础图形绘制要在鼠标按下时进行动态绘图,但是,无论何时都只需要记录两个坐标值,这点很重要。
基础图形绘制解决方案:
通过鼠标事件来处理坐标点,以及paintEvent函数来绘制图形。注意这里注释与上面注释不同的地方。

    void mouseMoveEvent(QMouseEvent* evt);//将鼠标移动时经过的目标作为临时结束目标
    void mousePressEvent(QMouseEvent* evt);//鼠标按下时,开始记录坐标
    void mouseReleaseEvent(QMouseEvent* evt);//鼠标释放时结束,记录最终结束坐标
    void paintEvent(QPaintEvent* );//按照记录,在开始目与最终,目标之间绘制需要的接触图形

源码:
工程文件:
paintPro.pro

#-------------------------------------------------
#
# Project created by QtCreator 2018-04-01T10:16:20
#
#-------------------------------------------------

QT       += core gui

#这里是C++11的宏,,代码中没有用到C++11特性,可以去掉,在Qt5+版本这么写Qt5以前的版本 
#这么写 QMAKE_CXXFLAGS += -std=c++11 
CONFIG   += c++11 #在Qt5+版本

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = paintPro
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0


SOURCES += main.cpp\
        Widget.cpp

HEADERS  += Widget.h

头文件:Widget.h

/*
*authour:   lbg
*date:      2018.4.1      
*/

#ifndef WIDGET_H
#define WIDGET_H
#include <QRadioButton>
#include <QComboBox>
#include <QGroupBox>
#include <QPainter>
#include <QPoint>
#include <QList>
#include <QMouseEvent>
#include <QPaintEvent>

#include <QWidget>

class Widget : public QWidget
{
    Q_OBJECT
    enum DrawType//绘制类型
    {
        NONE,       //无类型
        FREE,       //*绘图
        ELLIPSE,    //圆、椭圆
        RECT,       //矩形、正四边形
        LINE        //直线
    };
    
    struct DrawParam                //绘制参数结构体、类
    {
        DrawType m_type;            //绘制类型
        Qt::GlobalColor m_color;    //颜色
        QList<QPoint> m_points;     //坐标点       
    };
    
    DrawParam m_currentParam;       //当前参数,正在绘制
    QList<DrawParam> m_DrawList;    //所有参数,把将已经绘制结束的图形与正在绘制的图形进行分开处理

public:
    Widget(QWidget *parent = 0);
    ~Widget();

private://内部接口
    /*
    * @brief: getCurrentColor()根据界面选择情况,返回用户选择的颜色
    * @param: 无
    * @return: 颜色枚举   
    */
    Qt::GlobalColor getCurrentColor();
    
    /*
    * @brief:根据界面返回用户选择的绘制类型
    * @pram:无
    * @return:类型枚举
    */
    Widget::DrawType getCurrentType();
    
    /*
    * @brief: 根据当前用户选择的绘制类型(*绘制或基础图形绘制)来对当前坐标点进行处理
    * @param:当前处理的坐标点pos
    * @return:无 
    */
    void appendPos(QPoint pos); //辅助存放鼠标点
    
    /*
    * @brief:辅助paintEvent绘制函数,进行绘制
    * @param: QPainter对象, DrawParam当前绘制参数
    * @return:无
    */
    void drawGraphic(QPainter &painter, DrawParam ¶m);//
    
protected:
    //鼠标移动处理函数
    void mouseMoveEvent(QMouseEvent* evt);
    //鼠标按下处理函数
    void mousePressEvent(QMouseEvent* evt);
    //鼠标释放函数
    void mouseReleaseEvent(QMouseEvent* evt);
    //绘图函数
    void paintEvent(QPaintEvent* );

private://界面相关
    QRadioButton*   m_FreeDraw;     //*绘制按钮
    QRadioButton*   m_EllipseDraw;  //椭圆绘制按钮
    QRadioButton*   m_RectDraw;     //矩形绘制按钮
    QRadioButton*   m_LineDraw;     //直线绘制按钮
    QComboBox*      m_ColorSelect;  //颜色绘制
};

#endif // WIDGET_H

Widget.cpp

#include <QAction>
#include <QHBoxLayout>
#include <QtMath>
#include "Widget.h"


Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    //界面布局
    this->setFixedSize(800, 600);
    m_FreeDraw      = new QRadioButton("Free");
    m_EllipseDraw   = new QRadioButton("Ellipse");
    m_RectDraw      = new QRadioButton("Rect");
    m_LineDraw      = new QRadioButton("Line");

    m_ColorSelect = new QComboBox(this);
    m_ColorSelect->addItem("Red");
    m_ColorSelect->addItem("Black");
    m_ColorSelect->addItem("Green");
    m_ColorSelect->addItem("Yellow");
    m_ColorSelect->addItem("Blue");

    QHBoxLayout* m_hlayout = new QHBoxLayout;
    m_hlayout->addWidget(m_FreeDraw);
    m_hlayout->addWidget(m_EllipseDraw);
    m_hlayout->addWidget(m_RectDraw);
    m_hlayout->addWidget(m_LineDraw);
    m_hlayout->addWidget(m_ColorSelect);

    QGroupBox* box = new QGroupBox(this);
    box->setLayout(m_hlayout);
    box->resize(width(), height()/15);

}

Widget::~Widget()
{

}

Qt::GlobalColor Widget::getCurrentColor()
{
    //根据用户的选择情况,返回颜色
    Qt::GlobalColor ret = Qt::black;
    if(m_ColorSelect->currentText() == "Yellow") ret = Qt::yellow;
    if(m_ColorSelect->currentText() == "Black") ret = Qt::black;
    if(m_ColorSelect->currentText() == "Red") ret = Qt::red;
    if(m_ColorSelect->currentText() == "Green") ret = Qt::green;
    if(m_ColorSelect->currentText() == "Blue") ret = Qt::blue;
    
    return ret;
}

Widget::DrawType Widget::getCurrentType()
{
    //根据用户的选择情况,返回要绘制的类型
    Widget::DrawType ret = NONE;
    if(m_FreeDraw->isChecked())     ret = FREE;
    if(m_EllipseDraw->isChecked())  ret = ELLIPSE;
    if(m_RectDraw->isChecked())     ret = RECT;
    if(m_LineDraw->isChecked())     ret = LINE;
    return ret;
}

void Widget::appendPos(QPoint pos)
{
    //这里根据当前绘制参数作相应处理
    if(m_currentParam.m_type != NONE)
    {
        if(m_currentParam.m_type == FREE)
        {
            m_currentParam.m_points.append(pos);//*绘要把每个点都存下来
        }
        else
        {
            if(m_currentParam.m_points.size() == 2)//绘制列出的几个图形只要两个点
            {
                m_currentParam.m_points.removeLast();//把最后一个点弹出,后面再存入传进来这个点
            }
            m_currentParam.m_points.push_back(pos);
        }
    }
}

void Widget::drawGraphic(QPainter& painter, DrawParam& param)
{    
    if(param.m_type != NULL && param.m_points.count() >= 2)
    {
        int x = param.m_points[0].x() < param.m_points[1].x() ? param.m_points[0].x() : param.m_points[1].x();
        int y = param.m_points[0].y() < param.m_points[1].y() ? param.m_points[0].y() : param.m_points[1].y();        
        int w = qAbs(param.m_points[0].x() - param.m_points[1].x()) + 1;
        int h = qAbs(param.m_points[0].y() - param.m_points[1].y()) + 1;
        //设置painter的属性
        painter.setPen(QPen(param.m_color));
        painter.setBrush(QBrush(param.m_color));
        
        switch (param.m_type)
        {
        case FREE:
            for(int i = 0; i != param.m_points.count() - 1; i++) //*绘
            {
                painter.drawLine(param.m_points[i], param.m_points[i + 1]);
            }            
            break;
            
        case LINE:
            painter.drawLine(param.m_points[0], param.m_points[1]);
            break;
            
        case ELLIPSE:
            painter.drawEllipse(x, y, w, h);
            break;
        case RECT:
            painter.drawRect(x, y, w, h);
            break;
        default:
            break;            
            
        }
    }
}

void Widget::mouseMoveEvent(QMouseEvent *evt)
{
    appendPos(evt->pos());
    update();
}

void Widget::mousePressEvent(QMouseEvent *evt)
{
    m_currentParam.m_type   = getCurrentType();
    m_currentParam.m_color  = getCurrentColor();
    m_currentParam.m_points.append(evt->pos());
}

void Widget::mouseReleaseEvent(QMouseEvent *evt)
{
    appendPos(evt->pos());
    m_DrawList.append(m_currentParam);
    m_currentParam.m_color  = Qt::white;
    m_currentParam.m_type   = NONE;
    m_currentParam.m_points.clear();
    update();
}

void Widget::paintEvent(QPaintEvent *)
{
//    Q_UNUSED(evt)
    QPainter paint(this);
    for(int i = 0; i < m_DrawList.count(); i++)
    {
        drawGraphic(paint, m_DrawList[i]);
    }
    drawGraphic(paint, m_currentParam);
}

main.cpp

#include "Widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}
总结:
1、绘图程序需要重写鼠标事件处理函数
2、模型视图的思想适用于绘图程序
3、所有的图形绘制都由paintEvent函数完成
4、工程中要避免在绘制时进行浮点运算

扩展:
        可以将绘制的图形保存为本地文件(相关描述文件,图片)
        可以加入网络数据传输,做成一个可以大家一同使用的绘图板,界面由多个Client实时共享。