掌握 PyQt5:从零开始的桌面应用开发

PyQT5------图形化界面

文章目录

需要的库:pyqt5,pyqt5-tools

集成化图形界面工具

安装完上面两个库之后,按着以下顺序找图形化界面工具的程序

在项目里面打开Lib-->site-packages -> qt5-applications -> Qt -> bin -> designer.exe

找对之后,直接双击打开就是图形界面工具

找到这个程序之后就是引入到pycharm里面的外部工具了,在设置里面,找到外部工具那一栏,点击加号,名称自拟,在程序那一栏找刚刚找到的图形界面工具,实参不用管,工作目录写ProjectFileDir

这是图形界面工具,我们使用这个工具之后产生的是ui后缀的文件,不能直接被pycharm使用,还需要转化为py格式,所以引入另一个外部工具------------pyuic,可以使用pycharm搜索安装或者pip安装,同理先找程序位置,在程序那一栏选择该地址,在实参里面写-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py,工作目录写FileDir

这里的参数不用管为什么,只需要配置一次的事,不必理会

如果有好奇的,也没事,我问过gpt了

为什么使用 ProjectFileDir?

在 PyCharm 中,配置外部工具时,使用$ProjectFileDir$作为工作目录(working directory)是因为它会自动解析为当前项目的根目录。这是一种方便且灵活的方式,以确保无论你的项目结构如何,脚本或工具都能在正确的目录下运行。

  1. 项目根目录路径 : $ProjectFileDir$ 用于引用PyCharm项目的根目录。这个变量会在运行外部工具时被自动替换为项目文件夹的绝对路径。这意味着不管你在哪里运行该工具,它总是能在项目的根目录下找到你的资源、脚本和输出文件。

  2. 跨平台兼容性 : 使用这种变量避免了手动输入特定路径,特别是在多人协作或跨平台开发时(如在 Windows 和 Unix 系统间切换)。这样,可以避免路径分隔符(如 \/)造成的问题。

  3. 重用配置: 当你在不同的机器或克隆项目到不同目录时,不需要修改工具配置中的路径,这使得项目配置更加方便和易于管理。

示例场景

假设你有一个名为 test.py 的 Python 脚本,存放在项目的根目录下。如果在 PyCharm 中配置外部工具执行这个脚本:

  • 外部工具的程序 : python
  • 参数 : test.py
  • 工作目录 : $ProjectFileDir$

在运行时,PyCharm 会将 $ProjectFileDir$ 替换为实际项目的路径,比如 C:\Users\username\Projects\MyProject,然后实际的命令将变为:

bash 复制代码
C:\Python39\python.exe C:\Users\username\Projects\MyProject\test.py

其他 Varaiables

PyCharm 还提供了许多其他预定义变量,可以在配置外部工具时使用,例如:

  • $FilePath$: 当前打开文件的路径。
  • $FileDir$: 当前打开文件的目录。
  • $ProjectName$: 当前项目的名称。

使用这些变量可以提高开发效率和减少配置错误,使得项目的可移植性和适应性更强。

pyuic参数解释

在 PyCharm 中配置外部工具时,实参(arguments)中的 -m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py 用于运行 PyQt5 的图形用户界面(GUI)文件转换工具 pyuic,具体来说,解释如下:

  1. -m PyQt5.uic.pyuic:

    • 这个部分的意思是告诉 Python 运行一个模块,-m 选项用于执行库中的模块。
    • 在这里,PyQt5.uic.pyuic 是 PyQt5 库中的一个模块,负责将 .ui 文件(由 Qt Designer 生成的 GUI 设计文件)转换为 Python 代码。
  2. $FileName$:

    • 这是一个 PyCharm 的预定义变量,它会自动被替换为当前打开的文件的完整路径和文件名。
    • 例如,如果你打开的文件是 C:\Users\username\Projects\MyProject\test.ui,那么 $FileName$ 就会被替换成这个完整路径。
  3. -o $FileNameWithoutExtension$.py:

    • -o 是一个参数,表示输出文件(output file)。
    • $FileNameWithoutExtension$ 是另一个预定义变量,它会被替换为当前文件的名称(不带文件扩展名)。
    • 如果当前打开的文件是 test.ui,则 $FileNameWithoutExtension$ 将被替换为 test,所以 -o $FileNameWithoutExtension$.py 就会变成 -o test.py
    • 这意味着输出的 Python 文件将被命名为 test.py(与 UI 文件同名,但扩展名为 .py)。

整体含义

综上所述,整个命令的目的是将当前打开的 .ui 文件转换为 .py 文件,命名方式为与原 .ui 文件相同的名字,但扩展名为 .py。这在使用 Qt Designer 设计用户界面并希望将其集成到 Python 程序中时非常有用。

示例

假设你在 PyCharm 中打开一个名为 mainwindow.ui 的文件,运行该外部工具后,命令将被解释为:

bash 复制代码
python -m PyQt5.uic.pyuic C:\Users\username\Projects\MyProject\mainwindow.ui -o C:\Users\username\Projects\MyProject\mainwindow.py

这条命令会将 mainwindow.ui 文件转换为 mainwindow.py 文件,生成的 Python 文件中包含了构建该用户界面的代码,从而可以在 Python 程序中使用它。

使用PyQt5和pyuic

首先是点击工具,在下拉式弹窗里面选择外部工具,选择PyQt5,这时候运行的页面就是自制的窗口了,经过一系列设计,最后点击保存,一般是保存在项目里面,会产生一个ui文件

对着这个ui文件鼠标右键,选择外部工具pyuic,就会自动生成一个同名的py文件,这个py文件就是我们刚刚设计的图形界面

现在的pycharm已经解决了pyqt5只能使用低版本的问题,所以,直接使用你现在使用的python版本就可以了

这些外部工具的安装路径最好不能有中文,可能报错

创建pyqt5的程序

两个基本要素:应用,窗口

  1. 创建应用

    使用类QApplication

  2. 创建窗口

  3. 创建控件

    控件就是各种对话框,按钮,输入框等等

我们要使用的各种类,都在PyQt5下面的QtWidgets类里面

还要导入应用程序QApplication,

以及三种常用的基本窗口QWidget(小窗口),QMainWindow(大窗口),QDialog

  • QWidget 简单窗口,输入框,标签
  • QMainWindow 复杂窗口,也就是整个应用界面
  • QDialog 对话框

