2024-06-15-Pyside6高级操作01

1. 文件操作

1.1数据读写

在 PySide6 中对文件和文件夹的进行操作时,主要使用 QFile 类、QFileInfo 类和 QDir 类。

在 PySide6 窗口程序对文件或者文件夹进行操作时,不强制要求必须使用 PySide6 中提供的 QFile、QDir 等类,也可以使用 Python 内置的文件对象。

1.1.1 QIODevice类

把计算过程中的数据保存下来或者读取已有数据是任何程序都需要进行的工作,PySide6 把文件当作输入输出设备,可以把数据写到设备中,或者从设备中读取数据,从而达到读写数据的目的。可以利用 QFile 类调用 QIODevice 类的读写方法直接进行读写,或者把 QFile 类和 QTextStream 类结合起来,用文本流(text stream)的方法进行文本数据的读写,还可以把 QFile 类和 QDataStream 类结合进来,用数据流(data stream)的方法进行二进制数据的读写。

QIODevice 类是抽象类,是执行读数据和写数据类(如 QFile、QBuffer)的基类,它提供读数据和写数据的接口。QIODevice 类在 QtCore 模块中。直接或间接继承自 QIODevice,与本地读写文件有关的类有 QBuffer、QFile、QFileDevice、QProcess、QSaveFile、QTemporaryFile,这些类之间的继承关系下所示。

与文件读写有关的类

另外还有网络方面的读写类 QAbstractSocket、QLocalSocket、QNetworkReply、QSslSocket、QTcpSocket 和 QUdpSocket。QIODevice 类提供读写接口,但是不能直接使用 QIODevice 类进行数据的读写,而是使用子类 QFile 或 QBuffer 的继承自 QIODevice 的读写方法来进行数据读写。在一些系统中,将所有的外围设备都当作文件来处理,因此可以读写的类都可以当作设备来处理。

QIODevice 类的常用方法如下:

open(mode:QIODeviceBase.OpenMode) -> bool               # 打开设备,成功返回True
isOpen() -> bool                                        # 判断设备是否打开
setOpenMode(openMode:QIODeviceBase.OpenMode) -> None    # 打开设备后,重新设置打开模式
close() -> None                                         # 关闭设备   

setTextModeEnabled(enabled:bool) -> None                # 设置是否是文本模式

read(maxlen:int) -> QByteArray                          # 读取指定数量的字节数据
readAll() -> QByteArray                                 # 读取全部数据
readLine(maxlen:int) -> QByteArray                      # 读取一行数据

getChar(c:bytes) -> bool                                # 读取一个字符,并存储到c中
ungetChar(c:str) -> None                                # 将字符重新存储到设备中
peek(maxlen:int) -> QByteArray                          # 读取指定数量的字节
write(data:Union[QByteArray, bytes]) -> int             # 写入字符串,返回实际写入的字节数量
writeData(data:bytes, len:int) -> int                   # 写入数据,返回实际写入的字节数量   
putChar(c:str) -> bool                                  # 写入一个字符

setCurrentWriteChannel(channel:int) -> None             # 设置当前的写入通道
currentWriteChannel() -> int                            # 获取当前的写入通道
writeChannelCount() -> int                              # 获取写入数据的通道数量

setCurrentReadChannel(channel:int) -> None              # 设置当前的读取通道
currentReadChannel() -> int                             # 获取当前的读取通道
readChannelCount() -> int                               # 获取读取数据的通道数量

canReadLine() -> bool                                   # 获取是否可以按行读取
bytesToWrite() -> int                                   # 获取缓存中等待写入的字节数量
bytesAvailable() -> int                                 # 获取可读取的字节数量

setErrorString(errorString:str) -> None                 # 设置设备的出错信息
errorString() -> st                                     # 获取设备的出错信息

isWritable() -> bool                                    # 获取设备是否可以写入
isReadable() -> bool                                    # 获取设备是否可以读取
isSequential() -> bool                                  # 获取设备是否是顺序读取
isTextModeEnabled() -> bool                             # 获取设备是否能够以文本方式读写

atEnd() -> bool                                         # 获取是否已经到达文件末尾
seek(pos:int) -> None                                   # 移动到指定的位置
pos() -> int                                            # 获取当前位置
reset() -> bool                                         # 重置设备,回到起始位置

startTransaction() -> None                              # 对随机设备记录当前位置,对顺序设备,在内部赋值读取的数据以便恢复数据
rollbackTransaction() -> None                           # 回到调用startTransaction()时的位置
commitTransaction() -> None                             # 对顺序设备,放弃记录的数据
isTransactionStarted() -> bool                          # 获取是否开始记录位置

size() -> int                                           # 获取文件大小
skip(maxSize:int) -> int                                # 跳过指定数量的字节数据

waitForBytesWritten(msecs:int) -> bool                  # 对于缓存设备,该方法需要将数据写到设备或进故宫msecs毫秒后返回值
waitForReadyRead(msecs:int) -> bool                     # 当有数据可以读取前或经过msecs毫秒会阻止设备的运行

QIODevice 的子类 QFile、QBuffer 和 QTcpSocket 等需要用 open(QIODeviceBase.OpenMode) 方法打开一个设备,用 close() 方法关闭设备。打开设备时需要设置打开模式,参数 QIODeviceBase.OpenMode 可取的值如下所示,可以设置只读、只写、读写、追加和不使用缓存等模式,如果同时要选择多个选项可以用“|”连接,用openMode()方法获取打开模式。

QIODeviceBase.OpenModeFlag.NotOpen          # 还未打开
QIODeviceBase.OpenModeFlag.ReadOnly         # 以只读的方式打开
QIODeviceBase.OpenModeFlag.WriteOnly        # 以只写的方式打开,如果文件不存在,则创建新文件
QIODeviceBase.OpenModeFlag.ReadWrite        # 以读写的方式打开,如果文件不存在,则创建新文件
QIODeviceBase.OpenModeFlag.Append           # 以追加的方式打开,新增加的内容将被追加到文件末尾
QIODeviceBase.OpenModeFlag.Truncate         # 以重写的方式打开,在写入新的数据是会将原有数据全部清除,指针指向文件开头
QIODeviceBase.OpenModeFlag.Text             # 在读写时,将行结束符转换成\n,在写入时将行结束符转换成本地格式
QIODeviceBase.OpenModeFlag.Unbuffered       # 不使用缓存
QIODeviceBase.OpenModeFlag.NewOnly          # 创建和打开新文件时,只用于QFile设备,如果文件存在,打开将会失败,该模式是只写模式
QIODeviceBase.OpenModeFlag.ExistingOnly     # 打开文件时,如果文件不存在会出现错误,只适用于QFile设备

读写设备分为两种,一种是 随机设备(random-access device),另一种是 顺序设备(sequential device),用 isSequential() 方法可以判断设备是否是顺序设备。QFile 和 QBuffer 是随机设备,QTcpSocket 和 QProcess 是顺序设备。随机设备可以获取设备指针的位置,将指针指向指定的位置,从指定位置读取数据;而顺序设备只能依次读取数据。随机设备可以用 seek(pos:int) 方法定位,用 pos() 方法获取位置。

读取数据的方法有 read(maxlen:int)readAll()readData(data:bytes,maxlen:int)readLine(maxlen:int=0)readLineData(data:bytes,maxlen:int)getChar(c:bytes)peek(maxlen:int)read(maxlen:int) 表示读取指定长度的数据;readLine(maxlen:int=0) 表示读取行,参数 maxlen 表示允许读取的最大长度,若为 0 表示不受限制。写入数据的方法有 write(QByteArray)writeData(bytes)putChar(c:str)getChar(c:bytes)putChar(c:str) 只能读取和写入一个字符。如果要继承 QIODevice 创建自己的读写设备,需要重写 readData()writeData() 函数。

一些顺序设备支持多通道读写,这些通道表示独立的数据流,可以用 setCurrentReadChannel(int) 方法设置读取通道,用 setCurrentWriteChannel(int) 方法设置写入通道,用 currentReadChannel() 方法和 currentWriteChannel() 方法获取读取和写入通道。

1.1.2 QByteArray类

在利用 QIODevice 的子类进行读写数据时,通常返回值或参数是 QByteArray 类型的数据。QByteArray 用于存储二进制数据,至于这些数据到底表示什么内容(字符串、数字、图片或音频等),完全由程序的解析方式决定。如果采用合适的字符编码方式(字符集),字节数组可以恢复成字符串,字符串也可以转换成字节数组。字节数组会自动添加 "\0" 作为结尾,统计字节数组的长度时,不包含末尾的 "\0"。

用 QByteArray 创建字节数组的方法如下,其中 c 只能是一个字符。

QByteArray()
QByteArray(text:bytes, size:int=-1)
QByteArray(text:Union[QByteArray, bytes, bytearray str])
QByteArray(size:int, c:str)

用 Python 的 str(QByteArray,encoding="utf-8") 函数可以将 QByteArray 数据转换成 Python 的字符串型数据。用 QByteArray 的 append(str) 方法可以将 Python 的字符串添加到 QByteArray 对象中,同时返回包含字符串的新 QByteArray 对象。

Python3.x 中新添加了字节串 bytes 数据类型,其功能与 QByteArray 的功能类似。如果一个字符串前面加 "b",就表示是 bytes 类型的数据,例如 b'hello'。bytes 数据和字符串的对比如下:字节是计算机的语言,字符串是人类的语言,它们之间通过编码表形成对应关系。字符串由若干个字符组成,以字符为单位进行操作;bytes 由若干个字节组成,以字节为单位进行操作。bytes 和字符串除了操作的数据单元不同之外,它们支持的所有方法都基本相同。bytes 和字符串都是不可变序列,不能随意增加和删除数据。

xx=bytes("hello", encoding="utf-8") 方法可以将字符串 "hello" 转换成 bytes,用 yy=str(xx, encoding="utf-8")方法可以将 bytes 转换成字符串。bytes 也是一个类,用 bytes() 方法可以创建一个空 bytes 对象,用 bytes(int) 方法可以创建指定长度的 bytes 对象,用 decode(encoding="utf-8") 方法可以对数据进行解码,bytes 的操作方法类似于字符串的操作方法。

Python 中还有一个与 bytes 类似但是可变的数组 bytearray,其创建方法和字符串的转换方法与 bytes 相同,在 QByteArray 的各个方法中,可以用 bytes 数据的地方也可以用 bytearray。

QByteArray 类的常用方法:

# 实例方法
append(a:Union[QByteArray, bytes]) -> QByteArray                                                                # 在末尾追加数据
append(c:str) -> QByteArray                                                                                     # 在末尾追加字符串
append(count:int, c:str) -> QByteArray                                                                          # 在末尾追加字符串
append(s:bytes, len:int) -> QByteArray                                                                          # 在末尾追加字符串

at(i:int) -> str                                                                                                # 根据索引获取数据
chop(n:int) -> QByteArray                                                                                       # 获取从末尾移除len个字节后的字节数组
clear() -> None                                                                                                 # 清空所有字节

contains(bv:Union[QByteArray, bytes]) -> bool                                                                   # 获取是否包含指定的字节数组
contains(c:str) -> bool                                                                                         # 获取是否包含指定的字符串

count(bv:Union[QByteArray, bytes]) -> int                                                                       # 获取指定的字节数组的出现次数
count() -> int                                                                                                  # 获取字节数组的长度
size() -> int                                                                                                   # 获取字节数组的长度

data() -> bytes                                                                                                 # 获取字符串

startsWith(bv:Union[QByteArray, bytes]) -> bool                                                                 # 获取是否以指定的字节数组开头
startsWith(c:str) -> bool                                                                                       # 获取是否以指定的字符串开头
endsWith(bv:Union[QByteArray, bytes]) -> bool                                                                   # 获取末尾是否时指定的字节数组
endsWith(c:str) -> bool                                                                                         # 获取末尾是否是指定的字符串

fill(c:str, size:int=-1) -> QByteArray                                                                          # 使数组的每个数据为指定的字符,将长度调整为size

indexOf(bv:Union[QByteArray, bytes], from:int=0) -> int                                                         # 获取指定的字节数组在数组中的位置
indexOf(c:str, from:int=0) -> int                                                                               # 获取指定的字符串在数组中的位置

insert(i:int, bv:Union[QByteArray, bytes]) -> QByteArray                                                        # 在指定的位置插入字节数组
insert(i:int, c:str) -> QByteArray                                                                              # 在指定的位置插入字符串
insert(i:int, count:int, c:str) -> QByteArray                                                                   # 在指定的位置插入字符串

isEmpty() -> bool                                                                                               # 是否为空,长度为0时返回True
isNull() -> bool                                                                                                # 内容为空返回True

toLower() -> QByteArray                                                                                         # 将所有字符转换为小写
isLower() -> bool                                                                                               # 判断是否全部都是小写
toUpper() -> QByteArray                                                                                         # 将所有字符转换为大写
isUpper() -> bool                                                                                               # 判断是否全部都是大写

lastIndexOf(bv:Union[QByteArray, bytes], from:int=-1) -> int                                                    # 获取最后索引值
lastIndexOf(c:str, from:int=-1) -> int                                                                          # 获取最后索引值

