[关闭]
@Dmaxiya 2019-06-12T06:55:00.000000Z 字数 4130 阅读 2073

Qt 画板

其他


原有 bug

先从原有代码的明显的小 bug 和需要改进的地方说起吧。
1. 刚运行程序,不选择画什么图形就在画布上作画的话,会让程序崩溃,原因是在 CenterWidget 的构造函数中没有初始化drawType;
2. 无法显示工具栏的图标,原因是对应路径下没有图片;
3. 保存文件不可以任意命名文件名,原因不明;
4. 在不需要 tr() 的地方都使用了 tr,tr 主要是用来实现国际化的,详见关于qt中的tr()函数,建议都修改成 QString;
5. 画布背景修改为白色;
6. 析构函数没有完全释放所有指针。

功能分析

  1. 支持画圆、矩形、直线,支持随手写功能;
  2. 可以改变画笔的颜色与粗细;
  3. 可以保存为文件(专用格式),并且可以读取显示;
  4. 可以另存为图像(支持 JPG, BMP 等常用的文件格式);
  5. 增加笔型。即可以选择使用毛笔,铅笔, 蜡笔三种不同的笔来书写,画板上呈现出模拟三种不同笔书写的效果;
  6. 在前面的画笔程序中,增加 Redo, Undo 功能。可以指定 Undo 最多的次数,比如 10 次。

首先是绘画部分,查了一下比较高效的画法是画在 QImage 中,然后在 QWidge 的 update 中 drawImage,详见题Qt/C++:高效绘图,因此改进是在 CenterWidget 中增加 private 的 QImage 数据成员,以 QImage 为 QPainter 的画布,最后直接保存 QImage 对象。
其次是画的对象,有几种对象类型:椭圆、矩形、直线、随手写,其中随手写又可以支持不同的笔型,除了随手写的其他类型都不应该支持笔型,在 CenterWidget 的设置中可以设置笔型,但是只有在随手写时才能显示出笔型的效果,目前笔型比较好的支持是用 QImage 来储存,类似于 PS 的笔刷,QImage 支持修改大小、颜色。
对于椭圆、矩形、直线,画笔的颜色与粗细都可以直接作为 setPen 的参数传入,颜色、粗细、形状类型的参数都存在 CenterWidget 的数据成员中,在 CenterWidget 的构造函数中应初始化它们,并且在椭圆、矩形和直线的构造函数中应该显式传入这些参数;但是对于随手写则需要对应地修改 QImage,为提高效率,不在 mousePressEvent 的时候才设置画笔的 QImage,而在选中画笔时就修改 CenterWidget 的笔刷数据成员为对应的粗细及颜色,在开始随手写时将 CenterWidget 的笔刷数据成员传入随手写的数据成员中。
粗细和图形类型都设为枚举型,所有颜色直接采用 Qcolor 内置的颜色值(如 Qt::black, Qt::green, Qt::red 等),便于扩展程序,同时注意在设置粗细的菜单栏下应显示对应的粗细预览,就像 Windows 中的画图一样。一个比较好的体验是在使用随手写时能预览到要画的笔刷(在移动鼠标时可以看到笔刷的形状与颜色,但是只有在点下鼠标时才会开始绘画),但是这个暂时可以先放一下,等完成基本功能后再补充。
由于随手写需要用一系列点来储存,因此将起点 p1 和终点 p2 记录在 CenterWidget 中做 update 已经不再合适,应该用一个临时的 Shape 对象来作为 update 的对象,在 mouseReleaseEvent 中直接将临时 Shape 存入 Redo QList 中。
关于 Redo 和 Undo,由于涉及到两端的操作,而 Qt 中没有 QDeque,就用两个 QList 来记录 Redo 和 Undo 操作中的形状对象,且大小最大为 10。
最后是保存与读取,由于可以打开 *.jpg 和 *.bmp 格式,正常来说我们也应该能打开其他来源的同类型文件,但是大小可能与我们的默认窗口大小不符合,因此在绘图时应该有一个默认的窗口大小与打开文件的窗口大小,为简单起见,先窗口大小设为不可调整(不可以最大化,不可以被 Resize)。保存为专用格式暂时先不做,由于原先的代码不能将文件保存为任意格式,所以最后在保存文件时建议加上文件名的格式检查,除了文件名中禁止出现的字符,还要检查后缀,是否为 .jpg 或者 .bmp,若是则直接保存,否则自动加上后缀并保存。
读取一个新的文件时,Redo 和 Undo 的 QList 完全清空失效(在新建文件时也一样)。
最后关于信号与槽的部分,在初始化 MainWindow 时设置的 connect 实际上都是先调用自己的槽函数,再调用 CenterWidget 的函数,这样的设计看起来是不合理的,实际上可以通过加 QSignalMapper 的方式(详见Qt菜单QMenu QAction连接信号槽函数),与枚举型配合,直接将参数传到 CenterWindow 的函数中,再用对应的参数设定值,因此应该把这些被 MainWindow 的槽函数调用的 CenterWidget 中的函数设为 public 槽函数。由于 QColor 不能作为 mapping 参数,因此暂时也为 QColor 增加有限的枚举值,若要改为用调色板,想来应该可以设置对应的 signal 函数,这样就不需要颜色的枚举值了。
最后关于 CenterWidget 的 newDrawing、openDrawing、saveDrawing 三个函数体部分做的事情,看起来更像是应该在 MainWindow 中做的,什么弹出保存文件窗口之类的,CenterWidget 本应该只管画布部分的事就好了,这部分的代码需要大改,一部分保留一部分移到 MainWindow 中,函数声明暂定,具体后面会再修改。