最后使用sys库,自带的,接收底层参数

创建一个窗口

因为这个程序比较复杂,使用类的方式创建,也就是继承类

python 复制代码
from PyQt5.QtWidgets import QApplication,QWidget,QMainWindow,QDialog
import sys

# 继承类创建程序
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()

        # 设置窗口大小
        self.resize(1000,900) # 长,宽
        # 设置窗口位置
        self.move(600,300)  # 长,宽
        # 设置窗口的标题
        self.setWindowTitle('这是一个pyqt窗口')  

if __name__ == '__main__':
    app = QApplication(sys.argv)   # 实例化应用类,使用sys.argv,可以接收参数到命令行
    window = MyWindow()   # 窗口默认隐藏
    window.show()   # 显示窗口
    # app.exec_()   # 运行程序
    sys.exit(app.exec_())

首先是继承小窗口

使用初始化函数,设置窗口的大小,位置,标题等

  • self.resize(长,宽)
  • self.move(长,宽)
  • self.setWindowTitle(窗口名称)

后面的参数就是以像素为单位,在屏幕上创建

然后就是主函数启动程序,使用QApplication实例化,使用sys.argv接收参数,再实例化设计的窗口,

对于程序退出时,有两种写法

app.exec_()和sys.exit(app.exec_())的区别

在创建 PyQt5 程序时,app.exec_()sys.exit(app.exec_()) 都是用来启动应用程序的事件循环,但它们在使用时会有些不同。下面详细解释这两者的区别:

1. app.exec_()

  • 功能 : app.exec_() 是启动 PyQt5 应用程序事件循环的简单调用。它开始处理事件,如用户交互、窗口更新等。

  • 返回值 : 此方法返回一个整数值,表示应用程序的退出状态。这个值通常用于在程序结束时进行一些状态检查,但如果只是单独调用 app.exec_(),这个返回值并没有被使用。

  • 使用方式: 你可以直接调用它来启动应用程序:

    python 复制代码
    app = QApplication(sys.argv)
    # 其他初始化代码...
    app.exec_()  # 启动事件循环

2. sys.exit(app.exec_())

  • 功能 : sys.exit() 是一个用于结束程序的函数,它接受一个整数参数,通常是应用程序的退出状态码。

  • 作用 : 当你把 app.exec_() 包裹在 sys.exit() 中时,你确保应用程序在结束时返回其退出状态码到操作系统。这是一个良好的编程习惯,特别是在需要与其他系统工具、脚本或服务集成时,它们通常根据退出状态来进行错误检查或异常处理。

  • 返回值 : app.exec_() 的返回值被传递给 sys.exit(),这样可以使操作系统知道应用是正常退出(返回0)还是异常退出(返回其他非零值)。

  • 使用方式: 通常,更推荐的写法是:

    python 复制代码
    app = QApplication(sys.argv)
    # 其他初始化代码...
    sys.exit(app.exec_())  # 启动事件循环并确保正确退出状态

总结

  • 就功能而言app.exec_() 启动事件循环,而 sys.exit(app.exec_()) 启动事件循环并确保在程序结束时返回正确的退出代码。
  • 最佳实践 :推荐使用 sys.exit(app.exec_()),以确保在应用程序结束时,正确传递退出状态给操作系统。这有助于在需要时进行错误处理或调试。

所以,虽然两者在一定情况下可以互换使用,但出于健壮性和可维护性的考虑,最好使用 sys.exit(app.exec_())

添加控件------按钮,对话框等等

  • 标签 QLabel
  • 按钮 QPushButton
  • 输入框 QLineEdit
python 复制代码
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()

        # 设置窗口大小
        self.resize(700,600)
        # 设置窗口位置
        self.move(600,300)
        # 设置窗口的标题
        self.setWindowTitle('这是一个pyqt窗口')

        # 创建标签
        self.label = QLabel(self)# 实例化标签,把当前窗口传进去
        self.label.setText('这是一个标签')   # 设置标签内容
        self.label.move(100,200)   # 设置标签位置

        # 创建按钮
        self.button = QPushButton(self)
        self.button.setText('这是按钮')
        self.button.move(250,200)

信号与槽

每个按钮和标签都是独立的,互不关联,现在我们希望按下按钮,标签发生改变等等一系列操作

可以通过槽函数,把指令传输给其他的地方

python 复制代码
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()

        # 设置窗口大小
        self.resize(700,600)
        # 设置窗口位置
        self.move(600,300)
        # 设置窗口的标题
        self.setWindowTitle('这是一个pyqt窗口')

        # 创建标签
        self.label = QLabel(self)# 实例化标签,把当前窗口传进去
        self.label.setText('这是一个标签')   # 设置标签内容
        self.label.move(100,200)   # 设置标签位置

        # 创建按钮
        self.button = QPushButton(self)
        self.button.setText('这是按钮')
        self.button.move(250,200)
        self.button.clicked.connect(self.btn_click)

    def btn_click(self):
        self.label.setText('点击')

这里我们在类里面创建了一个槽函数,这个函数就是点击按钮的时候,需要发生的事件,这里就是把标签的内容改成"点击"

然后就是把按钮和槽函数绑定

首先是按钮,发生点击事件,也就是按钮被点击了clicked,然后使用connect链接槽函数

鼠标事件和键盘事件

就是点击鼠标左右键,还有按下键盘这些动作

python 复制代码
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()

        # 设置窗口大小
        self.resize(700,600)
        # 设置窗口位置
        self.move(600,300)
        # 设置窗口的标题
        self.setWindowTitle('这是一个pyqt窗口')

        # 创建标签
        self.label = QLabel(self)# 实例化标签,把当前窗口传进去
        self.label.setText('这是一个标签')   # 设置标签内容
        self.label.move(100,200)   # 设置标签位置

        # 创建按钮
        self.button = QPushButton(self)
        self.button.setText('这是按钮')
        self.button.move(250,200)
        self.button.clicked.connect(self.btn_click)
    # 改写鼠标事件
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.label.setText('鼠标左键')
        elif event.button() == Qt.RightButton:
            self.label.setText('鼠标右键')
        else:
            self.label.setText('鼠标未按下')
    def mouseReleaseEvent(self, a123):
        self.label.setText('鼠标释放')

    # 键盘事件
    def keyPressEvent(self, event):
        self.label.setText('键盘按下')

    def keyReleaseEvent(self, a0):
        self.label.setText('键盘释放')

    # 槽函数
    def btn_click(self):
        self.label.setText('点击')