length() -> int                                                                                                 # 获取字节数组的长度
mid(index:int, len:int=-1) -> QByteArray                                                                        # 从指定位置获取指定长度的数据

prepend(a:Union[QByteArray, bytes]) -> QByteArray                                                               # 在开头插入字节数组
remove(index:int, len:int) -> QByteArray                                                                        # 从指定位置删除指定长度的数据
repeated(times:int) -> QByteArray                                                                               # 获取重复times次后的数据
replace(index:int, len:int, s:Union[QByteArray, bytes]) -> QByteArray                                           # 从指定位置替换指定长度的数据
replace(before:Union[QByteArray, bytes], after:Union[QByteArray, bytes]) -> QByteArray                          # 替换指定位置的数据

resize(size:int) -> None                                                                                        # 调整长度

setNum(arg__1:float, format:str="g", precision:int=6) -> QByteArray                                             # 将浮点数转换成科学计数法数据
setNum(arg__1:int, base:int=10) -> QByteArray                                                                   # 将整数转换成字节数组

split(sep:str) -> List[QByteArray]                                                                              # 用分割符将字节数组分割成列表
squeeze() -> None                                                                                               # 释放不存储数据的内存

toBase64(options:QByteArray.Base64Option=QByteArray.Base64Option.Base64Encoding) -> QByteArray                  # 将字节数组转换为Base64编码
toDouble() -> Tuple[float, bool]                                                                                # 转换成浮点数
toFloat() -> Tuple[float, bool]                                                                                 # 转换成浮点数
toHex(separator:str="0") -> QByteArray                                                                          # 转换成十六进制

toShort(base:int=10) -> Tuple[int, bool]                                                                        # 根据进制转换成整数 
toInt(base:int=10) -> Tuple[int, bool]                                                                          # 根据进制转换成整数
toLong(base:int=10) -> Tuple[int, bool]                                                                         # 根据进制转换成整数
toLongLong(base:int=10) -> Tuple[int, bool]                                                                     # 根据进制转换成整数

toUShort(base:int=10) -> Tuple[int, bool]                                                                       # 根据进制转换成整数
toUInt(base:int=10) -> Tuple[int, bool]                                                                         # 根据进制转换成整数
toULong(base:int=10) -> Tuple[int, bool]                                                                        # 根据进制转换成整数
toULongLong(base:int=10) -> Tuple[int, bool]                                                                    # 根据进制转换成整数

toPercentEncoding(exclude:QByteArray=QByteArray(), include:QByteArray=QByteArray(), percent="%") -> QByteArray  # 转换成百分比编码

simplified() -> QByteArray                                                                                      # 去除内部、开始和结尾的空白字符

left(len:int) -> QByteArray                                                                                     # 从左侧获取指定长度的字节
right(len:int) -> QByteArray                                                                                    # 从右侧获取指定长度的字节
truncate(pos:int) -> None                                                                                       # 截断前pos个字符数据

# 静态方法
# 从Base645编码中解码
fromBase64(base64:Union[QByteArray, bytes], options:QByteArray.Base64Option=QByteArray.Base64Option.Base64Encoding) -> QByteArray
# 从Base64编码中解码
fromBase64Encoding(base64:Union[QByteArray, bytes], options:QByteArray.Base64Option=QByteArray.Base64Option.Base64Encoding) -> QByteArray
# 从十六进制数据中解码
fromHex(hexEncoded:Union[QByteArray, bytes]) -> QByteArray
# 从百分号编码中解码
fromPercentEncoding(pctEncoded:Union[QByteArray, bytes], percent:str="%") -> QByteArray
# 用前size个原生字节构建字符数组
fromRawData(data:Union[QByteArray, bytes], size:int=-1) -> QByteArray
# 将整数转换为字节数组
number(arg__1:int, base:int=10) -> QByteArray
# 将浮点数转换为字节数组
number(arg__1:float, format:str="g", precision:int=6)

setNum(float,format='g',precision=6) 方法或 number(float,format='g',precision=6) 方法可以将浮点数转换成用科学计数法表示的数据,其中格式 format 可以取'e'、'E'、'f'、'g'或'G','e'表示的格式如 [-]9.9e[+|-]999,'E' 表示的格式如 [-]9.9E[+|-]999,'f' 表示的格式如 [-]9.9,如果取 'g' 表示视情况选择 'e' 或 'f',如果取 'G' 表示视情况选择 'E' 或 'f'。

用静态方法 fromBase64(Union[QByteArray,bytes],options=QByteArray.Base64Encoding) 可以把 Base64 编码数据解码,用 toBase64(options:QByteArray.Base64Option) 方法可以转换成 Base64 编码,其中参数 options 可以取值如下:

QByteArray.Base64Option.Base64Encoding
QByteArray.Base64Option.Base64UrlEncoding
QByteArray.Base64Option.KeepTrailingEquals
QByteArray.Base64Option.OmitTrailingEquals
QByteArray.Base64Option.IgnoreBase64DecodingErrors
QByteArray.Base64Option.AbortOnBase64DecodingErrors

1.1.3 QFile读取数据

QFile 继承自 QIODevice,会继承 QIODevice 的方法。QFile 可以读写文本文件和二进制文件,可以单独使用,也可以与 QTextStream 和 QDataStream 一起使用。

用 QFile 类创建实例对象的方法如下所示:

QFile()
QFile(parent:QObject)
QFile(name:Union[str, bytes, os.PathLike])
QFile(name:Union[str, bytes, os.PathLike], parent:QObject)

其中 parent 是继承自 QObject 的实例,str 是要打开的文件。需要注意的是,文件路径中的分隔符可以用 "/""\\",而不能用 "\"

QFile 类的常用方法:

# 实例方法
open(flags:QIODeviceBase.OpenMode) -> None                      # 按照模式打开文件
setFileName(name:Union[str, bytes, os.PathLike]) -> None        # 设置文件路径和名称
fileName() -> str                                               # 返回文件名称
flush() -> None                                                 # 将缓存中的数据写入到文件中
close() -> None                                                 # 关闭文件

# 静态方法
setPermissions(filename:QFileDevice.Permission)                 # 设置权限
exists(fileName:str) -> bool                                    # 检查文件是否存在
copy(fileName:str, newName:str) -> None                         # 复制打开的文件到新文件
remove(fileName:str) -> None                                    # 删除文件
rename(oldName:str, newName:str) -> None                        # 重命名文件

from PySide6.QtCore import QFile

if __name__ == "__main__":
    # 1.创建一个文件信息
    file_path = "./文件.txt"
    file = QFile(file_path)

    # 2.以只写模式打开文件
    file.open(QFile.OpenModeFlag.WriteOnly)

    # 3.向文件中写入内容
    file.write("你好,世界!\n".encode("utf-8"))
    file.write("hello world!\n".encode("utf-8"))

    # 4.关闭文件
    file.close()

    # 5.以只读模式打开文件
    file.open(QFile.OpenModeFlag.ReadOnly)

    # 6.判断是否读到末尾
    while not file.atEnd():
        # 7.从文件中读取一行
        text = file.readLine()
        print(str(text, encoding="utf-8").strip())

    # 8.关闭文件
    file.close()

1.2 用流方式读取数据

从本机上读写数据更简便的方法是用流(stream)方式,读写文本数据用文本流 QTextStream,读写二进制数据用数据流 QDataStream。用数据流可以将文本、整数和浮点数以二进制格式保存到文件中,也可以将常用的类的实例保存到文件中,可以从文件中直接读取类的实例。

1.2.1 文本流

用 QTextStream 定义文本流的方法如下所示,可以看出其连接的设备可以是 QIODevice 或 QByteArray。

QTextStream()
QTextStream(device:QIODevice)
QTextStream(array:Union[QByteArray, bytes], openMode=QIODeviceBase.ReadWrite)

QTextStream 类的常用方法如下:

setDevice(device:QIODevice) -> None                                 # 设置操作的设备
device() -> QIODevice                                               # 获取操作的设备

setEncoding(encoding:QStringConverter.Encoding) -> None             # 设置编码
encoding() -> QStringConverter.Encoding                             # 获取编码
setAutoDetectUnicode(enabled:bool) -> None                          # 设置自动检测编码
setGenerateByteOrderMark(generate:bool) -> None                     # 如果设置成True,那么在写入文件时,会写入BOM

setFieldWidth(width:int) -> None                                    # 设置数据流的宽度,如果为0,则宽度时数据的宽度
fieldWidth() -> int                                                 # 获取数据流的宽度

setFieldAlignment(alignment:QTextStream.FieldAlignment) -> None     # 设置数据在数据流内的对齐方式
fieldAlignment() -> QTextStream.FieldAlignment                      # 获取数据在数据流内的对齐方式

setPadChar(ch:str) -> None                                          # 设置填充字符
padChar() -> str                                                    # 获取填充字符

setIntegerBase(base:int) -> None                                    # 设置整数的进制
integerBase() -> int                                                # 获取整数的进制

setNumberFlags(flags:QTextStream.NumberFlags) -> None               # 设置数字的标识
numberFlags() -> QTextStream.NumberFlags                            # 获取数字的标识

setRealNumberNotation(notation:QTextStream.NumberNotation) -> None  # 设置实数的标记方法
realNumberNotation() -> QTextStream.NumberNotation                  # 获取实数的标记方法
setRealNumberPrecision(precision:int) -> None                       # 设置实数的精度
realNumberPrecision() -> int                                        # 获取实数的精度

setStatus(status:QTextStream.Status) -> None                        # 设置状态
status() -> QTextStream.Status                                      # 获取状态
resetStatus() -> None                                               # 重置状态

read(maxlen:int) -> str                                             # 读取指定长度的数据
readAll() -> str                                                    # 读取所有数据
readLine(maxlen:int=0) -> str                                       # 按行读取数据,maxlen是一次允许度的最大长度

seek(pos:int) -> None                                               # 定位到指定位置
pos() -> int                                                        # 获取当前位置
flush() -> None                                                     # 将缓存中的数据写到设备中
atEnd() -> bool                                                     # 判断是否已经到达文件末尾
skipWhiteSpace() -> None                                            # 忽略空字符,直到非空字符或达到末尾
reset() -> None                                                     # 重置除字符串和缓冲以外的其它设置

QTextStream 的连接设备可以在创建文本数据流时定义,也可以用 setDevice(QIODevice) 方法来定义,用 device() 方法获取连接的设备。QTextStream 与 QFile 结合可读写文本文件,与 QTcpSocket、QUdpSocket 结合可读写网络文本数据。

QTextStream 没有专门的写数据的方法,需要用流操作符 "<<" 来完成写入数据。"<<" 的左边是 QTextStream 实例,右边可以是字符串、整数或浮点数,如果要同时写入多个数据,可以把多个 "<<" 写到一行中。读取数据的方法有 read(int)readAll()readLine(maxLength=0) ,其中 maxLength 表示读行时一次允许的最大字节数。用 seek(int) 方法可以定位到指定的位置,成功则返回 True;用 pos() 方法获取位置;用 atEnd() 方法获取是否还有可读取的数据。

setEncoding(QStringConverter.Encoding) 方法设置文本流读写数据的编码,文本流支持的编码如下:

QStringConverter.Encoding.Utf8
QStringConverter.Encoding.Utf16
QStringConverter.Encoding.Utf16LE
QStringConverter.Encoding.Utf16BE
QStringConverter.Encoding.Utf32
QStringConverter.Encoding.Utf32LE
QStringConverter.Encoding.Utf32BE
QStringConverter.Encoding.Latin1
QStringConverter.Encoding.System     # 系统默认的编码

setAutoDetectUnicode(bool) 方法设置是否自动识别编码,如果能识别出则会替换已经设置的编码。如果 setGenerateByteOrderMark(bool) 为 True 且采用 UTF 编码,会在写入数据前在数据前面添加自动查找编码标识 BOM(byte-order mark),即字节顺序标记,它是插入到以 UTF-8、UTF-16 或 UTF-32 编码 Unicode 文件开头的特殊标记,用来识别 Unicode 文件的编码类型。

setFieldWidth(width:int=0) 方法设置写入一段数据流的宽度,如果真实数据流的宽度小于设置的宽度,可以用 setFieldAlignment(QTextStream.FieldAlignment) 方法设置数据在数据流内的对齐方式,其余位置的数据用 setPadChar(str) 设置的字符来填充。参数 QTextStream.FieldAlignment 用于指定对齐方式,可以取值如下:

QTextStream.FieldAlignment.AlignLeft              # 左对齐
QTextStream.FieldAlignment.AlignRight             # 右对齐
QTextStream.FieldAlignment.AlignCenter            # 居中
QTextStream.FieldAlignment.AlignAccountingStyle   # 居中,但数值的符号位靠左

setNumberFlags(QTextStream.NumberFlag) 方法设置输出整数和浮点数时数值的表示样式,其中参数 QTextStream.NumberFlag 可以取值如下:

QTextStream.NumberFlag.ShowBase         # 以进制作为前缀
QTextStream.NumberFlag.ForcePoint       # 强制显示小数点
QTextStream.NumberFlag.ForceSign        # 强制显示正负号)
QTextStream.NumberFlag.UppercaseBase    # 进制显示成大写
QTextStream.NumberFlag.UppercaseDigits  # 表示10~35的字母用大写

