前言:虽然Qt是跨平台的C++开发框架,但是Qt的很多能力都是操作系统提供的,这就说明程序是运行在操作系统上的,需要系统给我们提供支撑。
一.事件
1.概述
事件类似于信号槽,用户进行的各种操作,会产生事件,我们同样可以给事件关联上对应的处理函数(处理逻辑),当事件触发时,就能执行对应的代码。
实际上,信号槽是对事件的进一步封装,事件是信号槽的底层机制。
大多数情况下,我们都可以通过Qt内置的信号槽来实现代码逻辑,但是架不住在某些特定情况下,Qt内置的信号不能满足要求,此时就需要我们来通过重写事件处理函数的形式,来手动实现逻辑。
在 Qt 中使用一个对象来表示一个事件。所有的 Qt 事件均继承于抽象类 QEvent。
常见事件属性如下:
鼠标事件 :鼠标左键、鼠标右键、鼠标滚轮,鼠标的移动,鼠标按键的按下和松开
键盘事件 :按键类型、按键按下、按键松开
定时器事件 :定时时间到达
进⼊离开事件: 鼠标的进⼊和离开
滚轮事件 :鼠标滚轮滚动
绘屏事件 :重绘屏幕的某些部分
显示隐藏事件 :窗口的显示和隐藏
移动事件: 窗口位置的变化
窗口事件: 是否为当前窗口
大小改变事件: 窗口大小改变
焦点事件: 键盘焦点移动
拖拽事件: 用鼠标进行拖拽
2.处理事件
让一段代码和某个事件关联起来,这样当事件触发时,就能执行对应的代码。
不同于信号槽通过connect完成关联,事件处理是通过重写对应的事件处理函数来实现的。
这里要用到**"多态"** 机制,创建子类,继承自Qt已有的父类,在子类中重写父类的事件处理函数,后续事件触发时,就会通过多态机制,执行子类中的函数。
值得注意的是,这里创建的子类,需要是对应的触发事件的控件,即想要哪个控件来触发事件,就创建对应的控件子类,在该子类中重写对应的函数。
下面实现一下,当鼠标进入enterEvent()函数,离开leaveEvent()函数两个事件。
我们要实现鼠标进入、离开该标签 时,能够触发事件,由于上述两个函数的父类为QWidget ,所以只需要创建一个对应的子类,继承自QLabel父类,进而重写对应的两个函数。
cpp
void Label::enterEvent(QEvent* event)
{
(void)event;
qDebug() << "鼠标进入";
}
void Label::leaveEvent(QEvent* event)
{
(void)event;
qDebug() << "鼠标离开";
}
还有一点要注意的是,我们通过Qt designer创建的Label,默认的基类为QLabel:
所以我们自定义的子类是无法关联上该控件的,所以我们需要修改它的基类:
右键点击控件,能够看到有一个"提升为":
填写我们自己创建的子类的类名及头文件 ,点击**"添加"** 、再点击**"提升"**,就可以修改其基类了。
结果如下:
通过qDebug可以在输出窗口打印对应的提示信息。
二.事件类型
下面分享一些常见的事件类型。
1.鼠标事件
void QWidget::mousePressEvent(QMouseEvent* event);//鼠标点击事件
void QWidget::mouseReleaseEvent(QMouseEvent* event);//鼠标释放事件
void QWidget::mouseDoubleClickEvent(QMouseEvent* event);//鼠标双击事件
void QWidget::mouseMoveEvent(QMouseEvent* event);//鼠标移动事件
void QWidget::wheelEvent(QWheelEvent* event);//鼠标滚轮滚动事件
2.键盘事件
void QWidget::keyPressEvent(QKeyEvent* event);//键盘按下事件
如果想要处理组合按键 事件,需要调用event对象中的modifiers()函数 ,获取用户按下的快捷键,随后与Qt::KeyboardModifier 中定义的处理键盘事件时对应的修改键进行比较:
Qt::NoModifier :无修改键
Qt::ShiftModifier :Shift 键
Qt::ControlModifier :Ctrl 键
Qt::AltModifier :Alt 键
Qt::MetaModifier :Meta键(在Windows上指Windows键,在macOS上指Command键)
Qt::KeypadModifier :使用键盘上的数字键盘进行输入时,Num Lock键处于打开状态
Qt::GroupSwitchModifier :用于在输入法组之间切换
例如获取Ctrl组合键,就需要进行如下判断:
event->modifiers() == Qt::ControlModifier
3.定时器事件
Qt 中在进行窗口程序的处理过程中,经常要周期性的执行某些操作,或者制作⼀些动画效果,使用定时器就可以实现。所谓定时器就是在间隔⼀定时间后,去执行某⼀个任务。定时器在很多场景下都会使用到,如弹窗自动关闭之类的功能等。
QTimerEvent类 用来描述⼀个定时器事件 。在使用时需要通过 startTimer() 函数 来开启⼀个定时器,这个函数需要输入⼀个以毫秒为单位 的整数作为参数来表明设定的时间,它返回的整型值代表这个定时器 。当定时器溢出时(即定时时间到达)就可以在timerEvent() 函数中获取该定时器的编号来进行相关操作。
void QObject::timerEvent(QTimerEvent* event);//定时器事件
4.窗口事件
void QWidget::moveEvent(QMoveEvent* event);//窗口移动事件
void QWidget::resizeEvent(QResizeEvent* event);//窗口大小事件
三.Qt文件
文件操作是应用程序必不可少的部分。Qt 作为⼀个通用开发库,提供了跨平台的文件操作能力。 Qt提供了很多关于文件的类,通过这些类能够对文件系统进行操作,如文件读写、文件信息获取、文件复制或重命名等。
在 Qt 中,文件读写的类为 QFile 。QFile 的父类为 QFileDevice ,QFileDevice 提供了文件交互操作的底层功能。 QFileDevice 的父类是 QIODevice ,QIODevice 的父类为 QObject。
QIODevice 是 Qt 中所有输入输出设备(input/output device,简称 I/O 设备)的基础类。
1.QFile
在 Qt 中,文件的读写主要是通过 QFile 类来实现。在 QFile 类中提供了⼀些用来读写文件的方法。对于文件的操作主要有:
读数据:QFile 中提供了多个方法用于读取文件内容;如 read()、readAll()、readLine()等。
写数据:QFile 中提供了多个方法用于往文件中写内容;如 write()、writeData()等。
关闭文件:文件使用结束后必须用函数 close() 关闭文件。
访问⼀个设备之前,需要使用 open() 函数打开该设备,而且必须指定正确的打开模式,QIODevice 中所有的打开模式由 QIODevice::OpenMode 枚举变量定义,其取值如下:
QIODevice::NotOpen :没有打开设备
QIODevice::ReadOnly :以只读方式打开设备
QIODevice::WriteOnly :以只写方式打开设备
QIODevice::ReadWrite :以读写方式打开设备
QIODevice::Append :以追加方式打开设备,数据将写到文件末尾
QIODevice::Truncate :每次打开文件后重写文件内容,原内容将被删除
QIODevice::Text :在读文件时,行尾终止符会被转换为 '\n';当写入文件时,行尾终止符会被转换为本地编码。如 Win32上为'\r\n';
QIODevice::Unbuffered :无缓冲形式打开文件,绕过设备中的任何缓冲区
QIODevice::NewOnly :文件存在则打开失败,不存在则创建文件
具体的文件操作方式如下:
1.根据文件路径,来创建一个QFile对象:
QFile file(path);
2.选择文件打开方式,打开文件,需要判断打开文件是否成功:
if(!(file.open(打开方式 )))
{}
3.读取/写入文件,可以使用QString对象来接收读取到的数据(前提为文本文件):
QString text = file.readAll();//读文件
const QString text = xxxx;//得到要写入的信息
file.write(text);//写文件
4.关闭文件
file.close();
2.文件和目录信息
QFileInfo 是 Qt 提供的⼀个用于获取文件和目录信息的类,如获取文件名、文件大小、文件修改日期等。QFileInfo类中提供了很多的方法,常用的有:
isDir() :检查该文件是否是目录;
isExecutable() :检查该文件是否是可执行文件;
fileName() :获得文件名;
completeBaseName() :获取完整的文件名;
suffix() :获取文件后缀名;
completeSuffix() :获取完整的文件后缀;
size() :获取文件大小;
isFile() :判断是否为文件;
fileTime() :获取文件创建时间、修改时间、最近访问时间等;
四.Qt多线程
在Qt中,多线程的处理⼀般是通过 QThread类 来实现。QThread 代表⼀个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。QThread对象管理程序中的⼀个控制线程。
常用的方法如下:
run() :
线程的入口函数。
start() :
通过调用 run() 开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,这个函数什么也不做。
currentThread() :
返回⼀个指向管理当前执行线程的 QThread的指针。
isRunning() :
如果线程正在运行则返回true;否则返回false。
sleep() / msleep() / usleep() :
使线程休眠,单位为秒 / 毫秒 / 微秒
wait() :
阻塞线程,直到满足以下任何⼀个条件:
- 与此 QThread 对象关联的线程已经完成执行(即当它从run()返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。
- 已经过了几毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回 false。
- 这提供了与 POSIX pthread_join() 函数类似的功能。
terminate() :
终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调度策略。在terminate() 之后使用 QThread::wait() 来确保。
finished() :
当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。
1.创建线程
创建线程的步骤:
- 自定义⼀个类,继承于 QThread,并且只有⼀个线程处理函数(和主线程不是同⼀个线程),这个线程处理函数主要就是重写父类中的 run() 函数。
- 线程处理函数里面写入需要执行的复杂数据处理;
- 启动线程不能直接调用 run() 函数,需要使用对象来调用start() 函数实现线程启动;
- 线程处理函数执行结束后可以定义⼀个信号来告诉主线程;
- 最后关闭线程。
线程函数内部不允许操作 UI 图形界面,⼀般做数据处理;只有主线程才可以操作UI界面。
2.线程安全
在Qt中,同样封装了对应的锁来维护线程的安全问题。
实现线程互斥和同步常用的类有:
- 互斥锁:QMutex、QMutexLocker(智能锁)
- 条件变量:QWaitCondition
- 信号量:QSemaphore
- 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
Qt锁的使用方式与Linux一致。