这里有两个鼠标事件,第一个是鼠标按下mousePressEvent,这里需要使用Qt库来判断鼠标的动作

初始化函数加一:from PyQt5.QtCore import Qt

鼠标事件的第一个参数是这个程序本身,第二个参数则是事件的别名,这里的event.button() 就是鼠标的按键,如果是鼠标左键,或者右键等等就执行对应的函数

  • mousePressEvent 鼠标按下
  • mouseReleaseEvent 鼠标释放
  • keyPressEvent 键盘按下
  • keyReleaseEvent 键盘释放

关于鼠标事件的第二个函数的参数

在 PyQt5 中,鼠标事件(以及其他事件)的方法是由 Qt 框架在内部调用的。这些事件方法通常名称是固定的,例如 mouseReleaseEvent(self, event),但是你可以自定义它们的参数名称。event 是一个常用的命名习惯,但它并不是必须的。

python 复制代码
def mouseReleaseEvent(self, a123):  # 这实际上是可行的
    # 处理鼠标释放事件
    pass

参数名称的影响

1. 参数的实际用途
  • 方法的第二个参数(如 event)是一个事件对象,封装了关于事件的所有信息(如鼠标位置、按钮状态等)。
  • 你可以用任何有效的标识符来替代 event,但在使用这个参数时,必须知道它代表的是哪个事件类型。
2. 代码可读性
  • 使用 event 作为参数名可以提高代码的可读性。当其他开发者(或你自己)看到这个方法时,自然会理解这是一个事件对象。
  • 如果使用不太明确的名称(如 a123),尽管代码仍然可以正常工作,但其他人可能会难以理解其含义。
3. 调用上下文
  • Qt 框架根据事件类型和相关信息自动调用这些方法。在调用时,Qt 会将实际的事件对象传递给你自定义的参数。因此,无论你命名参数为多少,实际上传递的都是同一个事件对象。

小结

  • 在技术上,参数的名称是可随意选择的,不会影响功能。例如,def mouseReleaseEvent(self, custom_name):def mouseReleaseEvent(self, event): 的效果是相同的。
  • 然而,为了提高代码的清晰度和可维护性,建议遵循社区的命名规范,使用 event 或更具描述性的名称。这样能够提高代码的可读性,帮助他人理解你的代码。

布局管理

之前学的那种move,resize等等都是固定好位置的布局,不能随着窗口大小的改变而改变,就是不太美观,可能会出问题,所以引申出布局管理来

  1. 水平布局 QHBoxLayout
  2. 垂直布局 QVBoxLayout
  3. 网格布局
  4. 表单布局

盒子布局

水平布局和垂直布局需要使用盒子管理器,就是那些控件需要在盒子里面摆放

python 复制代码
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()

        # 设置窗口大小
        self.resize(700,600)
        # 设置窗口位置
        self.move(600,300)
        # 设置窗口的标题
        self.setWindowTitle('这是一个pyqt窗口')

        # 创建按钮
        btn1 = QPushButton('按钮1',self)
        btn2 = QPushButton('按钮2',self)
        btn3 = QPushButton('按钮3',self)
        # 放置按钮到盒子
        v_layout = QVBoxLayout()  # 实例化垂直布局盒子

        v_layout.addWidget(btn1)
        v_layout.addWidget(btn2)
        v_layout.addWidget(btn3)

        # 把盒子放在窗体里面
        self.setLayout(v_layout)

这些控件在初始化的时候就确定好了,不用再写函数了

这里我们创建了三个按钮btn123,还有一个垂直布局的盒子,把按钮放在盒子里面,最后把盒子整个放在窗体里面就完了

弹簧--addStretch()

在垂直布局或者水平布局的时候,不希望按钮或者标签之间离的太近,或者离得太远,就需要弹簧来控制,

比如在按钮1和2之间加一个弹簧

python 复制代码
v_layout.addWidget(btn1)
v_layout.addStretch()
v_layout.addWidget(btn2)
v_layout.addWidget(btn3)

很明显,无论如何拖动窗口一直保持这个布局

这里我们可以添加很多个弹簧

python 复制代码
v_layout.addWidget(btn1)
v_layout.addStretch(1)
v_layout.addWidget(btn2)
v_layout.addStretch(4)
v_layout.addWidget(btn3)

这个弹簧里面的参数,就是弹簧之间的比例,不写参数默认是1,也就是弹簧所占的空间一样大

像这里,我写了1和4,创建页面的时候就是一比四的空隙

水平布局和垂直布局类似

只需要改调用的盒子类型,就可以从垂直布局改成水平布局了

python 复制代码
# 创建按钮
btn1 = QPushButton('按钮1',self)
btn2 = QPushButton('按钮2',self)
btn3 = QPushButton('按钮3',self)
# 放置按钮到盒子
v_layout = QHBoxLayout()  # 实例化水平布局盒子   # 这里从QVBoxLayout发生改变

v_layout.addWidget(btn1)
v_layout.addStretch(1)
v_layout.addWidget(btn2)
v_layout.addStretch(4)
v_layout.addWidget(btn3)

网格布局 -- QGridLayout

python 复制代码
# 创建按钮
btn1 = QPushButton('按钮1',self)
btn2 = QPushButton('按钮2',self)
btn3 = QPushButton('按钮3',self)
btn4 = QPushButton('按钮4',self)
btn5 = QPushButton('按钮5',self)
# 放置按钮到盒子
g_layout = QGridLayout()
g_layout.addWidget(btn1,0,0)
g_layout.addWidget(btn2,1,0)
g_layout.addWidget(btn3,0,1)
g_layout.addWidget(btn4,2,2)
g_layout.addWidget(btn5,2,4)

self.setLayout(g_layout)

这里就是设置了5个按钮,下面就是放置的位置

使用网格布局的时候,这个网格的大小,由填写的xy坐标最大值决定,比如在上面的5个按钮中,最大的x是2,最大的y是4,由于第3列是空行,所以,这里的按钮5自动填充在(2,3)了

表单布局 -- QFormLayout

这个布局在使用的时候,是按行添加内容的,一般处理输入用户名密码之类的

python 复制代码
f_layout = QFormLayout()
f_layout.addRow(QLabel('用户名:'),QLineEdit())
f_layout.addRow(QLabel('密码:'),QLineEdit())
self.setLayout(f_layout)

