掌握 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 设置图标

相关推荐
IT书架6 分钟前
golang面试题
开发语言·后端·golang
初遇你时动了情23 分钟前
uniapp 城市选择插件
开发语言·javascript·uni-app
编程修仙38 分钟前
Collections工具类
linux·windows·python
芝麻团坚果1 小时前
对subprocess启动的子进程使用VSCode python debugger
linux·ide·python·subprocess·vscode debugger
EterNity_TiMe_1 小时前
【论文复现】神经网络的公式推导与代码实现
人工智能·python·深度学习·神经网络·数据分析·特征分析
Stara05111 小时前
Git推送+拉去+uwsgi+Nginx服务器部署项目
git·python·mysql·nginx·gitee·github·uwsgi
zongzi_4941 小时前
二次封装的天气时间日历选择组件
开发语言·javascript·ecmascript
kikyo哎哟喂1 小时前
Java 代理模式详解
java·开发语言·代理模式
duration~2 小时前
SpringAOP模拟实现
java·开发语言
一条晒干的咸魚2 小时前
【Web前端】实现基于 Promise 的 API:alarm API
开发语言·前端·javascript·api·promise