setRealNumberNotation(QTextStream.RealNumberNotation) 方法设置浮点数的标记方法,参数 QTextStream.RealNumberNotation 可以取值如下:

QTextStream.RealNumberNotation.ScientificNotation     # 科学计数法
QTextStream.RealNumberNotation.FixedNotation          # 固定小数点
QTextStream.RealNumberNotation.SmartNotation          # 视情况选择合适的方法

setStatus(QTextStream.Status) 方法设置数据流的状态,参数 QTextStream.Status 可取值如下:

QTextStream.Status.Ok                  # 文本流正常
QTextStream.Status.ReadPastEnd         # 读取过末尾
QTextStream.Status.ReadCorruptData     # 读取了有问题的数据
QTextStream.Status.WriteFailed         # 不能写入数据
from PySide6.QtCore import QFile, QTextStream, QStringConverter

if __name__ == "__main__":
    # 1.创建一个文件信息
    file_path = "./文件.txt"
    file = QFile(file_path)

    # 2.以只写模式打开文件
    file.open(QFile.OpenModeFlag.WriteOnly)

    # 3.创建文本流
    writer = QTextStream(file)

    # 4.设置编码
    writer.setEncoding(QStringConverter.Encoding.Utf8)

    # 5.设置域宽
    writer.setFieldWidth(0)

    # 6.设置对齐方式
    writer.setFieldAlignment(QTextStream.FieldAlignment.AlignCenter)

    # 7.写入数据
    writer << "你好,世界!\n"
    writer << "hello world!\n"

    # 8.刷新写入
    writer.flush()

    # 9.关闭文件
    file.close()

    # 10.以只读模式打开信息
    file.open(QFile.OpenModeFlag.ReadOnly)

    # 11.判断是否读到末尾
    while not file.atEnd():
        # 12.从文件中读取一行
        text = file.readLine()
        print(str(text, encoding="utf-8").strip())

    # 13.关闭文件
    file.close()

1.2.2 数据流

数据流 QDataStream 用于直接读写二进制的数据和网络通信数据,二进制数据具体表示的物理意义由读写方法以及后续的解码决定,数据流的读写与具体的操作系统无关。

用 QDataStream 类创建数据流对象的方法如下所示,它可以连接到继承自 QIODevice 的设备或 QByteArray 上。

QDataStream()
QDataStream(device:QIODevice)
QDataStream(device:Union[QByteArray, bytes])
QDataStream(device:Union[QByteArray, bytes], flags:QIODeviceBase.OpenMode)

QDataStream 类的常用方法如下:

setDevice(device:QIODevices) -> None                                                # 设置设备
setByteOrder(arg__1:QDataStrem.ByteOrder) -> None                                   # 设置字节序
byteOrder() -> QDataStrem.ByteOrder                                                 # 获取字节序
setFloatingPointPrecision(precision:QDataStrem.FloatingPointPrecision) -> None      # 设置读写浮点数的精度
setStatus(status:QDataStrem.Status) -> None                                         # 设置流状态
resetStatus() -> None                                                               # 重置流状态
status() -> QDataStrem.Status                                                       # 获取流状态
setVersion(version:int) -> None                                                     # 设置版本号
version() -> int                                                                    # 获取版本号
skipRawData(len:int) -> int                                                         # 跳过原生数据,返回跳过的字节数量
commitTransaction() -> None                                                         # 完成数据块
rollbackTransaction() -> None                                                       # 回到数据块的记录点
abortTransaction() -> None                                                          # 放弃对数据块的记录
atEnd() -> bool                                                                     # 获取是否还有数据可读

创建数据流对象时,可以设置数据流关联的设备,也可用 setDevice(QIODevice) 方法重新设置关联的设备,用 device() 方法获取关联的设备。用 setVersion(int) 方法设置版本号。不同版本号的数据的存储格式有所不同,因此建议设置版本号。到目前为止,版本号可取值如下:

QDataStream.Qt_1_0
QDataStream.Qt_2_0
QDataStream.Qt_3_0
QDataStream.Qt_3_1
QDataStream.Qt_3_3
QDataStream.Qt_4_0 ~ QDataStream.Qt_4_9
QDataStream.Qt_5_0 ~ QDataStream.Qt_5_15
QDataStream.Qt_6_0 ~ QDataStream.Qt_6_2

setFloatingPointPrecision(QDataStream.FloatingPointPrecision) 方法设置读写浮点数的精度,其中参数 QDataStream.FloatingPointPrecision 可以取值如下:

QDataStream.FloatingPointPrecision.SinglePrecision
QDataStream.FloatingPointPrecision.DoublePrecision

对于版本高于 Qt_4_6 且精度设置为 DoublePrecision 的浮点数是 64 位精度,对于版本高于 Qt_4_6 且精度设置为 SinglePrecision 的浮点数是 32 位精度。

setByteOrder(QDataStream.ByteOrder) 方法设置字节序,参数 QDataStream.ByteOrder 可以取值如下:

QDataStream.ByteOrder.BigEndian    # 大端字节序,默认值
QDataStream.ByteOrder.LittleEndian # 小端字节序

大端字节序的高位字节在前,低位字节在后,小端字节序与此相反。

skipRawData(len:int) 方法可以跳过指定长度的原生字节,返回真实跳过的字节数。原生数据是机器上存储的二进制数据,需要用户自己解码。

startTransaction() 方法可以记录一个读数据的点,对于顺序设备会在内部复制读取的数据,对于随机设备会保存当前数据流的位置;用 commitTransaction() 方法确认完成记录一个数据块,当数据流的状态是已经超过末尾时,用该方法会回到数据块的记录点,如果状态是数据有误,则会放弃记录的数据块;用 rollbackTransaction() 方法在确认完成记录数据块之前返回到记录点;用 abortTransaction() 方法放弃对数据块的记录,并不影响当前读数据的位置。

数据流用于读/写整数、浮点数和逻辑值的方法和数值的范围如下所示。需要特别注意的是,在读数值时,必须按照写入数值时所使用的字节数来读,否则读取的数值不是写入时的数值。

readInt8() -> int   / writeInt8(arg__1:int) -> None         # 在1个字节上读/写带正负号整数
readInt16() -> int  / writeInt16(arg__1:int) -> None        # 在2个字节上读/写带正负号整数
readInt32() -> int  / writeInt32(arg__1:int) -> None        # 在4个字节上读/写带正负号整数
readInt64() -> int  / writeInt64(arg__1:int) -> None        # 在8个字节上读/写带正负号整数
readUInt8() -> int  / writeUInt8(arg__1:int) -> None        # 在1个字节上读/写无符号整数
readUInt16() -> int / writeUInt16(arg__1:int) -> None       # 在2个字节上读/写无符号整数
readUInt32() -> int / writeUInt32(arg__1:int) -> None       # 在4个字节上读/写无符号整数
readUInt64() -> int / writeUInt64(arg__1:int) -> None       # 在8个字节上读/写无符号整数
readBool() -> int   / writeBool(arg__1:int) -> None         # 在1个字节上读/写逻辑值
readFloat() -> int  / writeFloat(arg__1:int) -> None        # 在4个字节上读/写带正负号浮点数
readDouble() -> int / writeDouble(arg__1:int) -> None       # 在8个字节上读/写带正负号浮点数

数据流用于读/写字符串的方法如下所示。读/写字符串时不需要指定字节数量,系统会根据字符串的大小来决定所使用的字节数。

readString() -> str             / writeString(arg__1:str) -> None                   # 读/写文本
readQString() -> List[str]      / writeQString(arg__1:Sequence[str]) -> None        # 读/写文本
readQStringList() -> List[str]  / writeQStringList(arg__1:Sequence[str]) -> None    # 读/写文本列表

from PySide6.QtCore import QFile, QDataStream

if __name__ == "__main__":
    # 1.创建一个文件信息
    file_path = "./文件.txt"
    file = QFile(file_path)

    # 2.以只写模式打开文件
    file.open(QFile.OpenModeFlag.WriteOnly)

    # 3.创建文本流
    writer = QDataStream(file)

    # 4.向文件中写入数据
    writer.writeQString("你好,世界!\n")
    writer.writeString("hello world!\n")
    writer.writeInt32(27185)
    writer.writeFloat(27185.27185)

    # 5.关闭文件
    file.close()

    # 6.以只读模式打开文件
    file.open(QFile.OpenModeFlag.ReadOnly)

    # 7.从文件中读取数据
    value = writer.readQString().strip()
    print(value)

    value = writer.readString().strip()
    print(value)

    value = writer.readInt32()
    print(value)

    value = writer.readFloat()
    print(value)

    # 8.关闭文件
    file.close() 

1.3 临时数据的保存

1.3.1 临时文件

QTemporaryFile 类用于创建临时文件,它继承自 QFile,当用 Open() 方法打开设备时创建临时文件,并保证临时文件名是唯一的,不会和本机上的文件同名。

用 QTemporaryFile 创建临时文件对象的方法如下:

QTemporaryFile()
QTemporaryFile(parent:QObject)
QTemporaryFile(templateName:str)
QTemporaryFile(templateName:str, parent:QObject)

其中 templateName 是文件名称模板,或者不用模板而用指定文件名,parent 是继承自 QObject 类的实例对象。模板的文件名中包含 6 个或 6 个以上的大写 "X",扩展名可以自己指定,例如 QTemporaryFile("XXXXXXXX.sdb")、QTemporaryFile("abXXXXXXXXcd.sdb")。如果没有使用模板,而使用具体文件名,则临时文件名是在文件名基础上添加新的扩展名,如果指定了父对象,则用应用程序的名称(用 app.setApplicationName(str) 设置)再加上新的扩展名作为临时文件名。如果没有使用模板或指定文件名,则存放临时文件的路径是系统临时路径,可以通过 QDir.tempPath() 方法获取系统临时路径;如果使用模板或指定文件名,则存放到当前路径下,当前路径可以用 QDir.currentPath() 方法查询。

QTemporaryFile 类常用方法如下:

open() -> bool                      # 创建并打开临时文件
fileName() -> str                   # 获取临时文件名
setAutoRemove(b:bool) -> None       # 设置是否自动删除临时文件
autoRemove() -> bool                # 获取是否自动删除临时文件
setFileTemplate(name:str) -> None   # 设置临时文件的模板
fileTemplate() -> str               # 获取临时文件的模板

创建临时文件对象后,用 open() 方法打开文件,这时生成临时文件,临时文件名可以用 fileName() 方法获取,临时文件的打开方式是读写模式(QIODeviceBase.ReadWrite)。打开临时文件后,可以按照前面介绍的写入和读取方法来读写数据。用 setAutoRemove(bool) 方法设置临时文件对象销毁后临时文件是否自动删除,默认为 True。

1.3.2 临时路径

与创建临时文件类似,也可以创建临时路径,应保证所创建的临时路径不会覆盖本机上的路径,程序退出时自动删除临时路径。

创建临时路径的方法如下所示:

QTemporaryDir()
QTemporaryDir(templateName:str)

QTemporaryDir 类常用方法如下:

path() -> str                   # 获取创建的临时路径
isValid() -> bool               # 检查临时路径是否创建成功
errorString() -> str            # 获取错误信息
filePath(fileName:str) -> str   # 获取临时路径中的文件的路径
setAutoRemove(b:bool) -> None   # 设置是否自动移除临时路径
autoRemove() -> bool            # 获取是否自动移除临时路径
remove() -> bool                # 移除临时路径

1.3.3 存盘

QSaveFile 用来保存文本文件和二进制文件,在写入操作失败时不会导致已经存在的数据丢失。QSaveFile 执行写操作时,会先将内容写入到一个临时文件中,如果没有错误发生,则调用 commit() 方法来将临时文件中的内容移到目标文件中。这样能确保目标文件中的数据在写操作发生错误时不会丢失,也不会出现部分写入的情况,一般使用 QSaveFile 在磁盘上保存整份文档。QSaveFile 会自动检测写入过程中所出现的错误,并记住所有发生的错误,在调用 commit() 方法时放弃临时文件。

用 QSaveFile 创建保存文件的方法如下所示:

QSaveFile(parent:QObject=None)
QSaveFile(name:str)
QSaveFile(name:str, parent:QObject)

其中 name 是文件名,parent 是继承自 QObject的对象。

QSaveFile 常用方法如下:

setFileName(name:str) -> None                   # 设置保存数据的目标文件
fileName() -> str                               # 获取目标文件
open(mode:QIODeviceBase.OpenMode) -> bool       # 打开文件
commit() -> bool                                # 从临时文件中将数据写入到目标文件中
cancelWriting() -> None                         # 取消将数据写入到目标文件中
setDirectWriteFallback(enabled:bool) -> None    # 设置是否直接向目标文件中写数据
directWriteFallback() -> bool                   # 获取是否直接向目标文件中写数据
writeData(data:bytes, len:int) -> int           # 重写该函数,写入字符串,并返回实际写入的字节串的数量

open(flags:QIODeviceBase.OpenMode) 方法打开文件,并创建临时文件,如果创建临时文件出错则返回 False。可以使用 QDataStream 或 QTextStream 进行读写,也可以使用从 QIODevice 继承的 read()readLine()write()等方法进行读写。