在参数里面直接写标签和输入框库就可以了

布局可以互相嵌套

因为按钮一般存放在布局里面,而布局在类里面,所以第二个参数self可以不写

布局嵌套

python 复制代码
# 表单布局
f_layout = QFormLayout()
f_layout.addRow(QLabel('用户名:'),QLineEdit())
f_layout.addRow(QLabel('密码:'),QLineEdit())

# 水平布局盒子
h_layout = QHBoxLayout()
btn_ok = QPushButton('确认')
btn_cancel = QPushButton('取消')
h_layout.addWidget(btn_ok)
h_layout.addWidget(btn_cancel)
f_layout.addRow(h_layout)

self.setLayout(f_layout)

首先是实例化一个表单布局

使用addRow添加控件,这里使用了QLabel添加标签,使用QLineEdit添加输入框

同样在添加控件,然后就是实例化一个水平布局的盒子,使用QPushButton创建俩按钮,再使用addWidget添加控件,最后把这个水平布局盒子按行添加到表单布局里面

常用控件

  1. 文本输入控件
    1. 单行文本输入 -- QLineEdit
    2. 多行文本输入 -- QTextEdit
  2. 单选控件
  3. 复选控件(多选)
  4. 列表控件
  5. 下拉式列表控件
  6. 表格控件

文本输入框

在输入文本的时候可以是单行输入也可以是多行输入

在输入的时候,还可以设置显示模式,比如输入密码的时候,不显示输入的信息,只有那几个黑点,用来保护密码

python 复制代码
f_layout = QFormLayout()
user_name = QLineEdit('Aa123') # 参数啥也不写就是空
user_password = QLineEdit()
user_password.setEchoMode(QLineEdit.Password)  # 把密码的输入框设置为密码模式,就看不到输入的内容了

text_Edit = QTextEdit()
f_layout.addRow(QLabel('用户名:'),user_name)
f_layout.addRow(QLabel('密码:'),user_password)
f_layout.addRow(text_Edit)

self.setLayout(f_layout)

我们在实例化输入框的时候可以在参数里面写一些信息,这里的Aa123就会显示在输入框里面

单选按钮--QRadioButton

python 复制代码
h_layout = QHBoxLayout()
btn1 = QRadioButton('A')
btn2 = QRadioButton('B')
btn3 = QRadioButton('C')
h_layout.addWidget(QLabel("请选择:"))
h_layout.addWidget(btn1)
h_layout.addWidget(btn2)
h_layout.addWidget(btn3)
self.setLayout(h_layout)

现在是创建了几个按钮,想要把这些按钮绑定事件需要使用槽函数

python 复制代码
 h_layout = QHBoxLayout()
    btn1 = QRadioButton('A')
    btn2 = QRadioButton('B')
    btn3 = QRadioButton('C')
    h_layout.addWidget(QLabel("请选择:"))

    btn1.toggled.connect(self.btn_click) 
    btn2.clicked.connect(self.btn_click)
    btn3.toggled.connect(self.btn_click)

    h_layout.addWidget(btn1)
    h_layout.addWidget(btn2)
    h_layout.addWidget(btn3)
    self.setLayout(h_layout)

def btn_click(self):
    btn = self.sender()
    if btn.isChecked():   # 同一布局下面是互斥的
        msg = f'你选择了:{btn.text()}'
        print(msg)

增加了按钮和槽函数的链接

由两种方式可以连接按钮和槽函数:clicked(点击),toddled(选中)

槽函数:这里使用了self.sender() sender可以获取发送信息的对象

btn.isChecked()是为了再次确认按钮被选择

{btn.text()}是显示按钮的文本信息

把信息写在窗体里面

按照我的想法测试了一下,竟然实现了

这里我是想实现把你的操作输出在窗体里面,也就是你选择了ABC等等,之前是打印在了pycharm里面

完整代码:

python 复制代码
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(700,600)
        self.move(600,300)
        self.setWindowTitle('pyqt窗口')

        self.choose_btn = QLabel()
        h_layout = QHBoxLayout()
        btn1 = QRadioButton('A')
        btn2 = QRadioButton('B')
        btn3 = QRadioButton('C')
        h_layout.addWidget(QLabel("请选择:"))

        btn1.toggled.connect(self.btn_click)
        btn2.clicked.connect(self.btn_click)
        btn3.toggled.connect(self.btn_click)

        h_layout.addWidget(btn1)
        h_layout.addWidget(btn2)
        h_layout.addWidget(btn3)
        h_layout.addWidget(self.choose_btn)
        self.setLayout(h_layout)

    def btn_click(self):
        btn = self.sender()
        self.choose_btn.setText(f'你选择了{btn.text()}')

这里我增加了一个标签choose_btn,我希望这个标签显示的是我的选择,所以在点击按钮时,触发槽函数,进而输出信息到窗体

复选框--QCheckBox

python 复制代码
v_layout = QVBoxLayout()

v_layout.addWidget(QLabel('你的爱好是:'))
s1 = QCheckBox('编程')
s2 = QCheckBox('打游戏')
s3 = QCheckBox('追剧')
s4 = QCheckBox('听歌')

v_layout.addWidget(s1)
v_layout.addWidget(s2)
v_layout.addWidget(s3)
v_layout.addWidget(s4)
v_layout.addStretch(1)
self.setLayout(v_layout)

使用复选框QCheckBox库,按顺序创建按钮并添加到复选框里面

状态转变--stateChanged

这次把按钮和槽函数连接的时候需要使用的是状态改变函数

python 复制代码
 v_layout = QVBoxLayout()

    v_layout.addWidget(QLabel('你的爱好是:'))
    s1 = QCheckBox('编程')
    s2 = QCheckBox('打游戏')
    s3 = QCheckBox('追剧')
    s4 = QCheckBox('听歌')

    s1.stateChanged.connect(self.btn_chicked)
    s2.stateChanged.connect(self.btn_chicked)
    s3.stateChanged.connect(self.btn_chicked)
    s4.stateChanged.connect(self.btn_chicked)

    v_layout.addWidget(s1)
    v_layout.addWidget(s2)
    v_layout.addWidget(s3)
    v_layout.addWidget(s4)
    v_layout.addStretch(1)
    self.setLayout(v_layout)

