工具栏通常位于菜单栏的下方,上面存放着一些小按钮,如下图所示。
以下所有功能都是直接通过代码实现,而不是在设计模式下ui界面通过拖拽实现。当然,它是可以用拖拽实现的。
图片资源主要是用于动作的图标。在工程文件里面创建一个picture文件夹,然后在PPT截图了两张图片用作图标,图片格式没要求。
在该项目的基础上,点击QT菜单栏【文件】→【新建文件或项目】,左边选择【Qt】,中间选择【Qt Resource File】,选择【Chose】,自己命个名,然后不断下一步即可。
会发现工程里面多了Resources文件夹,这个myres就是我刚才命名的。
右键点击Resources,选择【Add Existing Directory】(添加现存的目录),出现以下界面把图片选上就行了。
然后目录就变成了这样,至此已经完成图片资源的导入。
mainwindow.cpp文件内容;
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QToolBar *bar = new QToolBar; //新建工具栏
this->addToolBar(bar); //将工具栏添加到这个窗口
QActionGroup *group = new QActionGroup(bar); //新建动作组合
QAction *drawLineAction = new QAction("Line", bar);
drawLineAction->setIcon(QIcon(":/picture/line.jpg")); //图标
drawLineAction->setToolTip(tr("Draw a line1.")); //工具栏提示
drawLineAction->setStatusTip(tr("Draw a line."));//下面状态栏文本
drawLineAction->setCheckable(true);
drawLineAction->setChecked(true);
group->addAction(drawLineAction); //向动作组合添加一个新动作
bar->addAction(drawLineAction); //向工具栏添加动作
QAction *drawRectAction = new QAction("Rectangle", bar);
drawRectAction->setIcon(QIcon(":/picture/rect.jpg"));
drawRectAction->setToolTip(tr("Draw a rectangle1."));
drawRectAction->setStatusTip(tr("Draw a rectangle."));
drawRectAction->setCheckable(true);
group->addAction(drawRectAction);
bar->addAction(drawRectAction);
}
MainWindow::~MainWindow()
{
delete ui;
}
mainwindow.h文件内容,主要是要引入对应的include。
效果:
QToolBar *bar = new QToolBar; //新建工具栏
this->addToolBar(bar); //将工具栏添加到这个窗口
工具栏对应的类是QToolBar,使用需要添加#include
。接下来要用addToolBar函数把工具栏添加到主窗口,也就是this对应的指针。addToolBar函数通过名字就很好理解,就是往某个窗口添加工具栏。
QActionGroup *group = new QActionGroup(bar); //新建动作组合
QActionGroup类对应的是动作组合,使用需要添加#include
。这个类对应的是多个动作,适应的情况是存在多个动作,但是某一时刻只有一个动作是激活状态,即他们是互斥的。比如常见的左对齐、右对齐、居中对齐,他们是仅有一个生效的。利用QActionGroup类可以简单达到这种互斥的效果。
QAction *drawLineAction = new QAction("Line", bar);
drawLineAction->setIcon(QIcon(":/picture/line.jpg")); //图标
drawLineAction->setToolTip(tr("Draw a line1.")); //工具栏提示
drawLineAction->setStatusTip(tr("Draw a line."));//下面状态栏文本
drawLineAction->setCheckable(true);
drawLineAction->setChecked(true);
group->addAction(drawLineAction); //向动作组合添加一个新动作
bar->addAction(drawLineAction); //向工具栏添加动作
QAction类对应的是单个动作,使用需要添加#include
。第一句代码就是生成了一个动作实例,父对象是bar,即工具栏。下面对应的是这个动作的不同属性,通过对应的函数分别设置。
1.setIcon函数:图标很好理解,使用图标需要提前导入图片资源,也就是文章最前面部分。并且要用QIcon强制转换为图标类型。
2.setToolTip函数:当鼠标放在图标上面出现的文字提示。
3.SetStatusTip函数:当鼠标放在图标上,状态栏出现的文字提示。
3.setCheckable函数:
这个函数设定这个动作是否是可复用动作。默认情况它的取值是false,即不复用。对应的表现就是按下动作之后,会自动弹起。类比一下按钮,按一下按钮,按钮会自动恢复。类似保存文件之类动作,按下就执行动作,然后自动弹起,并不存在两种状态,所以保持默认false即可。
如果通过函数设定为true,那就是可复用状态。那这个属性适用于哪种场景呢?比如字体的粗细,按下就是粗体,然后保持粗体,再按下弹起来,恢复正常。这就是动作的复用,存在两种状态。
4.setChecked函数:
这个函数对应的是上面那个属性,只有动作是checkable状态,才可以设置。默认情况下,checked属性是false,即不选中。如果设置为true,当你打开的时候,这个动作就默认已经被选中。
QLabel是标签控件,使用需要#include
。主要用于显示信息。statusBar是新建的MainWindow项目默认的状态栏,然后通过addWidget函数把标签控件加入状态栏,后续可以直接在标签里面设置显示文本,这样状态栏就会出现对应信息。
上述我们只是实现了工具栏的框架,只能点一点,看一看,但是没啥反应。
在mainwindow.h中添加自定义的信号和两个槽函数,这个自定义的信号用于发送画线还是画矩形,函数有一个参数来表示shape。Shape类也是自定义的类,后面会继续介绍。
signals:
void changeCurrentShape(Shape::Code newShape);
private slots:
void drawLineActionTriggered();
void drawRectActionTriggered();
在mainwindow.cpp文件实现两个槽函数,即发送对应的信号。至于这两个槽函数由什么控件触发,后续介绍。
void MainWindow::drawLineActionTriggered()
{
emit changeCurrentShape(Shape::Line);
}
void MainWindow::drawRectActionTriggered()
{
emit changeCurrentShape(Shape::Rect);
}
在mainwindow.cpp文件中的MainWindow::MainWindow函数添加三个connect将信号与槽对接起来。
drawLineAction和drawRectAction是上面创建的两个动作,点击这两个动作会发出triggered信号。但是这个信号是不带参数的,而接收信号的画板就区分不了两者。所以才需要新建一个新的带参数的信号。
流程如下:点击画直线图标,触发triggerrd信号,进入槽函数drawLineActionTriggered函数,在此函数emit一个带有Shape::Line参数的信号。这样画板就知道该画直线了。
//连接信号与槽
connect(drawLineAction, SIGNAL(triggered()),
this, SLOT(drawLineActionTriggered()));
connect(drawRectAction, SIGNAL(triggered()),
this, SLOT(drawRectActionTriggered()));
connect(this, SIGNAL(changeCurrentShape(Shape::Code)),
paintWidget, SLOT(setCurrentShape(Shape::Code)));
先看看shape相关文件,这是Line和Rect的父类。
shape.h
#ifndef SHAPE_H
#define SHAPE_H
#include
class Shape
{
public:
enum Code {
Line,
Rect
};
Shape();
void setStart(QPoint s)
{
start = s;
}
void setEnd(QPoint e)
{
end = e;
}
QPoint startPoint()
{
return start;
}
QPoint endPoint()
{
return end;
}
void virtual paint(QPainter & painter) = 0;
protected:
QPoint start;
QPoint end;
};
#endif // SHAPE_H
Shape存在两个QPoint点,起点和终点,通过这两个数据确定直线和矩形的大小。paint函数是一个虚函数,实际实现需要子类具体实现。Code是一个枚举向量,用来表示直线还是矩形。
shape.cpp
#include "shape.h"
Shape::Shape()
{
}
line.h
#ifndef LINE_H
#define LINE_H
#include "shape.h"
class Line : public Shape
{
public:
Line();
void paint(QPainter &painter);
};
#endif // LINE_H
line.cpp
#include "line.h"
Line::Line()
{
}
void Line::paint(QPainter &painter)
{
painter.drawLine(start, end);
}
line类继承于Shape,主要就是实现了画直线的操作。QPainter类是专门用于画图的类,使用需要#include
。drawLine是内置的画直线函数,只需要起点和终点两个参数,这两参数来自于父类Shape。
rect.h
#ifndef RECT_H
#define RECT_H
#include "shape.h"
class Rect : public Shape
{
public:
Rect();
void paint(QPainter &painter);
};
#endif // RECT_H
rect.cpp
#include "rect.h"
Rect::Rect()
{
}
void Rect::paint(QPainter &painter)
{
painter.drawRect(start.x(), start.y(),end.x() - start.x(), end.y() - start.y());
}
和Line类的区别就是换了drawRect函数,这是画矩形的专门函数。四个参数分别代表左上角顶点的x、y、矩形的宽度、矩形的高度。start和end是QPoint类,他们的成员函数x()和y()可以获取这个点的xy坐标。
PaintWidget是自定义的画板类,需要添加对应的头文件以及源文件。
paintwidget.h文件。
#ifndef PAINTWIDGET_H
#define PAINTWIDGET_H
#include
#include
#include "shape.h"
#include "line.h"
#include "rect.h"
#include
class PaintWidget : public QWidget
{
Q_OBJECT
public:
PaintWidget(QWidget *parent = 0);
public slots:
void setCurrentShape(Shape::Code s)
{
if(s != currShapeCode) {
currShapeCode = s;
}
}
protected:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
private:
Shape::Code currShapeCode;
Shape *shape;
bool perm;
QList shapeList;
};
#endif // PAINTWIDGET_H
PaintWidget类定义了一个槽函数,用于接收绘制形状的Shape::Code.
PaintWidget重定义了三个关于鼠标的事件:mousePressEvent,mouseMoveEvent和mouseReleaseEvent。这三个函数是父类的虚函数,我们可以重新定义函数内容。
QList是一个列表,主要是用于存储历史图片。
paintwidget.cpp文件。
#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);
painter.setBrush(Qt::white);
painter.drawRect(0, 0, size().width(), size().height());
foreach(Shape * shape, shapeList) {
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<setStart(event->pos());
shape->setEnd(event->pos());
}
}
void PaintWidget::mouseMoveEvent(QMouseEvent *event)
{
if(shape && !perm) {
shape->setEnd(event->pos());
update();
}
}
void PaintWidget::mouseReleaseEvent(QMouseEvent *event)
{
perm = true;
}
鼠标点击函数: 根据PaintWidget类的currShapeCode决定绘制直线还是矩形。shape是PaintWidget类一个指向Shape的指针。perm是PaintWidget类的成员,表示绘制是否结束。
将目前的图形shape存入列表shaoeList。将目前鼠标的位置pos设定为图形的起点和终点。
鼠标移动函数:
只要perm标志位没有变为true,在移动的过程中,将鼠标的位置设定为shape的结束点,并且通过update更新画板。
update函数是paintEvent提供的刷新函数,每次调用,都会重绘事件。相当于画笔随着鼠标的移动在不断地画线。
鼠标释放函数:
当鼠标释放的时候,perm变为true,所以此时移动鼠标,就不会改变终点,也不会刷新画板,相当于就图片就确定了。
重绘事件处理函数:
基础控件类QWidget提供的paintEvent函数是一个纯虚函数,继承它的子类想要进行重绘就必须重新实现。通过QPainter类定义了一个画家,通过setBrush函数设置画刷颜色为白色(主要是内部填充)。
drawRect函数就是画了一个和窗口等大的白底框,如果没有这句代码,背景就是灰色的。
foreach就是把shapeList的图形都画出来。比如我们先画了一个矩形,再画直线,直线update的时候就会把矩形给覆盖掉。所以每次刷新都需要把之前的图案再次画出来。
在mainwindow.cpp文件中的MainWindow::MainWindow函数添加画板。
//定义一块画图板
PaintWidget *paintWidget = new PaintWidget(this);
//设置为中心窗口
setCentralWidget(paintWidget);
参考文章:
Qt 一个简易画板的实现(QWidget)