3 Qt事件和文件
3.1 自定义控件封装
- 开发中,常需要自定义封装控件,如滑动条和选值框进行封装,实现拖动滑动条,改变选值框的内容;改变选值框,滑动框也跟着改变。
-
封装步骤:右击项目->添加新文件->Qt->Qt设计师界面类->选模板为Widget空模板->设类名。
-
在新类即为Widget类的提升类,将滑动条和选值框拖动到UI界面即可。在项目中添加Widget控件后,右击->提升为->输入刚刚类的名称->如果勾选全局包含下次可以直接提升。
-
提升后类变为新类名,此时虽然在项目UI界面没有显示,但是实际上存在,下面需要设定新类的一些内部关联,使用信号和槽监听,由于现在在项目中很难访问到封装的新控件中的小控件,也需要设定一些对外的接口。
-
内部关联写在
my_first_item.cpp
的默认构造中。
//设定范围
ui->spinBox->setRange(10,1000);
ui->horizontalSlider->setRange(10,1000);
//有重载,需要设函数指针
void (QSpinBox::* spSignal)(int)=&QSpinBox::valueChanged;
//当选值框改变时,改变滑动条
connect(ui->spinBox,spSignal,ui->horizontalSlider,&QSlider::setValue);
//当滑动条改变时,改变选值框
connect(ui->horizontalSlider,&QSlider::valueChanged,ui->spinBox,&QSpinBox::setValue);
//设定选值框初始值
ui->spinBox->setValue(500);
- 对外接口写在
my_first_item.cpp
中。
//设定选值框数值
void my_first_item::setNum(int num)
{
ui->spinBox->setValue(num);
}
//获取选值框数值
int my_first_item::getNum()
{
return ui->spinBox->value();
}
- 在项目中可以设定获取值的按钮和设定值的按钮,利用对外接口得到数据。
//按下获取值按钮,获取选值框数值并放进QLabel中
connect(ui->getValue,&QPushButton::clicked,[=](){
ui->getLabel->setText(QString::number(ui->widget->getNum()));
});
//按下值设为按钮,将单行编辑框数值设顶为选值框数值
connect(ui->setValue,&QPushButton::clicked,[=](){
ui->widget->setNum(ui->lineEdit->text().toInt());
});
- 注意点:
QString
类型与int
类型互转QString
->int
使用.toInt()
方法。int
->QString
使用静态成员函数QString::number(int num)
强转。
3.2 鼠标事件
-
当鼠标在控件上经过、点击的时候,QT都可以记录鼠标的动作,包括哪个键按下,坐标是多少,等等。记录鼠标事件也需要自行封装控件并提升类。
-
本案例先讲解
QWidget
类的enterEvent(QEvent *event)
和leaveEvent(QEvent *event)
方法,用于记录鼠标的进入控件和离开控件动作。再讲解QLabel
类的mouseMoveEvent(QMouseEvent *ev)
mousePressEvent(QMouseEvent *ev)
和mouseReleaseEvent(QMouseEvent *ev)
方法,用于记录鼠标的移动、点击和松开动作。
类名 | 方法 | 功能 |
---|---|---|
QWidget | void enterEvent(QEvent *event) | 记录鼠标进入窗体 |
void leaveEvent(QEvent *event) | 记录鼠标离开窗体 | |
QLabel | void mouseMoveEvent(QMouseEvent *ev) | 记录鼠标在标签中移动 |
void mousePressEvent(QMouseEvent *ev) | 记录鼠标在标签中点击 | |
void mouseReleaseEvent(QMouseEvent *ev) | 记录鼠标在标签中松开 |
labelTracking::labelTracking(QWidget *parent) : QLabel(parent)
{
//设定一直监控鼠标移动(默认是鼠标点击后才会监听)
//setMouseTracking(true);
}
void labelTracking::enterEvent(QEvent *event)
{
qDebug()<<"鼠标进入了~";
}
void labelTracking::leaveEvent(QEvent *event)
{
qDebug()<<"鼠标离开了~";
}
void labelTracking::mouseMoveEvent(QMouseEvent *ev)
{
if(ev->buttons()&Qt::LeftButton)
{
QString str=QString("鼠标移动到 x=%1 y=%2 globalX=%3 globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug()<<str;
}
}
void labelTracking::mousePressEvent(QMouseEvent *ev)
{
if(ev->button()==Qt::LeftButton)
{
QString str=QString("鼠标点击到 x=%1 y=%2 globalX=%3 globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug()<<str;
}
}
void labelTracking::mouseReleaseEvent(QMouseEvent *ev)
{
if(ev->button()==Qt::LeftButton)
{
QString str=QString("鼠标离开到 x=%1 y=%2 globalX=%3 globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug()<<str;
}
}
-
注意点
-
输出时使用了
QString("%1 %2...").arg(...).arg(...)
的方法,类似链式编程,其中%1
对应第一个.arg()
,%2
对应第二个.arg()
,以此类推,可以很方便构建字符串。 -
QMouseEvent
类的x()
属性是返回该控件中鼠标的x坐标,globalX()
属性是返回整个窗体中鼠标的x坐标。 -
QMouseEvent
类的button()
属性返回一个枚举值,用于记录鼠标瞬时发生的动作是由什么按键产生的,如点击,松开。 -
QMouseEvent
类的buttons()
属性返回一个枚举值,用于记录鼠标持久发生的动作是由什么按键产生的,如移动。 -
换句话来说,鼠标只有在点击那一刹那,
button()
属性才会为左键和右键的枚举值,在移动状态下button()
属性为Qt::NoButton
,相反,buttons()
属性记录了鼠标每时每刻的键值。 -
本例中如果要记录是鼠标左键执行点击和松开操作的,只需要比对
button()
属性和Qt::LeftButton
枚举值即可,但如果要记录是鼠标左键执行移动操作的,需要比对buttons()
属性和Qt::LeftButton
枚举值,可以用位操作符&
,此时只要移动的时候包含左键(如左右键一起移动)都可以触发。Constant Value Qt::NoButton 0x00000000 Qt::LeftButton 0x00000001 Qt::RightButton 0x00000002 Qt::MidButton 0x00000004
-
3.3 定时器1
- 如制作闹钟软件需要用到定时器,本案例讲解的是
virtual void QTimer::timerEvent(QTimerEvent *e) override
方法,通过在窗口中重写该函数并设定时间间隔,实现读秒。上面是每1秒+1,下面是每2秒+1。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
id1=startTimer(1000); //单位为ms
id2=startTimer(2000);
}
void MainWindow::timerEvent(QTimerEvent *e)
{
static int num_1=0;
static int num_2=0;
if(e->timerId()==id1)
ui->timeLabel_1->setText(QString::number(num_1++));
if(e->timerId()==id2)
ui->timeLabel_2->setText(QString::number(num_2++));
}
- 注意点
- 在函数中设定了两个成员变量
id1
和id2
,在程序开始运行时,设定两个计时器开始动作,并分配好各计时器id
值,在每次发生timerEvent
事件时,比对id
号是否为当前事件的timerId
。 - 两个读秒值需要设定为静态成员变量,避免多次定义无法增加值。
- 在函数中设定了两个成员变量
3.4 定时器2
- 第二种定时器的实现相对简单,需要添加
<QTimer>
类,并在主窗口的构造函数中声明类对象。
//第二种定时器
QTimer *timer=new QTimer(this);
static int num_3;
//开始计时,间隔为0.5秒
timer->start(500);
//每到间隔计时器会发送信号,通过信号和槽连接
connect(timer,&QTimer::timeout,[=](){
ui->timeLabel_3->setText(QString::number(num_3++));
});
//按下暂停键暂停计数
connect(ui->btn,&QPushButton::clicked,[=](){
timer->stop();
});
- 功能也较第一种定时器有提升,设置了暂停功能,且无需记录定时器序号,对于不同的定时器可以声明不同的类对象即可。
3.5 事件分发器bool event(QEvent *e)
- 由QT到各事件的处理中间要交由事件分发器
bool event(QEvent *e)
处理,如果返回值是true
则代表用户处理这个事件,不向下分发了,即被拦截;如果返回值是false
,分发到对应事件处理程序。 - 本案例中重写了
event
函数,拦截了鼠标按下操作。 - 通过
e->type()
属性可以判断用户的事件类型,是一个枚举值。
bool labelTracking::event(QEvent *e)
{
if(e->type()==QEvent::MouseButtonPress)
{
QMouseEvent *ev=static_cast<QMouseEvent *>(e);
QString str=QString("拦截到鼠标点击 x=%1 y=%2 globalX=%3 globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug()<<str;
//拦截事件,不向下分发
return true;
}
//否则交由父类处理
return QLabel::event(e);
}
- 注意点
- 拦截的事件类型为
QEvent
,需要使用static_cast<>()
函数强转为其子类QMouseEvent
,再使用其坐标属性。 - 一般很少自定义函数来拦截。
- 拦截的事件类型为
3.6 事件过滤器bool eventFilter(QObject* obj,QEvent* e)
- 通过事件过滤器,可以在程序分发到
event
事件分发器之前再做一次高级拦截。 - 步骤:给控件安装事件过滤器->重写
eventFilter
事件。
//1.给label控件安装事件过滤器
ui->label->installEventFilter(this);
//2.重写eventFilter事件
bool MainWindow::eventFilter(QObject* obj,QEvent* e)
{
//判断是否为监听的label控件
if(obj==ui->label)
{
//判断事件是否为鼠标点击
if(e->type()==QEvent::MouseButtonPress)
{
QMouseEvent *ev=static_cast<QMouseEvent *>(e);
QString str=QString("eventFilter拦截到鼠标点击 x=%1 y=%2 globalX=%3 globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug()<<str;
//拦截事件,不向下分发
return true;
}
}
//否则交由父类处理
return QWidget::eventFilter(obj,e);
}
- 与事件分发器不同的是,这次的安装事件过滤器和重写
eventFilter
事件都是写在mainWindow.cpp
下的,而event
事件分发器拦截是写在对应控件中的。
3.7 绘图事件
- QT可以在界面上绘图,如直线椭圆文字等等,都会自动在
paintEvent(QPaintEvent* )
中调用,需要在主窗口函数中重写该函数。
void MainWindow::paintEvent(QPaintEvent* )
{
//this指的是绘图设备
QPainter *painter=new QPainter(this);
//定义画笔
QPen *pen=new QPen;
//设定画笔颜色
pen->setColor(QColor(0,0,255));
//设定画笔风格(枚举值)
pen->setStyle(Qt::DotLine);
//设定笔宽
pen->setWidth(3);
//画家用画笔
painter->setPen(*pen);
//定义画刷
QBrush *brush=new QBrush;
//设定画刷颜色(枚举值)
brush->setColor(Qt::magenta);
//设定画刷风格(枚举值)
brush->setStyle(Qt::DiagCrossPattern);
//画家用画刷
painter->setBrush(*brush);
//画直线
painter->drawLine(QPoint(0,0),QPoint(100,100));
//画椭圆
painter->drawEllipse(QPoint(100,100),100,50);
//画矩形
painter->drawRect(200,200,100,100);
//写文字
painter->drawText(QPoint(300,100),"好好学习,天天向上");
}
-
注意点
-
设定完画刷就会自动填充封闭区域。
-
本例中将画家设定为一个指针却没有释放,可能会导致内存泄露,因此最好将画家对象直接实例化,放在堆区即可。
-
3.8 绘图高级设置
- 绘图抗锯齿设置,可以使图像边缘更加柔和,但效率较低。
setRenderHint
方法里的参数可以从枚举值中查到。
painter->drawEllipse(QPoint(200,200),100,100);
//设置抗锯齿能力,但效率较低
painter->setRenderHint(QPainter::QPainter::Antialiasing);
painter->drawEllipse(QPoint(400,200),100,100);
- 移动画家位置方法
translate()
,保存和复原画家位置方法save()
restore()
。
painter->drawRect(50,50,100,100);
//将画家向右移动150个单位
painter->translate(150,0);
painter->drawRect(50,50,100,100);
//保存画家当前位置(150,0)
painter->save();
//将画家向再右移动150个单位
painter->translate(150,0);
//复原画家保存的位置
painter->restore();
//事实上这个矩形和第二个矩形重合了,所以只显示两个矩形
painter->drawRect(50,50,100,100);
3.9 手动调用绘图事件
- 绘图事件可以画资源图片。
void MainWindow::paintEvent(QPaintEvent* )
{
QPainter painter(this);
painter.drawPixmap(xPos,20,QPixmap(":/Image/aite.png"));
}
//实现按下按钮图片向右移动50单位,如果超出屏幕在最左侧重新显示
connect(ui->btn,&QPushButton::clicked,[=](){
xPos+=50;
if(xPos>width())
xPos=0;
update();
});
- 注意点
- 手动调用绘图事件方法即
update()
,重新执行绘图事件中的函数。 - 可设置定时器+手动绘图事件 使图片自动移动。
- 手动调用绘图事件方法即
QTimer *timer=new QTimer(this);
timer->start(5);
connect(timer,&QTimer::timeout,[=]{
xPos+=1;
if(xPos>width())
xPos=0;
update();
});
3.10 绘图设备PainterDevice
-
绘图设备指继承
PainterDevice
的子类。上文提到的绘图设备都选的是当前窗口,本案例中介绍四种新的绘图设备:QPixmap
QBitmap
QImage
QPicture
,在这几种绘图设备上绘图可以保存到磁盘上。类名 介绍 QPixmap 专门为图像在屏幕上的显示做了优化 QBitmap 是QPixmap的一个子类,它的色深限定为1 QImage 可以对图像的各个像素进行访问 QPicture 可以记录和重现QPainter的各条命令
QPixmap
- 无需在绘图事件中编写代码,直接在主窗口cpp里就可以绘制并保存。
//QPixmap绘图设备
QPixmap pix(300,300);
pix.fill(Qt::white);
QPainter painter(&pix);
painter.setPen(QPen(Qt::blue));
painter.drawEllipse(QPoint(150,150),100,100);
pix.save("E:/pix.png");
QImage
- 与
QPixmap
不同的是QImage
绘图设备在实例化时需要指定格式(枚举值),比较常用的是32位RGBQImage::Format_RGB32
。
//QImage绘图设备
QImage img(300,300,QImage::Format_RGB32);
img.fill(Qt::white);
QPainter painter(&img);
painter.setPen(QPen(Qt::magenta));
painter.drawEllipse(QPoint(150,150),100,100);
img.save("E:/img.png");
QImage
还独有对图像的各个像素进行访问功能,使用.setPixelColor(int x,int y,QColor col)
方法即可。
//利用QImage对像素进行修改
QImage img;
img.load(":/Image/aite.png");
QPainter painter(this);
//修改像素点
for(int i=50;i<100;i++)
{
for (int j=50;j<100;j++)
{
img.setPixelColor(i,j,QColor(255,255,0));
}
}
img.save("E:/edited.png");
QPicture
QPicture
设备可以记录和重现QPainter的各条命令。- 在开始时,需要使用
begin()
方法,绘图结束时需要使用end()
方法。
//QPicture绘图设备
QPicture pic;
QPainter painter;
painter.begin(&pic);
painter.setPen(QPen(Qt::cyan));
painter.drawEllipse(QPoint(150,150),100,100);
painter.end();
pic.save("E:/pic.lh");
- 在保存时,可以保存为任意格式,这样在下次读取时,再读取该文件,可以重现上次的所有命令。
void MainWindow::paintEvent(QPaintEvent* )
{
//重现QPicture绘图指令
QPainter painter(this);
QPicture pic;
pic.load("E:/pic.lh");
painter.drawPicture(0,0,pic);
}
3.11 QFile文件读写操作
读文件
- QT中读取文件的方式与C++类似,需要包含
<QFile>
类,本案例中要求实现点击“选择文件”按钮弹出选择文件对话框,选择完成后在单行文字显示文字路径,在下方文字框中显示读取内容。
//点击按钮弹出文件对话框
connect(ui->pushButton,&QPushButton::clicked,[=]{
QString path=QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\Divin\\Desktop");
//将路径放到lineEdit中
ui->lineEdit->setText(path);
//QFile默认支持的格式是utf_8
QFile file(path);
//设置对文件操作类型(枚举值)
file.open(QIODevice::ReadOnly);
//将所有数据读到手
QByteArray arr=file.readAll();
//将读取到的数据放到textEdit
ui->textEdit->setText(arr);
file.close();
});
- 注意点
- 读取前需要设置读的操作类型(枚举值)。读取到的数据类型为
QByteArray
,而setText
方法需要的格式是QString
,因此这里包含一个隐式类型转换QByteArray->QString
。 - 上面读取的方法是
readAll()
全部读到手,也可使用readLine()
方法逐行读,再用atEnd()
方法判断是否读到末尾。
- 读取前需要设置读的操作类型(枚举值)。读取到的数据类型为
//逐行读
QByteArray arr;
while(!file.atEnd())
{
arr+=file.readLine();
}
QFile
默认支持的格式是utf_8
,如果txt
文件的编码格式是gbk
,需要使用编码格式类QTextCodec
进行转换。
//编码格式类
QTextCodec* codec=QTextCodec::codecForName("gbk");
//QFile默认支持的格式是utf_8
QFile file(path);
file.open(QIODevice::ReadOnly);
//将所有数据读到手
QByteArray arr=file.readAll();
//将读取到的数据经转码后放到textEdit
ui->textEdit->setText(codec->toUnicode(arr));
写文件
- 写文件的方法更加简单,设定好写的操作类型,再使用
write
方法即可。
//用追加方式写文件
file.open(QIODevice::Append);
file.write("\n啦啦啦");
file.close();
3.12 QFileInfo文件信息读取
-
利用
QFileInfo
类可以获取文件的一些详细信息,如后缀名、创建时间、修改时间等。常用接口 方法名 数据类型 大小(字节) size() qint64 后缀名 suffix() Qstring 文件路径 filePath() Qstring 文件名 fileName() Qstring 创建日期 birthTime() QDateTime 最后修改日期 lastModified() QDateTime
QFileInfo info(path);
qDebug()<<"文件大小:"<<info.size()<<"\n后缀名:"<<info.suffix();
qDebug()<<"文件路径:"<<info.filePath()<<"\n文件名:"<<info.fileName();
qDebug()<<"创建日期:"<<info.birthTime().toString("yyyy/MM/dd hh:mm:ss")<<"\n修改日期:"<<info.lastModified().toString("yyyy/MM/dd hh:mm:ss");
- 注意点
- 创建日期和最后修改日期的数据类型都是
QDateTime
,需要先包含该头文件,再使用toString
方法,格式可以查帮助文档,如toString("yyyy/MM/dd hh:mm:ss")
可以完整输出文件的年月日,时秒分的信息。
- 创建日期和最后修改日期的数据类型都是