def btn_chicked(self):
    btn = self.sender()
    print(f'你选择了{btn.text()},当前状态是{btn.isChecked()}')# btn.isChecked()是判断是否被勾选,如果被勾选为真否则为假

因为这个选项点击一次是True,再次点击同一个选项就是False,都是点击,但是选项的状态不同,使用isChecked可以看到选项的状态

列表--QListWidget

这个函数需要使用addItem添加内容,之前是addWidget

python 复制代码
f_layout = QFormLayout()

list1 = QListWidget()

list1.addItem('A')
list1.addItem('B')

list1.addItems(['c','d','e'])
f_layout.addRow(QLabel('你的选择是:'),list1)
self.setLayout(f_layout)

首先是实例化了一个表单布局的盒子

然后是实例化一个列表

接着往列表里面添加内容,有两种方式:一次添加一项内容addItem()

一次添加多个内容:addItems(['','','']) 这里的参数就是一个列表

列表选项绑定槽函数

python 复制代码
    f_layout = QFormLayout()

    list1 = QListWidget()
    list1.addItem('A')
    list1.addItem('B')

    list1.addItems(['c','d','e'])
    f_layout.addRow(QLabel('你的选择:'),list1)
    list1.currentItemChanged.connect(self.list_selected)

    self.setLayout(f_layout)
    
def list_selected(self,item):
    print(f'{item.text()}')

这里使用了currentItemchanged作为选项被选中的信号

下拉列表--QComboBox

python 复制代码
f_layout = QFormLayout()
list1 = QComboBox()
list1.addItem('A')
list1.addItem('B')
list1.addItems(['c','d','e'])
f_layout.addRow(QLabel('你的选择:'),list1)
self.setLayout(f_layout)

和列表差不多,这个有索引值,也就是说每个选项按顺序排好有自己的序号

下拉列表的桥--currentIndexChanged

python 复制代码
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(700,600)
        self.move(600,300)
        self.setWindowTitle('pyqt窗口')

        f_layout = QFormLayout()
        list1 = QComboBox()
        list1.addItem('A')
        list1.addItem('B')
        list1.addItems(['c','d','e'])
        list1.currentIndexChanged.connect(self.list_selected)
        f_layout.addRow(QLabel('你的选择:'),list1)
        self.setLayout(f_layout)

    def list_selected(self,idx):
        item = self.sender()
        print(f'你选择了:{item.currentText()},索引值是:{idx}')

这里是self.sender是发出信号的来源,给了item,对item再使用currentText(),就可以获取按钮的文本信息了,这里idx是按钮的索引值

索引值是从0开始的

表格--QTableWidget

类似excel里面的表格

  • 设置表格行数--setRowCount
  • 设置表格列数--setColumnCount
  • 设置表头--setHorizontalHeaderLabels
  • 设置表格数据--setItem

表格字体设置

python 复制代码
from PyQt5.QtGui import QFont
table.setFont(QFont('宋体',12))

列表转表格--QTableWidgetItem

python 复制代码
list_tatle = ['代号','类别','姓名']   # 表头
list1 = [[1122,'aabb','小王'],[2233,'bbcc','小张'],[3344,'adad','小李'],[5566,'cece','小红']]  # 表格数据

v_layout = QVBoxLayout() # 盒子
table = QTableWidget()  # 表格
table.setRowCount(len(list1))  # 设置行数
table.setColumnCount(len(list_tatle))  # 设置列数
table.setFont(QFont('宋体',12))  # 设置字体
table.setHorizontalHeaderLabels(list_tatle)  # 设置标头
# 填充表格数据 # 使用两个for循环依次填入表格
for row in range(len(list1)):
    for col in range(len(list_tatle)):
        data = QTableWidgetItem(list1[row][col])
        table.setItem(row,col,data)

v_layout.addWidget(table)
self.setLayout(v_layout)

我们需要使用QTableWidgetItem从列表获取数据,再使用setItem把二维列表的数据传给表格

表格的桥--itemSelectionChanged

python 复制代码
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDialog, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, \
    QGridLayout, QFormLayout, QLineEdit, QTextEdit,QRadioButton,QCheckBox,QListWidget,QComboBox,QTableWidget,QTableWidgetItem
import sys
from PyQt5.QtCore import Qt  # 对鼠标按键的识别
from PyQt5.QtGui import QFont

list_tatle = ['代号','类别','姓名']
list1 = [['1122','aabb','小王'],['2233','bbcc','小张'],['3344','adad','小李'],['5566','cece','小红']]

# 继承类创建程序
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(700,600)
        self.move(600,300)
        self.setWindowTitle('pyqt窗口')


        v_layout = QVBoxLayout()
        table = QTableWidget()
        table.setRowCount(len(list1))  # 设置行数
        table.setColumnCount(len(list_tatle))  # 设置列数
        table.setFont(QFont('宋体',12))  # 设置字体
        table.setHorizontalHeaderLabels(list_tatle)  # 设置标头
        # 填充表格数据 # 使用两个for循环依次填入表格
        for row in range(len(list1)):
            for col in range(len(list_tatle)):
                data = QTableWidgetItem(list1[row][col])
                table.setItem(row,col,data)
        table.itemSelectionChanged.connect(self.item_select)
        v_layout.addWidget(table)
        self.setLayout(v_layout)

    def item_select(self):
        data = self.sender()
        row = data.currentRow()
        print(list1[row])

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

这里需要把我们自己创建的二维列表放在全局变量当中,不然槽函数不能访问到这个列表

我们使用itemSelectionChanged连接表格和槽函数

槽函数使用self.sender找到信息来源,在使用currentRow获取当前选择的是表格的哪一行

QT图形设计工具--那个外部软件designer

首先一上来就是选择窗口,默认是有5个选择:

从上到下的顺序:按钮在底部,按钮在右侧,没有按钮,大窗口,小窗口(widget)


然后在软件的左侧就是各种控件

在构造界面的时候只需要从左侧把控件移动到窗口中

想要让两个控件对齐,只需要选中他们,鼠标右键选择布局,就可以对齐了

把配套的案件对齐之后,对着界面为空的地方再次单击鼠标右键就可以实现对整体的布局了

对整体进行布局之后,就可以实现对窗体的任意拖动了,窗体内的控件也可以等比例变化了

在对整体完成布局之后,还可以对控件进行微调,比如点击用户名这个标签,可以在属性里面选择font,进而选择粗体,就可以改变字体的属性了

