——功能需求
- 自由图形绘制
- 基本图形绘制
- 选择图形绘制颜色
类似于Windows自带的绘图程序
——界面解决方案
- 以QWidget为基类创建绘图主窗口
- 使用QGroupBox创建图形设置区域
- 使用单选按钮QRadioButton实现目标图形的选择
- 使用QComboBox实现绘图颜色的选择
效果:
问题分析:
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实时共享。