QSaveFile 不能调用 close() 函数,而是通过调用commit() 函数完成数据的保存。如果没有调用 commit() 函数,则 QSaveFile 对象销毁时,会丢弃临时文件。

当应用程序出错时,用 cancelWriting() 方法可以放弃写入的数据,即使又调用了 commit(),也不会发生真正保存文件操作。

QSaveFile 会在目标文件的同一目录下创建一个临时文件,并自动进行重命名。但如果由于该目录的权限限制不允许创建文件,则调用 open() 会失败。为了解决这个问题,即能让用户编辑一个现存的文件,而不创建新文件,可使用 setDirectWriteFallback(True) 方法,这样在调用 open() 时就会直接打开目标文件,并向其写入数据,而不使用临时文件。但是在写入出错时,不能使用 cancelWriting() 方法取消写入。

1.3.4 缓存

对于程序中反复使用的一些临时数据,如果将其保存到文件中,则反复读取这些数据要比从缓存读取数据慢得多。缓存是内存中一段连续的存储空间,QBuffer 提供了可以从缓存读取数据的功能,在多线程之间进行数据传递时选择缓存比较方便。缓存属于共享资源,所有线程都能进行访问。QBuffer 和 QFile 一样,也是一种读写设备,它继承自 QIODevice,可以用 QIODevice 的读写方法从缓存中读写数据,也可以与 QTextStream 和 QDataStream 结合读写文本数据和二进制数据。

用 QBuffer 创建缓存设备的方法如下:

QBuffer(parent=:QObject=None)
QBuffer(buffer:Union[QByteArray, bytes], parent=:QObject=None)

其中 parent 是继承自 QObject 的实例对象。定义 QBuffer 需要一个 QByterArray 对象,也可不指定 QByteArray,系统会给 QBuffer 创建一个默认的 QByteArray 对象。

QBuffer 类常用方法如下:

setBuffer(a:Union[QByteArray, byte]) -> None        # 设置缓存
buffer() -> QByteArray                              # 获取缓存中的QByteArray对象
setData(data:Union[QByteArray, byte]) -> None       # 给缓存设置QByteArray对象
data() -> QByteArray                                # 获取缓存中的QByteArray对象

open(mode:QIODeviceBase.OpenMode) -> bool           # 打开缓存
close() -> None                                     # 关闭缓存
canReadLine() -> bool                               # 获取是否可以按行读取
pos() -> int                                        # 获取指向缓存内部指针的位置
seek(pos:int) -> bool                               # 定位到指定位置
readData(data:bytes, maxlen:int) -> object          # 重写该函数,读取指定的最大数量的字节数据
writeData(data:bytes, len:int) -> int               # 重写该函数,写入数据
atEnd() -> bool                                     # 获取是否达到尾部
size() -> int                                       # 获取缓存中字节的总数

默认情况下,系统会自动给 QBuffer的实例创建默认的 QByteArray 对象,可以用 buffer() 方法或 data() 方法获取 QByteArray 对象,也可用 setBuffer(QByteArray) 方法设置缓存。QBuffer 需要用 open(QIODeviceBase.OpenMode) 方法打开缓存,成功则返回 True,打开后可以读写数据;用 close() 方法关闭缓存。

from PySide6.QtCore import QFile, QBuffer, QDataStream

if __name__ == "__main__":
    # 1.创建缓存
    buffer = QBuffer()

    # 2.打开缓存
    buffer.open(QBuffer.OpenModeFlag.WriteOnly)

    # 3.创建文本流
    writer = QDataStream(buffer)

    # 4.向文件中写入数据
    writer.writeQString("你好,世界!\n")
    writer.writeString("hello world!\n")
    writer.writeInt32(27185)
    writer.writeFloat(27185.27185)

    # 5.关闭文件
    buffer.close()

    # 6.以只读模式打开缓存
    buffer.open(QFile.OpenModeFlag.ReadOnly)

    # 7.从缓存中读取数据
    value = writer.readQString().strip()
    print(value)

    value = writer.readString().strip()
    print(value)

    value = writer.readInt32()
    print(value)

    value = writer.readFloat()
    print(value)

    # 8.关闭文件
    buffer.close()

1.4 文件管理

1.4.1 文件信息

文件信息 QFileInfo 用于查询文件的信息,如文件的相对路径、绝对路径、文件大小、文件权限、文件的创建及修改时间等。

用 QFileInfo 类创建文件信息对象的方法如下所示:

QFileInfo()
QFileInfo(file:QFileDevice)
QFileInfo(file:Union[str, bytes, os.PathLike])
QFileInfo(dir:Union[QDir, str], file:Union[str, bytes, os.PathLike])

其中 str 是需要获取文件信息的文件,QFileInfo(QDir,str) 表示用 QDir 路径下的 str 文件创建文件信息对象。

QFileInfo 类的常用方法如下:

setFile(file:Union[str, bytes]) -> None                 # 设置需要获取文件信息的文件
setFile(file:QFileDevice) -> None                       # 设置需要获取文件信息的文件
setFile(dir:Union[QDir, str], file:str) -> None         # 设置需要获取文件信息的文件

setCaching(on:bool) -> None                             # 设置是否使用缓存
refresh() -> None                                       # 刷新文件信息

absoluteDir() -> QDir                                   # 获取绝对路径目录
absoluteFilePath() -> str                               # 获取绝对路径
absolutePath() -> str                                   # 获取绝对路径
baseName() -> str                                       # 获取第一个"."之前的文件名
completeBaseName() -> str                               # 获取最后一个"."之前的文件名
suffix() -> str                                         # 获取扩展名,不包含"."
completeSuffix() -> str                                 # 获取第一个"."之后文件名,包含扩展名
fileName() -> str                                       # 获取文件名,包含扩展名,不包含路径
path() -> str                                           # 获取路径,不包含文件名
filePath() -> str                                       # 获取路径和文件名
canonicalFilePath() -> str                              # 获取绝对路径和文件名,路劲中不含链接符号和多余的".."及"."
canonicalPath() -> str                                  # 获取绝对路径,路径中不含链接符号和多余的".."及"."

birthTime() -> QDateTime                                # 获取创建时间
lastModified() -> QDateTime                             # 获取最后修改时间
dir() -> QDir                                           # 获取路径
group() -> str                                          # 获取文件组   
exists() -> bool                                        # 获取文件是否存在

isAbsolute() -> bool                                    # 是否绝对路径
isDir() -> bool                                         # 是否为目录
isFile() -> bool                                        # 是否为文件
isReadable() -> bool                                    # 是否可读文件
isWritable() -> bool                                    # 是否可写文件
isExecutable() -> bool                                  # 是否可执行文件
isHidden() -> bool                                      # 是否隐藏文件
isRelative() -> bool                                    # 是否相对路径
isRoot() -> bool                                        # 是否根目录
isShortcut() -> bool                                    # 是否为快捷方式
isSymLink() -> bool                                     # 是否为连接符号或快捷方式
isSymbolicLink() -> bool                                # 是否为连接符号

makeAbsolute() -> bool                                  # 转换为绝对路径

owner() -> str                                          # 获取文件所有者
ownerId() -> int                                        # 获取文件所有者ID

size() -> int                                           # 获取文件大小
symLinkTarget() -> str                                  # 获取被链接文件的绝对路径

可以在创建 QFileInfo 对象时设置要获取文件信息的文件,也可以用 setFile(dir:Union[QDir,str],file:str)setFile(file:Union[str,bytes])setFile(file:QFileDevice) 方法重新设置要获取文件信息的文件。

QFileInfo 提供了一个 refresh() 函数,用于重新读取文件信息。如果想关闭该缓存功能,以确保每次访问文件时都能获取当前最新的信息,可以通过 setCaching(False) 方法来完成设置。

from PySide6.QtCore import QFile, QFileInfo

if __name__ == "__main__":
    # 1.创建一个文件信息
    file_path = "./文件.txt"
    file = QFile(file_path)

    # 2.以读读写模式打开文件
    file.open(QFile.OpenModeFlag.ReadWrite)

    # 3.向文件中写入内容
    file.write("你好,世界!".encode("utf-8"))

    # 4.关闭文件
    file.close()

    # 5.获取文件的详情信息对象
    file_info = QFileInfo(file_path)

    # 6.读取文件的详情信息
    print("文件的大小:", file_info.size())
    print("文件上次修改的时间:", file_info.lastModified())
    print("文件上次读取的时间:", file_info.lastRead())
    print("文件是否可读:", file_info.isReadable())

1.4.2 路径管理

路径管理 QDir 用于管理路径和文件,它的一些功能与 QFileInfo 的功能相同。

用 QDir 创建路径管理对象的方法如下:

QDir(path:Union[QDir, str])
QDir(path:Union[str, bytes, os.PathLike])
QDir(path:Union[str, bytes, os.PathLike], nameFilter:str, sort:QDir.SortFlag=QDir.IgnoreCase, filter:QDir.Filters=QDir.AllEntries)

其中:path 是路径;nameFilter 是名称过滤器;sort 是枚举类型 QDir.SortFlag,指定排序规则;filters 是枚举类型 QDir.Filter,是属性过滤器。

QDir 类常用方法如下:

# 实例方法
setPath(path:Union[str, bytes]) -> None                             # 设置路径
path() -> str                                                       # 获取路径
absolutePath() -> str                                               # 获取绝对路径
absoluteFilePath(fileName:str) -> str                               # 获取绝对路径
relativeFilePath(fileName:str) -> str                               # 获取相对路径
canonicalPath() -> str                                              # 获取不含"."和".."的路径


cd(dirName:str) -> bool                                             # 更改路径
cdUp() -> bool                                                      # 更改路径到上一级

dirName() -> str                                                    # 获取最后一级的目录或文件名

setNameFilters(nameFilters:Sequence[str]) -> None                   # 设置文件名过滤器
setFilter(filter:QDir.Filter) -> None                               # 设置属性过滤器

setSorting(sort:QDir.SortFlag) -> None                              # 设置排序规则

# 根据过滤器和排序规则,获取路径下的所有文件信息和子路径信息
entryInfoList(filters:QDir.Filter=QDir.Filter.NoFilter, sort:QDir.SortFlag=QDir.SortFlag.NoSort) -> List[QFileInfo]
entryInfoList(nameFilters:Sequence[str], filters:QDir.Filter=QDir.Filter.NoFilter, sort:QDir.SortFlag=QDir.Filter.NoFilter) - List[QFileInfo]
entryList(filters:QDir.Filter=QDir.Filter.NoFilter, sort:QDir.SortFlag=QDir.Filter.NoFilter) -> List[str]
entryList(nameFilters:Sequence[str], filters:QDir.Filter=QDir.Filter.NoFilter, sort:QDir.SortFlag=QDir.Filter.NoFilter) -> List[str]

exists() -> bool                                                    # 判断路径或文件是否存在
exists(name:str) -> bool                                            # 判断路径或文件是否存在

isAbsolute() -> bool                                                # 获取是否是绝对路径
isRelative() -> bool                                                # 获取是否是相对路径
isRoot() -> bool                                                    # 获取是否是根路径
isReadable() -> bool                                                # 获取文件是否可读
# 获取路径是否为空
isEmpty(filters:QDir.Filter=QDir.Filter.NoFilter=QDir.Filters(QDir.Filter.AllEntries | QDir.Filter.NoDotAndDotDot))

makeAbsolute() -> bool                                              # 转换到绝对路径
mkdir(dirName:str) -> bool                                          # 创建子路径,路径已存在,则返回False
mkpath(dirPath:str) -> bool                                         # 创建多级目录,成功返回True

refresh() -> None                                                   # 刷新缓存

remove(fileName:str) -> bool                                        # 移除文件 
removeRecursively() -> bool                                         # 删除目录
rmdir(dirName:str) -> bool                                          # 删除目录
rmpath(dirPath:str) -> bool                                         # 删除目录

rename(oldName:str, newName:str) -> bool                            # 重命名文件或目录

# 静态方法
cleanPath(path:str) -> str                                          # 返回移除多余符号的路径
drives() -> List[QFileInfo]                                         # 获取根文件信息列表
setSearchPaths(prefix:str, searchPaths:Sequence[str]) -> None       # 设置搜索路径
home() -> QDir                                                      # 获取系统的用户路径
homePath() -> str                                                   # 获取系统的用户路径
isAbsolutePath(path:str) -> bool                                    # 判断路径是否是绝对路径
isRelativePath(path:str) -> bool                                    # 判断路径是否是相对路径
listSeparator() -> str                                              # 获取系统列表分割符
root() -> QDir                                                      # 获取根目录
rootPath() -> str                                                   # 获取根目录
separator() -> str                                                  # 获取系统路径分割符
setCurrent(path:str) -> bool                                        # 设置当前路径
temp() -> QDir                                                      # 获取临时目录
tempPath() -> str                                                   # 获取临时目录
fromNativeSeparators(pathName:str) -> str                           # 获取用"/"分割的路径
toNativeSeparators(pathName:str) -> str                             # 转换成用本机系统使用的分割符分割的路径

在创建路径对象时,指定的过滤器、排序规则用于获取路径下的文件和子路径。获取路径下的文件和子路径的方法有 entryInfoList(filters,sort)entryInfoList(Sequence[nameFilters],filters,sort)entryList(filters,sort)entryList[str]entryList(Sequence[nameFilters],filters,sort) ,其中属性过滤器 filters 可以取值如下:

QDir.Dirs            # 列出满足条件的路径
QDir.AllDirs         # 所有路径
QDir.Files           # 文件
QDir.Drives          # 驱动器
QDir.NoSymLinks      # 没有链接文件
QDir.NoDot           # 没有"."
QDir.NoDotDot        # 没有".."
QDir.NoDotAndDotDot  #  没有"."和".."
QDir.AllEntries      # 所有路径、文件和驱动器
QDir.Readable        # 可读文件
QDir.Writable        # 可写文件
QDir.Executable      # 可执行文件
QDir.Modified        # 可修改文件
QDir.Hidden          # 可隐藏文件
QDir.System          # 系统文件
QDir.CaseSensitive   # 区分大小写

排序规则 sort 可以取值如下:

QDir.Name
QDir.Time
QDir.Size
QDir.Type
QDir.Unsorted
QDir.NoSort
QDir.DirsFirst
QDir.DirsLast
QDir.Reversed
QDir.IgnoreCase
QDir.LocaleAware

from PySide6.QtCore import QDir

if __name__ == "__main__":
    # 1.创建一个文件信息
    path = "./template/test"
    dir = QDir(path)

    # 2.如果文件夹不存在,则创建
    if not dir.exists(path):
        dir.mkdir(path)

    # 3.切换目录
    dir.cd("E://")

    print("是否是根目录:", dir.isRoot())
    print("是否是绝对路径:", dir.isAbsolute())
    print("是否是相对路径:", dir.isRelative())
    print("是否可读:", dir.isReadable())

    dir_list = dir.entryList()
    for item in dir_list:
        print(item)

1.4.3 文件和路径监视器

QFileSystemWatcher 是文件和路径监视器,当被监视的文件或路径发生修改、添加和删除等变化时会发送相应的信号,被监视的文件和路径一般不超过 256 个。

用 QFileSystemWatcher 定义文件监视器对象的方法如下所示:

QFileSystemWatcher(parent:QObject=None)
QFileSystemWatcher(paths:Sequence[str], parent:QObject=None)

其中 parent 是继承自 QObjec t类的实例对象;Sequence[str] 是字符串列表,是被监视的文件或路径。

QFileSystemWatcher 类常用方法:

addPath(file:str) -> bool                           # 添加被监视的路径或文件,成功返回True
addPaths(files:Sequence[str]) -> List[str]          # 添加被监视的路径或文件,返回没有添加成功的路径和文件列表
directories() -> List[str]                          # 获取被监视的路径列表
files() -> List[str]                                # 获取被监视的文件列表
removePath(file:str) -> bool                        # 将被监视的路径或文件从监视中移除,成功返回True
removePaths(files:Sequcence[str]) -> List[str]      # 移除被监视的路径或文件,返回没有移除成功的路径和文件列表

QFileSystemWatcher 类常用信号:

directoryChanged(path:str)      # 当被监视的路径发生改变时发射信号
fileChanged(path:str)           # 当被监视的文件发生改变时发射信号

addPath(file:str) 方法或 addPaths(files:Sequence[str]) 方法添加被监视的路径或文件;用 removePath(file:str) 方法或 removePaths(files:Sequence[str]) 方法移除被监视的文件或路径;用 directories() 方法获取被监视的路径列表;用 files() 方法获取被监视的文件列表。当被监视的路径发生改变(增加和删除文件及路径)或文件发生改变(修改、重命名、删除)时,会分别发送 directoryChanged(path) 信号和 fileChanged(fileName) 信号。

2.多线程技术

QThread 类是 PySide 中的核心线程类,要实现一个线程,需要创建 QThread 类的有一个子类,并且实现其 run() 方法。

线程也有自己自己的生命周期,其中包含 5 种状态,分别为:新建状态就绪状态运行状态阻塞状态死亡状态新建状态 就是线程被创建时的状态;当线程对象调用 start() 方法后,线程就处于 就绪状态,当线程得到系统资源后就进入 运行状态

一旦线程进入运行状态,它会在就绪和运行状态下切换,同时也可能进入阻塞或死亡状态。当处于 运行状态 下的线程在调用 sleep()wait() 方法或者发生阻塞时,会进入 暂停状态;当在休眠结束或者阻塞解除时,线程会重新进入 就绪状态;当线程的 run() 方法执行完毕,或者线程发生错误、异常时,线程就进入 死亡状态

QThread 类的常用方法及其说明如下:

start(priority:QThread.Priority=QThread.Priority.InheritPriority) -> None       # 启动线程
run() -> None                                                                   # 线程的起点
wait(time:int) -> None                                                          # 阻塞线程
usleep(us:int) -> None                                                          # 以微秒为单位休眠线程
msleep(ms:int) -> None                                                          # 以毫秒为单位休眠线程
sleep(s:int) -> None                                                            # 以秒为单位休眠线程
exit(retcode:int=0) -> int                                                      # 退出线程的事件循环,并返回代码,返回0表示成功,任何非0值都表示错误
quit() -> int                                                                   # 退出线程的事件循环,并返回代码0(成功),相当于exit(0)
terminate() -> None                                                             # 强制终止线程
setPriority(priority:QThread.Priority.IdlePriority) -> None                     # 设置线程的优先级
isRunning() -> bool                                                             # 获取线程是否在运行
isFinished() -> bool                                                            # 获取线程是否完成

我们可以调用 setPriority(priority) 方法来设置线程的优先级,其中 priority 为 QThread.Priority 的枚举值,可以取值如下:

QThread.Priority.IdlePriority            # 空闲优先级
QThread.Priority.LowestPriority          # 最低优先级
QThread.Priority.LowPriority             # 低优先级
QThread.Priority.NormalPriority          # 系统默认优先级
QThread.Priority.HighPriority            # 高优先级
QThread.Priority.HighestPriority         # 最高优先级
QThread.Priority.TimeCriticalPriority    # 尽可能频繁地分配执行
QThread.Priority.InheritPriority         # 默认用与创建线程相同地优先级

在调用 start() 方法启动线程后,新创建的线程将调用 run() 方法开始执行。如果我们向强制终止线程,可以调用 terminate() 方法,在 terminate() 方法之后应该使用 wait() 方法,以确保当线程终止时,等待完成的所有线程都将被唤醒
  QThread 类常用的信号及其说明如下:

started()       # 在调用run()方法之前,在相关线程开始执行时从该线程发射
finished()      # 在相关线程完成执行之前,从该线程发射

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QThread

class Thread(QThread):
    def __init__(self):
        # 调用父类的初始化方法
        super(Thread, self).__init__()

    def run(self):
        """重写run()方法"""
        num = 10
        while num >=0 :
            print(num)
            # 线程休眠1秒
            Thread.sleep(1)
            if num == 0:
                # 退出线程
                self.quit()
            num -= 1

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建线程对象
    thread = Thread()
    # 3.启动线程
    thread.start()
    # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())

3.事件处理

3.1 事件与事件处理

3.1.1 什么是事件

​ 事件是程序收到外界的输入,处于某种状态时自动发送的信号。事件有固定的类型,每种类型有自己的处理函数,用户只要重写这些函数,即可达到特定的目的。通过事件可以用一个控件监测另外一个控件,并可过滤被监测控件发出的事件。

可视化应用程序在接受外界输入设备的输入时,会对输入设备输入的信息进行分类,根据分类的不同,用不同的函数进行处理,做出不同的反应。外界对 PySide6 程序进行输入信息的过程称为 事件。PySide6 程序对外界的输入进行处理的过程称为 事件处理,根据外界输入信息的不同,处理事件的函数也不同。

在主程序中都会创建一个 QApplication 的应用程序实例对象,然后调用实例对象的 exec() 函数,这将使应用程序进入一个循环,不断监听外界输入的信息。当输入的信息满足某种分类时,将会产生一个事件对象 QEvent(),事件对象中记录了外界输入的信息,并将事件对象发送给处理该事件对象的函数进行处理。

事件与槽相似,但是又有不同。信号 是指控件或窗口本身满足一定条件时,发送一个带数据的信息或不带数据的信息,需要编程人员为这个信息单独写处理这个信息的槽函数,并将信号和槽函数关联,发送信号时,自动执行与之关联的槽函数。而 事件 是外界对程序的输入,将外界的输入进行分类后交给函数处理,处理事件的函数是固定的,只需要编程人员把处理事件的函数重写,来达到处理外界输入的目的,而不需要将事件与处理事件的函数进行连接,系统会自动调用能处理事件的函数,并把相关数据作为实参传递给处理事件的函数。

3.1.2 QEvent类

QEvent 类是所有事件的基类,它在 QtCore 模块中。外界输入给程序的信息首先交给 QEvent 进行分类,得到不同类型的事件,然后系统将事件及相关信息交给控件或窗口的事件处理函数进行处理,得到对外界输入的响应。

QEvent 类的属性只有 accepted,常用的方法如下:

# 实例方法
accept() -> None                        # 事件被接受
ignore() -> None                        # 事件被拒绝
isAccepted() -> bool                    # 事件是否被接受
setAccepted(accepted:bool) -> None      # 设置事件是否被接受
clone() -> QEvent                       # 重写该函数,返回事件的副本
isPointerEvent() -> bool                # 获取事件是否为QPointerEvent事件
isSinglePointEvent() -> bool            # 获取事件是否为QSinglePointEvent事件
spontaneous() -> bool                   # 获取事件是否立即被处理
type() -> QEvent.Type                   # 获取事件的类型

# 静态方法
registerEventType(hint:int=-1) -> int   # 注册新的事件类型

accept()setAccepted(True) 方法接受一个事件,用 ignore()setAccepted(False) 方法拒绝一个事件。被接受的事件不会再传递给其他对象;被拒绝的事件会传递给其他对象处理,如果没有对象处理,则该事件会被丢弃。如果事件被 QWidget 的 event() 函数进行了处理,则用 spontaneous() 方法的返回值是 True,否则返回值是 False。event() 函数根据事件类型起到分发事件到指定处理函数的作用,可以在 event() 函数中对事件进行处理。用 type() 方法可以返回事件的类型。QEvent 中定义了事件的类型。

QEvent定义的主要事件类型

3.1.3 event()函数

​ 当 GUI 应用程序捕捉到事件发生后,会首先将其发送到 QWidget 或子类的 event(QEvent) 函数中进行数据处理,如果没有重写 event() 函数进行事件处理,事件将会分发到事件默认的处理函数中,因此 event() 函数是事件的集散地。如果重写了 event() 函数,当 event() 函数的返回值是 True 时,表示事件已经处理完毕,事件不会再发送给其他处理函数;当 event() 函数的返回值是 False 时,表示事件还没有处理完毕。event() 函数可以截获某些类型的事件,并处理事件。

3.2 鼠标事件和滚轮事件

3.2.1 鼠标事件

​ 鼠标事件类 QMouseEvent 涉及鼠标按键的单击、释放和鼠标移动操作,与 QMouseEvent 关联的事件类型有 QEvent.MouseButtonDblClickQEvent.MouseButtonPressQEvent.MouseButtonReleaseQEvent.MouseMove

当在一个窗口或控件中按住鼠标按键或释放按键时会产生鼠标事件 QMouseEvent,鼠标移动事件只会在按下鼠标按键的情况下才会发生,除非通过显式调用窗口的 setMouseTracking(True) 函数来开启鼠标轨迹跟踪,这种情况下只要鼠标指针移动,就会产生一系列鼠标事件。

当产生鼠标事件时,会生成 QMouseEvent 类的实例对象,并将实例对象作为实参传递给相关的处理函数。QMouseEvent 类包含了用于描述鼠标事件的参数。QMouseEvent 类在 QtGui 模块中,它的常用方法如表下所示。

button() -> Qt.MouseButton                  # 获取产生鼠标事件的按键
buttons() -> Qt.MouseButtons                # 获取产生鼠标事件是被按下的按键

modifiers() -> Qt.KeyboardModifiers         # 获取鼠标事件的修饰键
device() -> QInputDevice                    # 获取产生鼠标事件的设备
deviceType() -> QInputDevice.deviceType     # 获取产生鼠标事件的设备类型

flags() -> Qt.MouseEventFlags               # 获取鼠标事件的标识
source() -> Qt.MouseEventSource             # 获取产生鼠标事件的源

globalPosition() -> QPoint                  # 获取全局的鼠标位置  
scenePosition() -> QPointF                  # 获取屏幕的鼠标位置
position() -> QPointF                       # 获取相对于控件的鼠标位置 

button() 方法可以获取产生鼠标事件的按键,用 buttons() 方法获取产生鼠标事件时被按住的按键,返回值可以取值如下:

Qt.MouseButton.NoButton
Qt.MouseButton.AllButtons
Qt.MouseButton.LeftButton
Qt.MouseButton.RightButton
Qt.MouseButton.MidButton
Qt.MouseButton.MiddleButton
Qt.MouseButton.BackButton
Qt.MouseButton.ForwardButton
Qt.MouseButton.TaskButton
Qt.MouseButton.ExtraButtoni(i=1,2,…,24)

source() 方法可以获取鼠标事件的来源,返回值可以取值如下:

Qt.MouseEventSource.MouseEventNotSynthesized               # 来自鼠标
Qt.MouseEventSource.MouseEventSynthesizedBySystem          # 来自鼠标和触摸屏
Qt.MouseEventSource.MouseEventSynthesizedByQt              # 来自触摸屏
Qt.MouseEventSource.MouseEventSynthesizedByApplication     # 来自应用程序

产生鼠标事件的同时,有可能按下了键盘上的 Ctrl、Shift 或 Alt 等修饰键,用 modifiers() 方法可以获取这些键。 modifiers() 方法的返回值可以取值如下:

Qt.KeyboardModifier.NoModifier            # 没有修饰键
Qt.KeyboardModifier.ShiftModifier         # Shift键
Qt.KeyboardModifier.ControlModifier       # Ctrl键
Qt.KeyboardModifier.AltModifier           # Alt键
Qt.KeyboardModifier.MetaModifier          # Meta键,Windows系统为window键
Qt.KeyboardModifier.KeypadModifier        # 小键盘上的键
Qt.KeyboardModifier.GroupSwitchModifier   # Mode_switch键

deviceType() 方法可以获取产生鼠标事件的设备类型,返回值是 QInputDevice.DeviceType 的枚举值,可取值如下:

QInputDevice.DeviceType.Unknown
QInputDevice.DeviceType.Mouse
QInputDevice.DeviceType.TouchScreen
QInputDevice.DeviceType.TouchPad
QInputDevice.DeviceType.Stylus
QInputDevice.DeviceType.Airbrush
QInputDevice.DeviceType.Puck
QInputDevice.DeviceType.Keyboard
QInputDevice.DeviceType.AllDevices

处理 QMouseEvent 类鼠标事件的函数如下:

mouseDoubleClickEvent(event:QMouseEvent)  # 双击鼠标按键
mouseMoveEvent(event:QMouseEvent)         # 移动鼠标
mousePressEvent(event:QMouseEvent)        # 按下鼠标按键
mouseReleaseEvent(event:QMouseEvent)      # 释放鼠标按键

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QFileDialog
from PySide6.QtGui import QPixmap, QPainter
from PySide6.QtCore import QRect, QPoint, Qt