这是实现了对窗口的重命名,首先在对象查看器里面选择窗口,然后在属性里面找到windowTitle,在这一行后面填写你想要的名字

设计完界面之后进行保存,会在pycharm的项目文件夹出现这个ui文件,在使用pyuic这个工具进行格式转换

然后就是调用这个设计的界面了

模板:

python 复制代码
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDialog, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, \
    QGridLayout, QFormLayout, QLineEdit, QTextEdit,QRadioButton,QCheckBox,QListWidget,QComboBox,QTableWidget,QTableWidgetItem
import sys


class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Form()  # 实例化界面类
        self.ui.setupUi(self)  # 实例化界面函数,真正的启动 # 这里的setupUi需要一个对象也就是咱自己创建的类self

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

这个主函数是启动应用的开关,class类是咱自己设计的逻辑函数,里面负责调用界面脚本等等

前两行就是需要的库了,当然是用到什么库写什么库,我这里是把全部的库都写上了,有点臃肿

然后就是ui转py文件了,这里是把py文件整体复制下来,填充到4、5行的位置

最后自己构建一下槽函数,把按钮绑定一下,就完了

业务逻辑

业务逻辑就是验证用户名和密码,这个函数写在自己的类里面,界面py尽量不动

python 复制代码
    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "登录框"))
        self.label.setText(_translate("Form", "用户名"))
        self.label_2.setText(_translate("Form", "密码  "))
        self.pushButton.setText(_translate("Form", "确认"))
        self.pushButton.clicked.connect(Form.get_login)   #  绑定槽函数,注意参数是Form
        self.pushButton_2.setText(_translate("Form", "取消"))
    
   
class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Form()  # 实例化界面类
        self.ui.setupUi(self)  # 实例化界面函数,真正的启动

    def get_login(self):
        print('点击确认')
        user_name = self.ui.lineEdit.text()
        password = self.ui.lineEdit_2.text()
        print(f'用户{user_name},密码{password}')

在逻辑函数中,可以访问界面函数的变量,比如这里就获取了输入框的信息

案例--音乐下载器

设计思路:

  1. 设计界面
  2. 编写爬虫代码
  3. 绑定爬虫
  4. 打包exe文件

这个是最终的设计成果,所有的下载歌曲都在"下载mp3"文件夹里面

完整代码

  • 逻辑代码
python 复制代码
import os.path
import re

import requests
from PyQt5.QtWidgets import QApplication,QWidget,QMessageBox
import sys

from PyQt5 import QtCore, QtGui, QtWidgets
from get_music import get_url



class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(800, 500)
        self.verticalLayout = QtWidgets.QVBoxLayout(Form)
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = QtWidgets.QLabel(Form)
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.lineEdit = QtWidgets.QLineEdit(Form)
        self.lineEdit.setObjectName("lineEdit")
        self.horizontalLayout.addWidget(self.lineEdit)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.listWidget = QtWidgets.QListWidget(Form)
        self.listWidget.setObjectName("listWidget")
        self.verticalLayout.addWidget(self.listWidget)
        self.listWidget.itemDoubleClicked.connect(Form.downloads_music)

        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem)
        self.pushButton = QtWidgets.QPushButton(Form)
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout_2.addWidget(self.pushButton)
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem1)
        self.pushButton_2 = QtWidgets.QPushButton(Form)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout_2.addWidget(self.pushButton_2)
        spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem2)
        self.pushButton_3 = QtWidgets.QPushButton(Form)
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_3.setFont(font)
        self.pushButton_3.setObjectName("pushButton_3")
        self.horizontalLayout_2.addWidget(self.pushButton_3)
        spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem3)
        self.verticalLayout.addLayout(self.horizontalLayout_2)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "音乐下载器"))
        self.label.setText(_translate("Form", "音乐名称:"))
        self.pushButton.setText(_translate("Form", "搜索"))
        self.pushButton.clicked.connect(Form.btn_search)
        self.pushButton_2.setText(_translate("Form", "更多"))
        self.pushButton_2.clicked.connect(Form.btn_more)
        self.pushButton_3.setText(_translate("Form", "清空"))
        self.pushButton_3.clicked.connect(Form.btn_clear)


class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        self.page1=1

    def btn_search(self):
        # print('点击搜索')
        self.page1 = 1
        pr_input = self.ui.lineEdit.text()
        linlks = get_url(pr_input,1)
        for linlk in linlks:

            self.ui.listWidget.addItem(f'歌名:{linlk[0]},歌手:{linlk[1]},id={linlk[2]},下载链接:{linlk[3]}')
            # print(f'歌名:{linlk[0]},歌手:{linlk[1]}id={linlk[2]}')

    def btn_more(self):
        pr_input = self.ui.lineEdit.text()
        self.page1 += 1

        linlks = get_url(pr_input, self.page1)
        for linlk in linlks:
            self.ui.listWidget.addItem(f'歌名:{linlk[0]},歌手:{linlk[1]},id={linlk[2]},下载链接:{linlk[3]}')

    def btn_clear(self):
        self.ui.lineEdit.clear()
        self.ui.listWidget.clear()
        self.page1 = 1

    def downloads_music(self,data):  # 这里第二个参数就把双击对象的文本内容传过来了
        # print(data.text())
        data = data.text()
        author = re.findall(fr'歌名:(.*?),',data)[0]
        song = re.findall(fr'歌手:(.*?),',data)[0]
        id = re.findall(fr'id=(.*?),',data)[0]
        download_urls = re.findall('下载链接:(.*)',data)[0]
        # print(author)
        # print(song)
        # print(id)
        # print(download_urls)
        # QMessageBox.information(self, '下载提示!', f'是否下载{song}-{author}?', QMessageBox.Yes | QMessageBox.No,QMessageBox.Yes)  # 这里是下载前的提示,我觉得没用,就隐藏了
        code = requests.get(download_urls)
        url_text = code.text
        if 'ID3'in url_text:  # 因为mp3文件的开头都是ID3
            music = requests.get(download_urls).content
            if not os.path.exists('下载mp3'):
                os.mkdir('下载mp3')
            # print(f'下载mp3/{song}-{author}-{id}.mp3')
            with open(fr'下载mp3/{song}-{author}-{id}.mp3', 'wb') as f:
                f.write(music)
                QMessageBox.warning(self, '下载提示!', '下载成功')
        else:
            QMessageBox.warning(self,'下载提示!',f'下载失败!下载地址是:{download_urls}')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())
  • 爬虫代码