头文件

Shape.h

  1. enum ShapeType {Line, Ellipse, Rectangle, RandomWrite};
  2. enum Color {black, green, red};
  3. enum Width {Thin = 1, Light = 5, Thick = 10, Heavy = 20};
  4. // 随便写的,英文单词不对就改,粗细主要看显示效果
  5. class Shape {
  6. public:
  7. Width getWidth();
  8. const QColor& getColor();
  9. ShapeType getType();
  10. virtual void draw(QPainter &painter) = 0;
  11. virtual void setNextPoint(const QPoint &p) = 0;
  12. protected:
  13. Width width;
  14. QColor color;
  15. ShapeType type;
  16. Shape(const Width &w, const QColor &c, const ShapeType &t);
  17. };

Line.h

  1. class Line: public Shape {
  2. public:
  3. Line(const QLine &l, const Width &w, const QColor &c);
  4. void draw(QPainter &painter);
  5. void setNextPoint(const QPoint &p);
  6. private:
  7. QLine line;
  8. };

Ellipse.h

  1. class Ellipse: public Shape {
  2. public:
  3. Ellipse(const QRect &r, const Width &w, const QColor &c);
  4. void draw(QPainter &painter);
  5. void setNextPoint(const QPoint &p);
  6. private:
  7. QRect rect;
  8. };

Rectangle.h

  1. class Rectangle: public Shape {
  2. public:
  3. Rectangle(const QRect &r, const Width &w, const QColor &c);
  4. void draw(QPainter &painter);
  5. void setNextPoint(const QPoint &p);
  6. private:
  7. QRect rect;
  8. };

RandomWrite.h

  1. class RandomWrite: public Shape {
  2. public:
  3. RandomWrite(const QImage &i, const Width &w, const QColor &c);
  4. void appendPoint(const QPoint);
  5. void draw(QPainter &painter);
  6. void setNextPoint(const QPoint &p);
  7. private:
  8. QVector<QPoint> points;
  9. QImage image;
  10. };

CenterWidget.h

  1. class CenterWidget: public QWidget {
  2. Q_OBJECT
  3. public:
  4. explicit CenterWidget(QWidget *parent);
  5. void paintEvent(QPaintEvent *p);
  6. void initDrawing();
  7. void loadImage(const QImage &i);
  8. const QImage& getImage();
  9. bool getModifiedFlag();
  10. public slots:
  11. void setShapeType(const ShapeType &t);
  12. void setColorType(const Color &c);
  13. protected:
  14. void mousePressEvent(QMouseEvent *e);
  15. void mouseMoveEvent(QMouseEvent *e);
  16. void mouseReleaseEvent(QMouseEvent *e);
  17. private:
  18. QList<Shape> Redo;
  19. QList<Shape> Undo;
  20. Shape *shape;
  21. ShapeType shapeType;
  22. QColor color;
  23. Width width;
  24. bool beginDraw;
  25. QLabel mousePosLabel;
  26. bool isModified;
  27. QString fileName;
  28. };

MainWindow.h

  1. class MainWindow: public QMainWindow {
  2. Q_OBJECT
  3. public:
  4. MainWindow(QWidget *parent = 0);
  5. ~MainWindow();
  6. public slots:
  7. void newDrawing();
  8. void openDrawing();
  9. void saveDrawing();
  10. void closeEvent();
  11. private:
  12. CenterWidget CenterWidget;
  13. };
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注