class MyWidget(QWidget):
    def __init__(self):
        # 1.调用父类Qwidget类的__init__()方法
        super().__init__()
        # 2.设置鼠标单击时光标位置
        self.start = QPoint(0, 0)
        # 3.记录窗口的中心点
        self.center = QPoint(self.width() // 2, self.height() // 2)
        # 4.调用setupUi()方法初始化页面
        self.setup_ui()

    def setup_ui(self):
        # 1.设置窗口对象大小
        self.resize(700, 500)

        # 2.创建QPixmap图像
        self.pixmap = QPixmap()

        # 3.设置初始的宽度和高度
        self.pixmap_width = 0
        self.pixmap_height = 0

        # 4.设置初始的偏移量
        self.translate_x = 0
        self.translate_y = 0

    def mouseDoubleClickEvent(self, event):
        """双击鼠标事件的处理函数"""
        # 1.创建文件对话框
        fileDialog = QFileDialog(self)
        # 2.设置文件过滤器
        fileDialog.setNameFilter("图像文件(*.png *.jpeg *.jpg *.ico)")
        # 3.设置文件模式
        fileDialog.setFileMode(QFileDialog.FileMode.ExistingFiles)

        # 4.判断是否选择了文件
        if fileDialog.exec():
            # 5.加载图片
            self.pixmap.load(fileDialog.selectedFiles()[0])
            # 6.获取图像的宽和高
            self.pixmap_width = self.pixmap.width()
            self.pixmap_height = self.pixmap.height()
            # 7.设置中心点的位置
            self.center = QPoint(self.width() // 2, self.height() // 2)
            # 8.更新窗口
            self.update()

    def mousePressEvent(self, event):
        """鼠标按下事件处理函数"""
        # 1.获取鼠标位置
        self.start = event.position()

    def mouseMoveEvent(self, event):
        """鼠标移动事件的处理的函数"""
        # 1.如果是按Ctrl+鼠标左键移动的话
        if event.modifiers() == Qt.KeyboardModifier.ControlModifier and event.buttons() == Qt.MouseButton.LeftButton:
            # 2.鼠标的偏移量
            self.translate_x = event.position().x() - self.start.x()
            self.translate_y = event.position().y() - self.start.y()
            self.start = event.position()
            self.update()

    def paintEvent(self, event):
        """窗口绘制处理函数,当窗口刷新时调用该函数"""
        # 1.获取中心点
        self.center = QPoint(self.center.x() + self.translate_x, self.center.y() + self.translate_y)
        # 2.绘制区域的左上角点
        point_left_top = QPoint(self.center.x() - self.pixmap_width / 2, self.center.y() - self.pixmap_height / 2 )
        # 3.绘制区域的右下角点
        point_right_bottom = QPoint(self.center.x() + self.pixmap_width / 2, self.center.y() + self.pixmap_height / 2)
        # 4.图像绘制区域
        self.rect = QRect(point_left_top, point_right_bottom)
        # 5.绘图
        painter = QPainter(self)
        painter.drawPixmap(self.rect, self.pixmap)

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建一个窗口
    window = MyWidget()
    # 3.展示窗口
    window.show()
    # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())

image-20240617110827503

3.2.2 滚轮事件

鼠标滚轮的滚动事件类是 QWheelEvent,其常用方法如下:

button() -> Qt.MouseButton                  # 获取产生鼠标事件的按键
buttons() -> Qt.MouseButtons                # 获取产生鼠标事件是被按下的按键

modifiers() -> Qt.KeyboardModifiers         # 获取鼠标事件的修饰键
device() -> QInputDevice                    # 获取产生鼠标事件的设备
deviceType() -> QInputDevice.deviceType     # 获取产生鼠标事件的设备类型

globalPosition() -> QPoint                  # 获取全局的鼠标位置  
scenePosition() -> QPointF                  # 获取屏幕的鼠标位置
position() -> QPointF                       # 获取相对于控件的鼠标位置

angleDelta() -> QPoint                      # 获取鼠标事件的旋转角度
pixelDelta() -> QPoint                      # 获取鼠标事件的移动距离
phase() -> Qt.ScrollPhase                   # 获取鼠标事件的相位
inverted() -> bool                          # 获取鼠标事件是否是反向的
source() -> Qt.MouseEventSource             # 获取产生鼠标事件的源

angleDelta().y() 返回两次事件之间鼠标竖直滚轮旋转的角度,angleDelta().x() 返回两次事件之间鼠标水平滚轮旋转的角度。如果没有水平滚轮,则 angleDetal().x() 的值为 0,正数值表示滚轮相对于用户在向前滑动,负数值表示滚轮相对于用户在向后滑动。

inverted() 方法将 angleDelta()pixelDelta() 的值与滚轮转向之间的取值关系反向,即正数值表示滑轮相对于用户在向后滑动,负数值表示滑轮相对于用户在向前滑动。

phase() 方法返回设备的状态,返回值如下:

Qt.ScrollPhase.NoScrollPhase     # 不支持滚动
Qt.ScrollPhase.ScrollBegin       # 开始位置
Qt.ScrollPhase.ScrollUpdate      # 处于滚动状态
Qt.ScrollPhase.ScrollEnd         # 结束位置
Qt.ScrollPhase.ScrollMomentum    # 不触碰设备,由于惯性仍处于滚动状态

处理 QWheelEvent 滚轮事件的函数如下:

wheelEvent(event:QWheelEvent)

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QFileDialog
from PySide6.QtGui import QPixmap, QPainter
from PySide6.QtCore import QRect, QPoint, Qt

class MyWidget(QWidget):
    def __init__(self):
        # 1.调用父类Qwidget类的__init__()方法
        super().__init__()
        # 2.设置鼠标单击时光标位置
        self.start = QPoint(0, 0)
        # 3.记录窗口的中心点
        self.center = QPoint(self.width() // 2, self.height() // 2)
        # 4.调用setupUi()方法初始化页面
        self.setup_ui()

    def setup_ui(self):
        # 1.设置窗口对象大小
        self.resize(700, 500)

        # 2.创建QPixmap图像
        self.pixmap = QPixmap()

        # 3.设置初始的宽度和高度
        self.pixmap_width = 0
        self.pixmap_height = 0

        # 4.设置初始的偏移量
        self.translate_x = 0
        self.translate_y = 0

        # 5.设置缩放比例
        self.pixmap_scale_x = 0
        self.pixmap_scale_y = 0

    def mouseDoubleClickEvent(self, event):
        """双击鼠标事件的处理函数"""
        # 1.创建文件对话框
        fileDialog = QFileDialog(self)
        # 2.设置文件过滤器
        fileDialog.setNameFilter("图像文件(*.png *.jpeg *.jpg *.ico)")
        # 3.设置文件模式
        fileDialog.setFileMode(QFileDialog.FileMode.ExistingFiles)

        # 4.判断是否选择了文件
        if fileDialog.exec():
            # 5.加载图片
            self.pixmap.load(fileDialog.selectedFiles()[0])
            # 6.获取图像的宽和高
            self.pixmap_width = self.pixmap.width()
            self.pixmap_height = self.pixmap.height()
            # 7.设置中心点的位置
            self.center = QPoint(self.width() // 2, self.height() // 2)
            # 8.设置缩放尺寸
            self.pixmap_scale_x = self.pixmap_width / (self.pixmap_width + self.pixmap_height)
            self.pixmap_scale_y = self.pixmap_height / (self.pixmap_width + self.pixmap_height)
            # 9.更新窗口
            self.update()

    def wheelEvent(self, event):
        """鼠标滚轮事件的处理函数"""
        # 1.如果是Ctrl键
        if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
            if (self.pixmap_width > 10 and self.pixmap_height > 10) or event.angleDelta().y() > 0:
                # 2.获取缩放后的尺寸
                self.pixmap_width = self.pixmap_width + int(event.angleDelta().y() / 10 * self.pixmap_scale_x)
                self.pixmap_height = self.pixmap_height + int(event.angleDelta().y() / 10 * self.pixmap_scale_y)
                # 3.更新画面
                self.update()
                print(self.pixmap_width, self.pixmap_height)

    def paintEvent(self, event):
        """窗口绘制处理函数,当窗口刷新时调用该函数"""
        # 1.获取中心点
        self.center = QPoint(self.center.x() + self.translate_x, self.center.y() + self.translate_y)
        # 2.绘制区域的左上角点
        point_left_top = QPoint(self.center.x() - self.pixmap_width / 2, self.center.y() - self.pixmap_height / 2 )
        # 3.绘制区域的右下角点
        point_right_bottom = QPoint(self.center.x() + self.pixmap_width / 2, self.center.y() + self.pixmap_height / 2)
        # 4.图像绘制区域
        self.rect = QRect(point_left_top, point_right_bottom)
        # 5.绘图
        painter = QPainter(self)
        painter.drawPixmap(self.rect, self.pixmap)

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建一个窗口
    window = MyWidget()
    # 3.展示窗口
    window.show()
    # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())

image-20240617111212100

3.3 键盘事件

键盘事件 QKeyEvent 涉及键盘键的按下和释放,与 QKeyEvent 关联的事件类型有 QEvent.KeyPressQEvent.KeyReleaseQEvent.ShortcutOverride,处理键盘事件的函数是 keyPressEvent(QKeyEvent)keyReleaseEvent(QKeyEvent)。当发生键盘事件时,将创建 QKeyEvent 的实例对象,并将实例对象作为实参传递给处理函数。

键盘事件 QKeyEvent 的常用方法如下:

count() -> int                                      # 获取按键的数量
isAutoRepeat() -> bool                              # 获取是否是重复事件
key() -> int                                        # 获取按键的代码
matches(key:QKeySequence.StandardKey) -> bool       # 如果按键匹配标准的按键
modifiers() -> Qt.KeyboardModifiers                 # 返回按键上的字符
text() -> str                                       # 返回按键的文本

如果按下一个键不放,将连续触发键盘事件,用 isAutoRepeat() 方法可以获取某个事件是否是重复事件。用 key() 方法可以获取按键的 Qt.key 代码值,不区分大小写;可以用 text() 方法获取按键的字符,区分大小写。用 matches(QKeySequence.StandardKey) 方法可以判断按下的键是否匹配标准的按键,QKeySequence.StandardKey 中定义了常规的标准按键,例如 Ctrl+C 表示复制、Ctrl+V 表示粘贴、Ctrl+S 表示保存、Ctrl+O 表示打开、Ctrl+W 或 Ctrl+F4 表示关闭。

import sys

from PySide6.QtWidgets import QApplication, QWidget

class MyWidget(QWidget):
    def keyPressEvent(self, event):
        print(f"按键【{event.text()}】按下了")

    def keyReleaseEvent(self, event):
        print(f"按键【{event.text()}】释放了")

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建一个窗口
    window = MyWidget()
    # 3.展示窗口
    window.show()
    # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())

3.4 拖放事件

3.4.1 鼠标拖放事件

拖放事件包括 鼠标进入鼠标移动鼠标释放鼠标移出 事件,对应的事件类型分别是 QEvent.DragEnter、QEvent.DragMove、QEvent.Drop 和 QEvent.DragLeave。拖放事件类分别为 QDragEnterEvent、QDragMoveEvent、QDropEvent 和 QDragLeaveEvent,其实例对象中保存着拖放信息。

QDragEnterEvent 类是从 QDropEvent 类和 QDragMoveEvent 类继承而来的,它没有自己特有的方法;QDragMoveEvent 类是从 QDropEvent 类继承而来的,它继承了 QDropEvent 类的方法;又添加了自己新的方法;QDragLeaveEvent 类是从 QEvent 类继承而来的,它也没有自己特有的方法。

QDropEvent 类常用的方法如下:

keyboardModifiers() -> Qt.keyboardModifiers             # 获取修饰键
mimeData() -> QMimeData                                 # 获取mime数据
mouseButtons() -> Qt.MouseButtons                       # 获取按下的鼠标按键
position() -> QPointF                                   # 获取鼠标位置
dropAction() -> Qt.DropAction                           # 获取采取的动作
possibleActions() -> Qt.DropActions                     # 获取可能的动作
proposedAction() -> Qt.DropActions                      # 系统推荐的动作
acceptProposedAction() -> None                          # 接受推荐的动作
setDropAction(action:Qt.DropAction) -> None             # 设置释放动作
source() -> QObject                                     # 获取被拖对象

要使一个控件或窗口接受拖放,必须用 setAcceptDrops(True) 方法设置成接受拖放,在进入事件的处理函数 dragEnterEvent(QDragEnterEvent) 中,需要把事件对象设置成 accept() ,否则无法接受后续的移动和释放事件。

拖放事件中,用 mimeData() 方法获取被拖放物体的 QMimeData 数据,MIME(multipurpose internet mail extensions) 是多用途互联网邮件扩展类型。

在释放动作中,被拖拽的物体可以从原控件中被复制或移动到目标控件中,复制或移动动作可以通过 setDropAction(Qt.DropAction) 方法来设置,其中 Qt.DropAction 可以取值如下:

Qt.CopyAction          # 复制
Qt.MoveAction          # 移动
Qt.LinkAction          # 链接
Qt.IgnoreAction        # 什么都不做
Qt.TargetMoveAction    # 目标对象接管

QDragMoveEvent 类常用方法如下:

accept() -> None            # 在控件或窗口的边界内都可接受移动事件
accept(r:QRect) -> None     # 在指定的区域内接受移动事件
answerRect() -> QRect       # 返回可以释放的区域
ignore() -> None            # 在整个边界内部忽略移动事件    
ignore(r:QRect) -> None     # 在指定的区域内忽略移动事件

拖放事件的处理函数分别如下:

dragEnterEvent(event:QDragEnterEvent)
dragMoveEvent(event:QDragMoveEvent)
dropEvent(event:QDropEvent)
dragLeaveEvent(event:QDragLeaveEvent)

3.4.2 粘贴板数据

QMimeData 类用于描述存放到粘贴板上的数据,并通过拖放事件传递粘贴板上的数据,从而在不同的程序间传递数据,也可以在同一个程序内传递数据。创建 QMimeData 实例对象的方法如下,它在 QtCore 模块中。

QMimeData()

QMimeData 可以存储的数据有文本、图像、颜色和地址等。它的常用方法如下:

formats() -> List[str]                              # 获取格式列表
hasFormat(mimetype:str) -> bool                     # 获取是否有某种格式
removeFormat(mimetype:str) -> None                  # 移除格式
    
setColorData(color:Any) -> None                     # 设置颜色数据
hasColor() -> bool                                  # 获取是否有颜色数据
colorData() -> Any                                  # 获取颜色数据
    
setHtml(html:str) -> None                           # 设置HTML数据
hasHtml() -> bool                                   # 获取是否有HTML数据
html() -> str                                       # 获取HTML数据
    
setImageData(image:Any) -> None                     # 设置图片数据
hasImage() -> bool                                  # 获取是否有图片数据
imageData() -> Any                                  # 获取图片数据
    
setText(text:str) -> None                           # 设置文本数据
hasText() -> bool                                   # 获取是否有文本数据
text() -> str                                       # 获取文本数据
    
setUrls(urls:Sequence[QUrl]) -> None                # 设置URL数据
hasUrls() -> bool                                   # 获取是否有URL数据
urls() -> List[QUrl]                                # 获取URL数据

setData(mimetype:str, data:QByteArray) -> None      # 设置某种格式的数据
data(mimetype:str) -> QByteArray                    # 获取某种格式的数据

clear() -> None                                     # 清空格式和数据

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtGui import QPixmap, QPainter
from PySide6.QtCore import QPoint, QRect

class MyWidget(QWidget):
    def __init__(self):
        # 1.调用父类Qwidget类的__init__()方法
        super().__init__()
        # 2.设置鼠标单击时光标位置
        self.start = QPoint(0, 0)
        # 3.记录窗口的中心点
        self.center = QPoint(self.width() // 2, self.height() // 2)
        # 4.设置成接受拖放事件
        self.setAcceptDrops(True)
        # 5.调用setupUi()方法初始化页面
        self.setup_ui()

    def setup_ui(self):
        # 1.设置窗口对象大小
        self.resize(700, 500)

        # 2.创建QPixmap图像
        self.pixmap = QPixmap()

        # 3.设置初始的宽度和高度
        self.pixmap_width = 0
        self.pixmap_height = 0

        # 4.设置初始的偏移量
        self.translate_x = 0
        self.translate_y = 0

    def dragEnterEvent(self, event):
        """拖动进入事件"""
        # 如果粘贴板数据是文件地址
        if event.mimeData().hasUrls():
            # 接受可移动事件
            event.accept()
        else:
            # 忽略可移动事件
            event.ignore()

    def dropEvent(self, event):
        """释放事件"""
        # 1.获取被拖放图像的地址列表
        image_urls = event.mimeData().urls()
        # 2.获取被拖放文件的地址列表
        filename = image_urls[0].toLocalFile()
        # 3.加载图片
        self.pixmap.load(filename)
        # 4.获取图片的宽高
        self.pixmap_width = int(self.pixmap.width())
        self.pixmap_height = int(self.pixmap.height())
        # 5.设置中心点的位置
        self.center = QPoint(self.width() // 2, self.height() // 2)
        # 6.更新窗口
        self.update()

    def paintEvent(self, event):
        """窗口绘制处理函数,当窗口刷新时调用该函数"""
        # 1.获取中心点
        self.center = QPoint(self.center.x() + self.translate_x, self.center.y() + self.translate_y)
        # 2.绘制区域的左上角点
        point_left_top = QPoint(self.center.x() - self.pixmap_width / 2, self.center.y() - self.pixmap_height / 2)
        # 3.绘制区域的右下角点
        point_right_bottom = QPoint(self.center.x() + self.pixmap_width / 2, self.center.y() + self.pixmap_height / 2)
        # 4.图像绘制区域
        self.rect = QRect(point_left_top, point_right_bottom)
        # 5.绘图
        painter = QPainter(self)
        painter.drawPixmap(self.rect, self.pixmap)

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建一个窗口
    window = MyWidget()
    # 3.展示窗口
    window.show()
    # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())

image-20240617135405014

3.4.3 拖拽类

如果要在程序内部拖放控件,需要先把控件定义成可移动控件,可移动控件需要在其内部定义 QDrag 的实例对象。QDrag 类用于拖放物体,它继承自 QObject 类,创建 QDrag 实例对象的方法如下:

QDrag(parent:QObject)

QDrag 类常用的方法如下:

# 实例方法
exec(supportedActions:Qt.DropActions=Qt.MoveAction) -> Qt.DropAction                    # 开始拖放操作,并返回释放时的动作
exec(supportedActions:Qt.DropActions, defaultAction:Qt.DropAction) -> Qt.DropAction     # 开始拖放操作,并返回释放时的动作
defaultAction() -> Qt.DropAction                                                        # 返回默认的释放动作
setDragCursor(cursor:QPixmap, action:Qt.DropAction) -> None                             # 设置拖拽时的光标形状
dragCursor(action:Qt.DropAction) -> QPixmap                                             # 获取拖拽时的光标形状
setHotSpot(hotspot:QPoint) -> None                                                      # 设置热点位置
hotSpot() -> QPoint                                                                     # 获取热点位置
setMimeData(data:QMimeData) -> None                                                     # 设置拖放中传递的数据
mimeData() -> QMimeData                                                                 # 获取数据
setPixmap(arg__1:QPixmap) -> None                                                       # 设置拖拽式鼠标显示的图像
pixmap() -> QPixmap                                                                     # 获取图像
source() -> QObject                                                                     # 返回被拖放物体的父控件
target() -> QObject                                                                     # 返回目标控件
supportedActions() -> Qt.DropActions                                                    # 获取支持的动作

# 静态方法
cancel() -> None                                                                        # 取消拖放

创建 QDrag 实例对象后,用 exec(supportedActions:Qt.DropActions,defaultAction:Qt.DropAction)exec(supportedActions:Qt.DropActions=Qt.MoveAction) 方法开启拖放,参数是拖放事件支持的动作和默认动作, Qt.DropAction 可以取值如下:

Qt.DropAction.CopyAction           # 复制数据到目标对象
Qt.DropAction.MoveAction           # 移动数据到目标对象
Qt.DropAction.LinkAction           # 在目标和原对象之间建立链接关系
Qt.DropAction.IgnoreAction         # 忽略,对数据不做任何事情
Qt.DropAction.TargetMoveAction     # 目标对象接管数据

setHotSpot(QPoint) 方法设置热点位置。热点位置是拖拽过程中,光标相对于控件左上角的位置。为了防止误操作,可以用 QApplication 的 setStartDragDistance(int) 方法和 setStartDragTime(msec) 方法设置拖动开始一定距离或一段时间后才开始进行拖放事件。

QDrag 类的常用信号说明如下:

actionChanged(action:Qt.DropAction)
targetChanged(newTarget:QObject)

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QPushButton, QFrame
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtGui import Qt, QDrag
from PySide6.QtCore import QPointF, QMimeData

class MyPushButton(QPushButton):
    def __init__(self, parent=None):
        super().__init__(parent)

    def mousePressEvent(self, event):
        """鼠标按键事件"""
        # 1.如果是鼠标左键按下
        if event.button() == Qt.MouseButton.LeftButton:
            # 2.创建拖拽类
            self.drag = QDrag(self)
            # 3.设置热点位置
            self.drag.setHotSpot(QPointF.toPoint(event.position()))
            # 4.获取数据
            mineData = QMimeData()
            self.drag.setMimeData(mineData)
            # 5.开启拖放操作
            self.drag.exec()

class MyFrame(QFrame):
    def __init__(self, parent=None):
        super().__init__(parent)
        # 1.允许拖放操作
        self.setAcceptDrops(True)
        # 2.设置分割线方向
        self.setFrameShape(QFrame.Shape.Box)
        # 3.创建按钮控件
        self.button_1 = MyPushButton(self)
        self.button_1.setText("button 1")
        self.button_1.move(100, 100)

        self.button_2 = MyPushButton(self)
        self.button_2.setText("button 2")
        self.button_2.move(200, 200)

    def dragEnterEvent(self, event):
        self.child = self.childAt(QPointF.toPoint(event.position()))
        event.accept()

    def dragMoveEvent(self, event):
        if self.child:
            self.child.move(QPointF.toPoint(event.position()) - self.child.drag.hotSpot())

    def dropEvent(self, event):
        if self.child:
            self.child.move(QPointF.toPoint(event.position()) - self.child.drag.hotSpot())

class MyWidget(QWidget):
    def __init__(self):
        # 1.调用父类Qwidget类的__init__()方法
        super().__init__()
        # 2.设置成接受拖放事件
        self.setAcceptDrops(True)
        # 3.调用setupUi()方法初始化页面
        self.setup_ui()

    def setup_ui(self):
        # 1.设置窗口对象大小
        self.resize(700, 500)

        self.frame_1 = MyFrame(self)
        self.frame_2 = MyFrame(self)

        layout = QHBoxLayout(self)
        layout.addWidget(self.frame_1)
        layout.addWidget(self.frame_2)

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建一个窗口
    window = MyWidget()
    # 3.展示窗口
    window.show()
    # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())

image-20240617140354359

3.4.4 剪贴板

剪贴板 QClipboard 类似于拖放,可以在不同的程序间用复制和粘贴操作来传递数据。QClipboard 位于 QtGui 模块中,继承自 QObject 类,用 QClipboard(parent=None) 方法可以创建剪贴板对象。可以直接往剪贴板中复制文本数据、 QPixmap 和 QImage,其他数据类型可以通过 QMimeData 来传递。

剪贴板 QClipboard 的常用方法如下所示。

setText(text:str, mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> None               # 将文本赋值到剪贴板
text(mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> str                             # 从剪贴板上获取文本
text(subtype:str, mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> Tuple[str, str]    # 从subtype指定的数据类型中获取文本
setPixmap(pixmap:QPixmap, mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> None       # 将QPixmap图像复制到剪贴板上
pixmap(mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> QPixmap                       # 从剪贴板上获取QPixmap图像
setImage(image:QImage, mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> None          # 将QImage图像复制到剪贴板上
image(mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> QImage                         # 从剪贴板上获取QImage图像
setMimeData(data:QMimeData, mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> None     # 将QMimeData数据复制到剪贴板上
mimeData(mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> QMimeData                   # 从剪贴板上获取QMimeData数据
clear(mode:QClipboard.Mode=QClipboard.Mode.Clipboard) -> None                           # 清空剪贴板

剪贴板 QClipboard 的常用信号如下所示。

changed(mode:QClipboard.Mode=QClipboard.Mode.Clipboard)     # 剪贴板模式改变时发射信号
dataChanged()                                               # 剪贴板数据改变时发射信号
findBufferChanged()                                         # 在查找缓冲区被更改时发射信号。这只适用于macOS。
selectionChanged()               

3.5 窗口和控件的常用事件

窗口和控件的常用事件包括窗口或控件的隐藏、显示、移动、缩放、重绘、关闭、获得和失去焦点等,通常需要重写这些事件的处理函数,以便达到特定的目的。

3.5.1 显示事件和隐藏事件

在用 show() 方法或 setVisible(True) 方法显示一个顶层窗口之前会发生 QEvent.Show 事件,调用 showEvent(QShowEvent) 处理函数,显示事件类 QShowEvent 只有从 QEvent 继承的属性,没有自己特有的属性。在用 hide() 方法或 setVisible(False) 方法隐藏一个顶层窗口之前会发生 QEvent.Hide 事件,调用 hideEvent(QHideEvent) 处理函数,隐藏事件类 QHideEvent 只有从 QEvent 继承的属性,没有自己特有的属性。

3.5.2 缩放事件和移动事件

当一个窗口或控件的宽度和高度发生改变时会触发 QEvent.Resize 事件,调用 resizeEvent(QResizeEvent) 处理函数。缩放事件类 QResizeEvent 只有两个方法 oldSize()size(),分别返回缩放前和缩放后的窗口尺寸 QSize。

当改变一个窗口或控件的位置时会触发 QEvent.Move 事件,调用 moveEvent(QMoveEvent) 处理函数。移动事件类 QMoveEvent 只有两个方法 oldPos()pos() ,分别返回窗口左上角移动前和移动后的位置 QPoint。

3.5.3 移入事件和移出事件

当光标进入窗口时,会触发 QEvent.Enter 进入事件,进入事件的处理函数是 enterEvent(QEnterEvent);当光标离开窗口时,会触发 QEvent.Leave 离开事件,离开事件的处理函数是 leaveEvent(QEvent)。可以重写这两个函数,以达到特定的目的。

3.5.4 焦点事件

当一个控件获得和失去键盘输入焦点时,会触发 QEvent.FocusIn 和 QEvent.FocusOut 事件,这两个事件的处理函数分别是 focusInEvent(QFocusEvent)focusOutEvent(QFocusEvent),焦点事件类 QFocusEvent 的方法有 gotFocus()lostFocus()reason()。当事件类型 type() 的值是 QEvent.FocusIn 时,gotFocus()方法的返回值是 True,当事件类型 type() 的值是 QEvent.FocusOut 时,lostFocus() 方法的返回值是 True;reason() 方法返回获得焦点的原因,其返回值的类型是 Qt.FocusReason,其值如下:

Qt.FocusReason.MouseFocusReason
Qt.FocusReason.TabFocusReason
Qt.FocusReason.BacktabFocusReason
Qt.FocusReason.ActiveWindowFocusReason
Qt.FocusReason.PopupFocusReason
Qt.FocusReason.ShortcutFocusReason
Qt.FocusReason.MenuBarFocusReason
Qt.FocusReason.OtherFocusReason

3.5.5 绘制事件

绘制事件 QPaintEvent 是窗体系统产生的,在一个窗口首次显示、隐藏后又显示、缩放窗口、移动控件,以及调用窗口的 update()repaint()resize() 方法时都会触发 QEvent.Paint 事件。绘制事件发生时,会调用 paintEvent(QPaintEvent) 处理函数,该函数是受保护的,不能直接用代码调用,通常在 paintEvent(QPaintEvent) 处理函数中处理一些与绘图、显示有关的事情。

绘制事件类 QPaintEvent 只有两个方法 rect()region() 方法,分别返回被重绘的矩形区域 QRect 和裁剪区域 QRegion。

3.5.6 关闭事件

当用户单击窗口右上角的 ❌ 按钮或执行窗口的 close() 方法时,会触发 QEvent.Close 事件,调用 closeEvent(QCloseEvent) 处理该事件。如果事件用 ignore() 方法忽略了,则什么也不会发生;如果事件用 accept() 方法接收了,首先窗口被隐藏,在窗口设置了 setAttribute(Qt.WA_DeleteOnClose,True) 属性的情况下,窗口会被删除。窗口事件类 QCloseEvent 没有特殊的属性,只有从 QEvent 继承来的方法。

3.5.7 定时器事件

从 QObject 类继承的窗口和控件都会有 startTimer(ms:int,timerType=Qt.CoarseTimer) 方法和 killTimer(int) 方法。 startTimer() 方法会启动一个定时器,并返回 定时器的 ID 号。如果不能启动定时器,则返回值是 0,参数 ms 是 定时器的事件间隔,单位是毫秒;timerType 是 定时器的类型,可以取值如下:

Qt.TimerType.PreciseTimer
Qt.TimerType.CoarseTimer
Qt.TimerType.VeryCoarseTimer

窗口或控件可以用 startTimer() 方法启动多个定时器,启动定时器后,会触发 timerEvent(QTimerEvent) 事件,QTimerEvent 是定时器事件类。用 QTimerEvent 的 timerId() 方法可以获取触发定时器事件的定时器 ID;用 killTimer(id:int) 方法可以停止定时器,参数是定时器的 ID。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QPushButton, QHBoxLayout
from PySide6.QtCore import Qt
from functools import partial

class MyWidget(QWidget):
    def __init__(self):
        # 1.调用父类Qwidget类的__init__()方法
        super().__init__()

        # 2.启动定时器
        timer_id_1 = self.startTimer(500, Qt.TimerType.PreciseTimer)
        timer_id_2 = self.startTimer(1000, Qt.TimerType.CoarseTimer)

        # 3.创建布局
        layout = QHBoxLayout(self)

        # 4.创建按钮控件
        button_1 = QPushButton("停止第一个定时器", self)
        button_2 = QPushButton("停止第二个定时器", self)

        # 5.将按钮添加到布局中
        layout.addWidget(button_1)
        layout.addWidget(button_2)

        # 6.定义信号与槽的连接
        # 使用lambad传递参数
        button_1.clicked.connect(lambda: self.kill_timer(timer_id_1))
        # 使用partial()函数传递参数
        button_2.clicked.connect(partial(self.kill_timer, timer_id_2))

    def kill_timer(self, value):
        # 关闭定时器
        if value:
            self.killTimer(value)

    def timerEvent(self, event):
        """定时事件"""
        print(f"定时器ID: {event.timerId()}")

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建一个窗口
    window = MyWidget()
    # 3.展示窗口
    window.show()
    # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())

3.6 事件过滤

​ 一个控件或窗口的 event() 函数是所有事件的集合点,可以在 event() 函数中设置某种类型的事件是接收还是忽略,另外还可以用事件过滤器把某种事件注册给其他控件或窗口进行监控、过滤和拦截。

一个控件产生的事件可以交给其他控件进行处理,而不是由自身的处理函数处理,原控件称为 被监测控件,进行处理事件的控件称为 监测控件。要实现这个目的,需要将被监测控件注册给监测控件。

要把被监测对象的事件注册给监测控件,需要在被监测控件上安装监测器,被监测控件的监测器用 installEventFilter(QObject) 方法定义,其中 QObject 是监测控件。如果一个控件上安装了多个事件过滤器,则后安装的过滤器先被使用。用 removeEventFilter(QObject) 方法可以解除监测。

要实现对被监测对象事件的过滤,需要在监测对象上重写过滤函数 eventFilter(QObject,QEvent),其中参数 QObject 是传递过来的被监测对象,QEvent 是被检测对象的事件类对象。过滤函数如果返回 True,表示事件已经过滤掉了;如果返回 False,表示事件没有被过滤。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QPushButton, QFrame
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtGui import Qt, QDrag
from PySide6.QtCore import QPointF, QMimeData, QEvent

class MyPushButton(QPushButton):
    def __init__(self, parent=None, text=""):
        super().__init__(text, parent)

    def mousePressEvent(self, event):
        """鼠标按键事件"""
        # 1.如果是鼠标左键按下
        if event.button() == Qt.MouseButton.LeftButton:
            # 2.创建拖拽类
            self.drag = QDrag(self)
            # 3.设置热点位置
            self.drag.setHotSpot(QPointF.toPoint(event.position()))
            # 4.获取数据
            mineData = QMimeData()
            self.drag.setMimeData(mineData)
            # 5.开启拖放操作
            self.drag.exec()

class MyFrame(QFrame):
    def __init__(self, parent=None):
        super().__init__(parent)
        # 1.允许拖放操作
        self.setAcceptDrops(True)
        # 2.设置分割线方向
        self.setFrameShape(QFrame.Shape.Box)
        # 3.创建按钮控件
        self.button = MyPushButton(self, "button")
        self.button.move(100, 100)

    def dragEnterEvent(self, event):
        self.child = self.childAt(QPointF.toPoint(event.position()))
        event.accept() if self.child else event.ignore()

    def dragMoveEvent(self, event):
        if self.child:
            self.child.move(QPointF.toPoint(event.position()) - self.child.drag.hotSpot())

class MyWidget(QWidget):
    def __init__(self):
        # 1.调用父类Qwidget类的__init__()方法
        super().__init__()
        # 2.设置成接受拖放事件
        self.setAcceptDrops(True)
        # 3.调用setupUi()方法初始化页面
        self.setup_ui()

    def setup_ui(self):
        # 1.设置窗口对象大小
        self.resize(700, 500)

        self.frame_1 = MyFrame(self)
        self.frame_2 = MyFrame(self)

        layout = QHBoxLayout(self)
        layout.addWidget(self.frame_1)
        layout.addWidget(self.frame_2)

        # 将按钮的事件注册到窗口上
        self.frame_1.button.installEventFilter(self)
        self.frame_2.button.installEventFilter(self)

    def eventFilter(self, watched, event):
        if watched ==  self.frame_1.button and event.type() == QEvent.Type.Move:
            self.frame_2.button.move(event.pos())
            return True
        if watched ==  self.frame_2.button and event.type() == QEvent.Type.Move:
            self.frame_1.button.move(event.pos())
            return True
        return super().eventFilter(watched, event)

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建一个窗口
    window = MyWidget()
    # 3.展示窗口
    window.show()
    # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())

3.7 自定义事件

用户定义自己的事件首先要创建一个继承自 QEvent 的类,并给自定义事件一个 ID 号(值),该 ID 号的值只能在 QEvent.User(值为 1000)和 QEvent.MaxUser(值为 65535)之间,且不能和已有的 ID 号相同。为保证 ID 号的值不冲突,可以用 QEvent 类的静态函数 registerEventType(hint:int=-1) 注册自定义事件的 ID 号,并检查给定的 ID 号是否合适,如果 ID 号合适,会返回指定的 ID 号值;如果不合适,则推荐一个 ID 号值。在自定义事件类中根据情况定义所需的属性和方法。

需要用 QCoreApplication 的 sendEvent(receiver,event) 函数或 postEvent(receiver,event) 函数发送自定义事件,其中 receiver 是自定义事件的接收者,event 是自定义事件的实例化对象。用 sendEvent(receiver,event) 函数发送的自定义事件被 QCoreApplication 的 notify() 函数直接发送给 receiver 对象,返回值是事件处理函数的返回值;用 postEvent(receiver,event) 函数发送的自定义事件添加到事件队列中,它可以在多线程应用程序中用于在线程之间交换事件。

控件或窗口上都有个 customEvent(event) 函数,用于处理自定义事件,自定义事件类的实例作为实参传递给形参 event,也可以用 event(event) 函数处理,在 customEvent(event) 函数或 event(event) 函数中根据事件类型进行相应的处理,也可用事件过滤器来处理。

from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QEvent, QCoreApplication

class CustomEvent(QEvent):
    # 自定义事件的ID
    CustomEventType = QEvent.Type(QEvent.registerEventType())

    def __init__(self, data=None):
        super().__init__(CustomEvent.CustomEventType)
        self.data = data

class MyWidget(QWidget):
    def customEvent(self, event):
        """自定义事件的处理函数"""
        if isinstance(event, CustomEvent):
            print(f"Received custom event with data: {event.data}")

if __name__ == "__main__":
    # 1.创建一个QApplication类的实例
    app = QApplication(sys.argv)
    # 2.创建并分发自定义事件
    event = CustomEvent(data="Hello, Custom Event!")
    # 3.指定自定义事件的接收者
    receiver = MyWidget()
    # 4.使用QCoreApplication.postEvent来发送事件
    QCoreApplication.postEvent(receiver, event)
    # 5.进入程序的主循环并通过exit()函数确保主循环安全结束
    sys.exit(app.exec())
posted @ 2025-04-16 08:54  Yasuo_Hasaki  阅读(33)  评论(0)    收藏  举报
//雪花飘落效果