python 复制代码
import pprint
import re
import requests

def get_url(input,pages=1):
    url = 'https://music.txqq.pro/'
    data = {
            'input': input,
            'filter': 'name',
            'type': 'netease',
            'page': pages
    }
    header = {'x-requested-with':'XMLHttpRequest'}
    rt = requests.post(url=url,headers=header,data=data)
    rt.encoding = rt.apparent_encoding
    links = rt.json()
    # print(links)
    re_code = links['code']
    print(re_code)
    mp3_links = links['data']
    track_links = []
    for link0 in mp3_links:
        downloed_url = link0['url']
        title = link0['title']
        author = link0['author']
        id = re.findall('id=(.*?).mp3',downloed_url)[0]
        # print(title,author,id,downloed_url)
        track_links.append([title,author,id,downloed_url])
    return track_links
  • 界面py
python 复制代码
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file '音乐下载器.ui'
#
# Created by: PyQt5 UI code generator 5.15.11
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(400, 300)
        self.verticalLayout = QtWidgets.QVBoxLayout(Form)
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = QtWidgets.QLabel(Form)
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.lineEdit = QtWidgets.QLineEdit(Form)
        self.lineEdit.setObjectName("lineEdit")
        self.horizontalLayout.addWidget(self.lineEdit)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.listView = QtWidgets.QListView(Form)
        self.listView.setObjectName("listView")
        self.verticalLayout.addWidget(self.listView)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem)
        self.pushButton = QtWidgets.QPushButton(Form)
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout_2.addWidget(self.pushButton)
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem1)
        self.pushButton_2 = QtWidgets.QPushButton(Form)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout_2.addWidget(self.pushButton_2)
        spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem2)
        self.pushButton_3 = QtWidgets.QPushButton(Form)
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_3.setFont(font)
        self.pushButton_3.setObjectName("pushButton_3")
        self.horizontalLayout_2.addWidget(self.pushButton_3)
        spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem3)
        self.verticalLayout.addLayout(self.horizontalLayout_2)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "音乐下载器"))
        self.label.setText(_translate("Form", "音乐名称:"))
        self.pushButton.setText(_translate("Form", "搜索"))
        self.pushButton_2.setText(_translate("Form", "更多"))
        self.pushButton_3.setText(_translate("Form", "清空"))

设计音乐下载器界面

首先要整一个标签,提示用户要输入的信息

然后再整一个输入框,让用户输入信息,我们获取用户输入的信息

然后就是一个list view这个文本框显示我们根据用户输入的信息得到的内容

接着就是三个按钮:搜索,下一页,清空

这是比较基本的需求,具体的细节可以自己脑补

爬虫代码

设计思路:

  • 设计函数:给一个歌名,返回一个含歌曲详细信息的列表
  • 找网站

首先是找个音乐网站,这个网站不要太牛逼,不然光反爬就够你喝一壶了

最好是找个小网站,也不要太卡,功能不用太复杂,支持搜索音乐,下载音乐就行

像图片这种格局的网站就好了

这里使用F12查看咱们下载的音乐在哪里,一般都是在数据包里面,也就是异步获取,需要使用post请求

这里的url和data不要弄错,最重要的是请求头headers

因为服务器会从请求头来判断给你返回什么内容,如果没有请求头,就返回源代码,而音乐的下载链接等详细信息都在数据包里面,我们就得不到想要的结果

所以,先去原网站,打开开发者工具栏,找下载链接,先找源代码(一般源代码都没有)

然后刷新网页,查看数据包,你会找到一个只有data的数据包,里面就是一系列的音乐下载链接

可能会遇到的问题

  • 你一点击F12网页自动关闭
    • 解决方法:换一个网站
  • 网站上能找到数据包,但是pycharm中无论如何都得不到数据包,只有源代码
    • 解决方法:把网站的数据包的所有请求头全部复制,制成字典,得到数据包之后,再一个个删除键值对
  • 数据包里面不是mp3结尾的下载链接
    • 解决方法:换网站,这个可能是api,反正我不会
  • 没有找到数据包
    • 解决方法:换网站,可能是反爬,问题比较复杂

具体问题具体分析,有其他问题评论区见

处理数据包

也就是拿到网站返回的数据包后,对数据包进行拆解

这个数据包一般是json格式,所以用json进行解析,不要用text了

python 复制代码
import pprint
import re
import requests

def get_url(input,pages=1):
    url = 'https://music.txqq.pro/'
    data = {
            'input': input,
            'filter': 'name',
            'type': 'netease',
            'page': pages
    }
    header = {'x-requested-with':'XMLHttpRequest'}
    rt = requests.post(url=url,headers=header,data=data)
    rt.encoding = rt.apparent_encoding
    links = rt.json()
    # print(links)
    re_code = links['code']
    print(re_code)
    mp3_links = links['data']
    track_links = []
    for link0 in mp3_links:
        downloed_url = link0['url']
        title = link0['title']
        author = link0['author']
        id = re.findall('id=(.*?).mp3',downloed_url)[0]
        # print(title,author,id,downloed_url)
        track_links.append([title,author,id,downloed_url])
    return track_links

这个数据包一般返回一个字典,第一个数据是code也就是状态码,咱们一般用不到,当然为了使代码更加健壮,可以获取一下这个状态码

第二个数据就是各种歌曲的信息了,包括歌曲的下载链接,歌名,歌手,作词,作曲等等,很明显也是一个列表,所以我使用了for循环,依次拿数据来解析

列表里面的每一项都是字典,所以我用取值的方式得到了对应的信息,也就是23~25行

因为数据包里面有一套歌曲信息,一个个返回比较麻烦,就以列表的形式进行返回了

绑定爬虫

在上一步,我们拿到了歌曲数据包,也就是返回值的列表

函数的使命完成了

现在就是设计槽函数和按钮连接了

python 复制代码
def btn_search(self):
    # print('点击搜索')
    self.page1 = 1
    pr_input = self.ui.lineEdit.text()
    linlks = get_url(pr_input,1)
    for linlk in linlks:

        self.ui.listWidget.addItem(f'歌名:{linlk[0]},歌手:{linlk[1]},id={linlk[2]},下载链接:{linlk[3]}')
        # print(f'歌名:{linlk[0]},歌手:{linlk[1]}id={linlk[2]}')

