@Dmaxiya
2019-06-12T14:55:00.000000Z
字数 4130
阅读 1438
其他
先从原有代码的明显的小 bug 和需要改进的地方说起吧。
1. 刚运行程序,不选择画什么图形就在画布上作画的话,会让程序崩溃,原因是在 CenterWidget 的构造函数中没有初始化drawType
;
2. 无法显示工具栏的图标,原因是对应路径下没有图片;
3. 保存文件不可以任意命名文件名,原因不明;
4. 在不需要 tr()
的地方都使用了 tr
,tr
主要是用来实现国际化的,详见关于qt中的tr()函数,建议都修改成 QString
;
5. 画布背景修改为白色;
6. 析构函数没有完全释放所有指针。
JPG
, BMP
等常用的文件格式);首先是绘画部分,查了一下比较高效的画法是画在 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
中,函数声明暂定,具体后面会再修改。
enum ShapeType {Line, Ellipse, Rectangle, RandomWrite};
enum Color {black, green, red};
enum Width {Thin = 1, Light = 5, Thick = 10, Heavy = 20};
// 随便写的,英文单词不对就改,粗细主要看显示效果
class Shape {
public:
Width getWidth();
const QColor& getColor();
ShapeType getType();
virtual void draw(QPainter &painter) = 0;
virtual void setNextPoint(const QPoint &p) = 0;
protected:
Width width;
QColor color;
ShapeType type;
Shape(const Width &w, const QColor &c, const ShapeType &t);
};
class Line: public Shape {
public:
Line(const QLine &l, const Width &w, const QColor &c);
void draw(QPainter &painter);
void setNextPoint(const QPoint &p);
private:
QLine line;
};
class Ellipse: public Shape {
public:
Ellipse(const QRect &r, const Width &w, const QColor &c);
void draw(QPainter &painter);
void setNextPoint(const QPoint &p);
private:
QRect rect;
};
class Rectangle: public Shape {
public:
Rectangle(const QRect &r, const Width &w, const QColor &c);
void draw(QPainter &painter);
void setNextPoint(const QPoint &p);
private:
QRect rect;
};
class RandomWrite: public Shape {
public:
RandomWrite(const QImage &i, const Width &w, const QColor &c);
void appendPoint(const QPoint);
void draw(QPainter &painter);
void setNextPoint(const QPoint &p);
private:
QVector<QPoint> points;
QImage image;
};
class CenterWidget: public QWidget {
Q_OBJECT
public:
explicit CenterWidget(QWidget *parent);
void paintEvent(QPaintEvent *p);
void initDrawing();
void loadImage(const QImage &i);
const QImage& getImage();
bool getModifiedFlag();
public slots:
void setShapeType(const ShapeType &t);
void setColorType(const Color &c);
protected:
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
private:
QList<Shape> Redo;
QList<Shape> Undo;
Shape *shape;
ShapeType shapeType;
QColor color;
Width width;
bool beginDraw;
QLabel mousePosLabel;
bool isModified;
QString fileName;
};
class MainWindow: public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void newDrawing();
void openDrawing();
void saveDrawing();
void closeEvent();
private:
CenterWidget CenterWidget;
};