这个槽函数对应的就是搜索按钮,我们想要搜索,就要知道用户想要搜索什么,所以使用lineEdit.text()得到输入框的内容

然后就是把得到的歌名给爬虫函数

然后把得到的信息放在listWidget里面,使用addItem一行行的把内容填充进去

这里一般为了整洁会把下载链接隐藏,那样就会导致一个问题,下面你想要下载的时候,还要去找这个链接,老师的方法是把所有的链接和其他信息储存起来,放在一个全局列表里面,在以后想要下载的时候再去找

我感觉很麻烦,确实是干净了,但是代码的复杂程度就上去了,还浪费了一定的内存空间,去列表找链接也浪费时间

所以我把链接也一同放在listWidget里面了

第二个槽函数--btn_more

python 复制代码
def btn_more(self):
    pr_input = self.ui.lineEdit.text()
    self.page1 += 1

    linlks = get_url(pr_input, self.page1)
    for linlk in linlks:
        self.ui.listWidget.addItem(f'歌名:{linlk[0]},歌手:{linlk[1]},id={linlk[2]},下载链接:{linlk[3]}')

这个槽函数对应的是"更多"按钮,也就是我们想要在同一歌名下得到更多的信息

因为数据包也有次序,在网站上直接搜索歌曲得到的是页数为1的数据包,再点击下一页又会刷新新的数据包,得到页数为2的数据包等等

所以涉及一个页数的信息,在init初始化里面定义一个变量page1,这个变量是专门来定位数据包的

第三个槽函数--btn_clear

python 复制代码
def btn_clear(self):
    self.ui.lineEdit.clear()
    self.ui.listWidget.clear()
    self.page1 = 1

这个槽函数是最简单的,负责清理面板,也就是点击''清理''的时候把lineEdit输入框和listWidget展示框清空,有自带的clear函数

第四个槽函数--downloads_music

python 复制代码
def downloads_music(self,data):  # 这里第二个参数就把双击对象的文本内容传过来了
    # print(data.text())
    data = data.text()
    author = re.findall(fr'歌名:(.*?),',data)[0]
    song = re.findall(fr'歌手:(.*?),',data)[0]
    id = re.findall(fr'id=(.*?),',data)[0]
    download_urls = re.findall('下载链接:(.*)',data)[0]
    # print(author)
    # print(song)
    # print(id)
    # print(download_urls)
    # QMessageBox.information(self, '下载提示!', f'是否下载{song}-{author}?', QMessageBox.Yes | QMessageBox.No,QMessageBox.Yes)  # 这里是下载前的提示,我觉得没用,就隐藏了
    code = requests.get(download_urls)
    url_text = code.text
    if 'ID3'in url_text:  # 因为mp3文件的开头都是ID3
        music = requests.get(download_urls).content
        if not os.path.exists('下载mp3'):
            os.mkdir('下载mp3')
        # print(f'下载mp3/{song}-{author}-{id}.mp3')
        with open(fr'下载mp3/{song}-{author}-{id}.mp3', 'wb') as f:
            f.write(music)
            QMessageBox.warning(self, '下载提示!', '下载成功')
    else:
        QMessageBox.warning(self,'下载提示!',f'下载失败!下载地址是:{download_urls}')

这个槽函数负责处理咱们想要下载的歌曲,也就是在展示框里面看到想要下载的歌曲,双击一下,直接下载

这里需要一个参数,也就是想要下载的歌曲信息,也就是列表的那一行信息,我们只需要在参数里面加个data就可以得到双击的那一行信息

因为前面我把下载的链接写在了信息里面,所以可以直接下载链接,这里我拿了一下歌曲的名字和歌手来创建mp3文件,因为涉及一个重名的问题,我发现重名歌曲的id不同,所以我还取了一下id来作为文件名,经过这个组合就避免的重名的问题

遇到的问题

歌曲的下载链接无效,因为涉及版权等等问题,很多下载链接都是不能使用的,但是这些链接都能正常访问

也就是说,失效的地址和正常的地址都能正常访问

失效的地址打开是一个网站,正常的地址打开就是mp3文件

*这里还要再次判断一下链接是否失效,发现mp3文件的开头是''ID3'',而失效的地址一般没有

所以使用if判断''ID3"是否在网页源代码里面

为了让下载的歌曲比较集中,我把所有的歌曲放在了文件夹里面,名称就是下载mp3

打包exe

最后就是把写好的py程序打包,毕竟费那么大精力,不就是让不会python的人直接使用吗

要是仅仅下载歌曲,不搞界面程序,一个爬虫代码就结束了

这里使用pycharm里面的工具,鼠标右键"打开于(open in)"----找到''终端''

使用pyinstaller

输入指令-F -i 图标.ico 主函数main.py --noconsole(意思是不要终端那个黑窗口)

这里就是之前的文章写的打包命令,在爬虫基础1里面的末尾

-F 生成exe文件

-i 设置图标

相关推荐
夜夜敲码8 分钟前
C语言教程(十六): C 语言字符串详解
c语言·开发语言
爱吃泡芙的小白白13 分钟前
爬虫学习——使用HTTP服务代理、redis使用、通过Scrapy实现分布式爬取
redis·分布式·爬虫·http代理·学习记录
宋康14 分钟前
C语言结构体和union内存对齐
c语言·开发语言
逢生博客19 分钟前
使用 Python 项目管理工具 uv 快速创建 MCP 服务(Cherry Studio、Trae 添加 MCP 服务)
python·sqlite·uv·deepseek·trae·cherry studio·mcp服务
꧁坚持很酷꧂20 分钟前
Linux Ubuntu18.04下安装Qt Craeator 5.12.9(图文详解)
linux·运维·qt
居然是阿宋24 分钟前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin
堕落似梦26 分钟前
Pydantic增强SQLALchemy序列化(FastAPI直接输出SQLALchemy查询集)
python
ChoSeitaku34 分钟前
17.QT-Qt窗口-工具栏|状态栏|浮动窗口|设置停靠位置|设置浮动属性|设置移动属性|拉伸系数|添加控件(C++)
c++·qt·命令模式
Cao1234567893211 小时前
简易学生成绩管理系统(C语言)
c语言·开发语言
The Future is mine1 小时前
C# new Bitmap(32043, 32043, PixelFormat.Format32bppArgb)报错:参数无效,如何将图像分块化处理?
开发语言·c#