0.PySide2、PyQt5 简介

PySide2、PyQt5 都是基于著名的 Qt 库。

Qt库里面有非常强大的图形界面开发库,但是Qt库是C++语言开发的,PySide2、PyQt5可以让我们通过Python语言使用Qt。

但是 PySide2、PyQt5 这两者有什么区别呢?

可以形象地这样说: PySide2 是Qt的 亲儿子 , PyQt5 是Qt还没有亲儿子之前的收的 义子 (Riverbank Computing这个公司开发的)。

那为什么 PyQt5 这个义子 反而比 PySide2 这个亲儿子更出名呢?

原因很简单:PySide2 这亲儿子最近(2018年7月)才出生。

但是亲儿子毕竟是亲儿子,Qt准备大力培养,PySide2 或许更有前途。

已经在使用 PyQt5 的朋友不要皱眉, 两个库的使用 对程序员来说,差别很小:它们的调用接口几乎一模一样。

如果你的程序是PyQt5开发的,通常只要略作修改,比如把导入的名字从 PyQt5 换成 PySide2 就行了。反之亦然。


1.安装 PySide2

很简单,直接pip安装即可:

pip install pyside2 -i https://pypi.douban.com/simple/

也可以安装PyQt5:

pip install pyqt5-tools -i https://pypi.douban.com/simple/

注意事项:

建议使用Python3.6

2.第一个案例

用Qt实现一个简单的案例:

'''导入依赖'''
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton, QPlainTextEdit, QMessageBox

# QApplication       App主对象
# QMainWindow        主窗口
# QPushButton        按钮
# QPlainTextEdit     文本输入框
# QMessageBox        消息盒子

'''初始化App'''
app = QApplication([])

'''声明用于处理数据的函数'''

text = '''
薛蟠     4560 25
薛蝌     4460 25
薛宝钗   35776 23
薛宝琴   14346 18
王夫人   43360 45
王熙凤   24460 25
王子腾   55660 45
王仁     15034 65
尤二姐   5324 24
贾芹     5663 25
贾兰     13443 35
贾芸     4522 25
尤三姐   5905 22
贾珍     54603 35
'''


def handleCalc():
    info = textEdit.toPlainText()

    # 薪资20000 以上 和 以下 的人员名单
    salary_above_20k = ''
    salary_below_20k = ''
    for line in info.splitlines():
        if not line.strip():
            continue
        parts = line.split(' ')
        # 去掉列表中的空字符串内容
        parts = [p for p in parts if p]
        name, salary, age = parts
        if int(salary) >= 20000:
            salary_above_20k += name + '\n'
        else:
            salary_below_20k += name + '\n'

    QMessageBox.about(window,
                      '统计结果',
                      f'''薪资20000 以上的有:\n{salary_above_20k}
                \n薪资20000 以下的有:\n{salary_below_20k}'''
                      )


'''#创建控件#'''

'''创建主窗口'''
window = QMainWindow()
window.resize(500, 400)  # 指定窗口大小
window.move(300, 310)  # 窗口显示位置,基于桌面
window.setWindowTitle('薪资统计')  # 标题

'''创建文本输入框'''
textEdit = QPlainTextEdit(window)  # 显示在window窗口上
textEdit.setPlaceholderText("请输入薪资表")  # 默认显示文本
textEdit.move(10, 25)  # 初始位置
textEdit.resize(300, 350)  # 窗口大小
textEdit.insertPlainText(text)  # 插入文字

'''创建按钮'''
button = QPushButton('统计', window)
button.move(380, 80)  # 初始位置
button.clicked.connect(handleCalc)  # 按钮绑定函数
'''显示主窗口'''
window.show()

app.exec_()

使用面向对象封装:

'''导入依赖'''
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton, QPlainTextEdit, QMessageBox

# QApplication       App主对象
# QMainWindow        主窗口
# QPushButton        按钮
# QPlainTextEdit     文本输入框
# QMessageBox        消息盒子

'''初始化App'''
app = QApplication([])


# 将窗口封装到类
class Appss():
    def __init__(self):
        '''#创建控件#'''

        text = '''
薛蟠     4560 25
薛蝌     4460 25
薛宝钗   35776 23
薛宝琴   14346 18
王夫人   43360 45
王熙凤   24460 25
王子腾   55660 45
王仁     15034 65
尤二姐   5324 24
贾芹     5663 25
贾兰     13443 35
贾芸     4522 25
尤三姐   5905 22
贾珍     54603 35
            '''

        '''创建主窗口'''
        self.window = QMainWindow()
        self.window.resize(500, 400)  # 指定窗口大小
        self.window.move(300, 310)  # 窗口显示位置,基于桌面
        self.window.setWindowTitle('薪资统计')  # 标题

        '''创建文本输入框'''
        self.textEdit = QPlainTextEdit(self.window)  # 显示在window窗口上
        self.textEdit.setPlaceholderText("请输入薪资表")  # 默认显示文本
        self.textEdit.move(10, 25)  # 初始位置
        self.textEdit.resize(300, 350)  # 窗口大小
        self.textEdit.insertPlainText(text)  # 插入文字

        '''创建按钮'''
        self.button = QPushButton('统计', self.window)
        self.button.move(380, 80)  # 初始位置
        self.button.clicked.connect(self.handleCalc)  # 按钮绑定函数
        '''显示主窗口'''
        self.window.show()

    '''声明用于处理数据的函数'''

    def handleCalc(self):
        info = self.textEdit.toPlainText()  # 获取文本框内容

        # 薪资20000 以上 和 以下 的人员名单
        salary_above_20k = ''
        salary_below_20k = ''
        for line in info.splitlines():
            if not line.strip():
                continue
            parts = line.split(' ')
            # 去掉列表中的空字符串内容
            parts = [p for p in parts if p]
            name, salary, age = parts
            if int(salary) >= 20000:
                salary_above_20k += name + '\n'
            else:
                salary_below_20k += name + '\n'

        QMessageBox.about(self.window,
                          '统计结果',
                          f'''薪资20000 以上的有:\n{salary_above_20k}
                    \n薪资20000 以下的有:\n{salary_below_20k}'''
                          )


a = Appss()  # 创建窗口并显示
app.exec_()

3.使用Qt设计师绘制UI

#####Qt设计师路径:

虚拟环境\\venv\Lib\site-packages\PySide2\designer.exe


将Qt设计师绘制的ui保存为.ui 文件

用Python处理.ui文件

有了界面定义文件,我们的Python程序就可以从文件中加载UI定义,并且动态 创建一个相应的窗口对象。

如下:

from PySide2.QtWidgets import QApplication, QMessageBox
from PySide2.QtUiTools import QUiLoader

class Stats:

    def __init__(self):
        # 从文件中加载UI定义
        # 从 UI 定义中动态 创建一个相应的窗口对象
        # 注意:里面的控件对象也成为窗口对象的属性了
        # 比如 self.ui.button , self.ui.textEdit
        self.ui = QUiLoader().load('main.ui')
        self.ui.button.clicked.connect(self.handleCalc)

    def handleCalc(self):
        info = self.ui.textEdit.toPlainText()

        salary_above_20k = ''
        salary_below_20k = ''
        for line in info.splitlines():
            if not line.strip():
                continue
            parts = line.split(' ')

            parts = [p for p in parts if p]
            name,salary,age = parts
            if int(salary) >= 20000:
                salary_above_20k += name + '\n'
            else:
                salary_below_20k += name + '\n'

        QMessageBox.about(self.ui,
                    '统计结果',
                    f'''薪资20000 以上的有:\n{salary_above_20k}
                    \n薪资20000 以下的有:\n{salary_below_20k}'''
                    )

app = QApplication([])
stats = Stats()
stats.ui.show()
app.exec_()
转化UI文件为Python代码:

还有一种使用UI文件的方式:
先把UI文件直接转化为包含界面定义的Python代码文件,然后在你的程序中使用定义界面的类

pyside2-uic main.ui > ui_main.py

如果你安装的是PyQt5,执行如下格式的命令转化:

pyuic5 main.ui > ui_main.py

然后在你的代码文件中这样使用定义界面的类

from PySide2.QtWidgets import QApplication,QMainWindow
from ui_main import Ui_MainWindow

# 注意 这里选择的父类 要和你UI文件窗体一样的类型
# 主窗口是 QMainWindow, 表单是 QWidget, 对话框是 QDialog
class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        # 使用ui文件导入定义界面类
        self.ui = Ui_MainWindow()
        # 初始化界面
        self.ui.setupUi(self)

        # 使用界面定义的控件,也是从ui里面访问
        self.ui.webview.load('http://www.baidu.com')

app = QApplication([])
mainw = MainWindow()
mainw.show()
app.exec_()

那么我们该使用哪种方式比较好呢?动态加载还是转化为Python代码?
建议:通常采用动态加载比较方便,因为改动界面后,不需要转化,直接运行,特别方便。
但是,如果 你的程序里面有非qt designer提供的控件, 这时候,需要在代码里面加上一些额外的声明,而且 可能还会有奇怪的问题。往往就 要采用 转化Python代码的方法。

界面布局 Layout

我们前面写的界面程序有个问题,如果你用鼠标拖拽主窗口边框右下角,进行缩放,就会发现里面的控件一直保持原有大小不变。这样会很难看。
我们通常希望,随着主窗口的缩放, 界面里面的控件、控件之间的距离也相应的进行缩放。
Qt是通过界面布局Layout类来实现这种功能的。

我们最常用的 Layout布局 有4种,分别是:

  • QHBoxLayout 水平布局
  • QVBoxLayout 垂直布局
  • QGridLayout 表格布局
  • QFormLayout 表单布局

    如果我们选择的主窗口是MainWindow类型,要给MainWindow整体设定Layout,必须 先添加一个控件到 centralwidget 下面 ,如下:

    然后才能右键点击 MainWindow,选择布局,如下:

常用控件操作

按钮

QPushButton 就是常见的按钮

#当按钮被点击就会发出 clicked 信号,可以这样指定处理该信号的函数
button.clicked.connect(handleCalc)

#代码中可以使用 setText 方法来改变按钮文本,比如
button.setText(text)

#所有控件(继承自QWidget类)都支持 禁用和启用方法。
#禁用后,该控件不再处理用户操作

#禁用
button.setEnabled(False)
#启用
button.setEnabled(True)

#给按钮设置图标
from PySide2.QtCore import Qt,QSize
from PySide2.QtGui import QIcon

# 设置图标
button.setIcon(QIcon('logo.png'))

# 设置图标大小
button.setIconSize(QSize(30, 30))
输入框

QLineEdit 是只能单行编辑的文本框。

#当文本框中的内容被键盘编辑,被点击就会发出 textChanged 信号,可以这样指定处理该信号的函数
edit.textChanged.connect(handleTextChange)

#用户在文本框中任何时候按下回车键,就会发出 returnPressed 信号。

#有时我们需要处理这种情况,比如登录界面,用户输完密码直接按回车键就进行登录处理,比再用鼠标点击登录按钮快捷的多。
#可以指定处理 returnPressed 信号,如下所示
passwordEdit.returnPressed.connect(onLogin)

#通过 text 方法获取编辑框内的文本内容,比如
text = edit.text()


#通过 setPlaceholderText 方法可以设置提示文本内容,比如
edit.setPlaceholderText('请在这里输入URL')

#通过 setText 方法设置编辑框内的文本内容为参数里面的文本字符串,比如
edit.setText('你好,Python')

#clear 方法可以清除编辑框内所有的文本内容,比如
edit.clear()

#copy 方法可以拷贝当前选中文本到剪贴板,比如
edit.copy()

#paste 方法可以把剪贴板内容,拷贝到编辑框当前光标所在处,比如
edit.paste()

QPlainTextEdit 是可以 多行 的纯文本编辑框。

#信号:文本被修改
#当文本框中的内容被键盘编辑,被点击就会发出 textChanged 信号,可以这样指定处理该信号的函数
edit.textChanged.connect(handleTextChange)
#注意: Qt在调用这个信号处理函数时,不会传入文本框目前的内容字符串,作为参数。
#这个行为 和 单行文本框不同。

#信号:光标位置改变
#当文本框中的光标位置变动,就会发出 cursorPositionChanged 信号,可以这样指定处理该信号的函数
edit.cursorPositionChanged.connect(handleChanged)

#获取文本
#通过 toPlainText 方法获取编辑框内的文本内容,比如

text = edit.toPlainText()
#获取选中文本
# 获取 QTextCursor 对象
textCursor = edit.textCursor()
selection = textCursor.selectedText()

#设置提示
#通过 setPlaceholderText 方法可以设置提示文本内容,比如
edit.setPlaceholderText('请在这里输入薪资表')

#设置文本
#通过 setPlainText 方法设置编辑框内的文本内容 为参数里面的文本字符串,比如
edit.setPlainText('''你好,白月黑羽 hello byhy''')
#原来的所有内容会被清除

#在末尾添加文本
#通过 appendPlainText 方法在编辑框末尾添加文本内容,比如
edit.appendPlainText('你好,白月黑羽')
#注意:这种方法会在添加文本后 自动换行

#在光标处插入文本
#通过 insertPlainText 方法在编辑框末尾添加文本内容,比如
#edit.insertPlainText('你好,白月黑羽')
#注意:这种方法 不会 在添加文本后自动换行

#清除所有文本
#clear 方法可以清除编辑框内所有的文本内容,比如
#edit.clear()

#拷贝文本到剪贴板
#copy 方法可以拷贝当前选中文本到剪贴板,比如
#edit.copy()

#粘贴剪贴板文本
#paste 方法可以把剪贴板内容,拷贝到编辑框当前光标所在处,比如
#edit.paste()

#设置最大行数
#有时候,代码会不断往文本框添加内容,为了防止占用过多资源,可以设置文本框最大行数。
#像这样:
#edit.document().setMaximumBlockCount(1000)
#就设置最大为 1000行

QTextBrowser 是 只能查看文本 控件。

#通过 append 方法在编辑框末尾添加文本内容,比如
textBrowser.append('你好,白月黑羽')

#有时,浏览框里面的内容长度超出了可见范围,我们在末尾添加了内容,往往希望控件自动翻滚到当前添加的这行,
#可以通过 ensureCursorVisible 方法来实现
textBrowser.append('你好,白月黑羽')
textBrowser.ensureCursorVisible()
#注意:这种方法会在添加文本后 自动换行

#在光标处插入文本
#通过 insertPlainText 方法在编辑框末尾添加文本内容,比如
edit.insertPlainText('你好,白月黑羽')
#注意:这种方法 不会 在添加文本后自动换行

QLabel 就是常见的标签,可以用来显示文字(包括纯文本和富文本)、图片 甚至动画。

#代码中可以使用 setText 方法来改变标签文本内容,比如
button.setText(text)

QComboBox 是组合选择框,如下图所示

#如果用户操作修改了QComboBox中的选项就会发出 currentIndexChanged 信号,可以这样指定处理该信号的函数:
cbox.currentIndexChanged.connect(handleSelectionChange)

#代码中可以使用 addItem 方法来添加一个选项到 末尾 ,参数就是选项文本
cbox.addItem('byhy')

#代码中可以使用 addItems 方法来添加多个选项到 末尾,参数是包含了多个选项文本的列表
cbox.addItems(['byhy','白月黑羽','python教程'])

#代码中可以使用 clear 方法来清空选项,也就是删除选择框内所有的选项\
cbox.clear()

#代码中可以使用 currentText 方法来获取当前 选中的选项 的文本,比如
method = cbox.currentText()


QListWidget 是列表控件,如下图所示

Qt Designer 如下图 选择:

#代码中可以使用 addItem 方法来添加一个选项到 末尾 ,参数就是选项文本
listWidget.addItem('byhy')

#代码中可以使用 addItems 方法来添加多个选项到 末尾,参数是包含了多个选项文本的列表
listWidget.addItems(['byhy','白月黑羽','python教程'])

#代码中可以使用 takeItem 方法来删除1个选项,参数是该选项所在行
listWidget.takeItem(1)

#代码中可以使用 clear 方法来清空选项,也就是删除选择框内所有的选项
listWidget.clear()

#currentItem 方法可以得到列表当前选中项对象(QListWidgetItem) ,再调用这个对象的 text 方法,就可以获取文本内容,比如
listWidget.currentItem().text()

#可以使用 item 方法获取指定某行的 QListWidgetItem,比如:
listWidget.item(0).text()


QTableWidget 是表格控件,如下图所示

表格控件单元格里面可以显示文字,也可以显示富文本、图片等内容。
表格控件的每个单元格里面要显示内容,都必须创建一个 QTableWidgetItem 类型的对象。
Qt Designer 如下图 选择:

我们可以通过 Qt designer 为一个表格创建列和对应的标题栏。
只需要双击 Qt designer 设计的窗体中的 表格控件, 就会出现这样的对话框。

在 列 标签栏中,点击左下角的加号,就可以为 添加一个列,并且设置标题栏名称。

#insertRow 方法可以在指定位置插入一行,比如
#table.insertRow(0)
#就插入一行到第 1 行这个位置, 表格原来第1行(包括原来的第1行)以后的内容,全部往下移动一行。

table.insertRow(2)
#就插入一行到第 3 行这个位置, 表格原来第3行(包括原来的第3行)以后的内容,全部往下移动一行。

#removeRow 方法可以删除指定位置的一行,比如
table.removeRow(0)
#就删除第 1 行, 表格原来第1行以后的内容,全部往上移动一行。

table.removeRow(2)
#就删除第 3 行, 表格原来第3行以后的内容,全部往上移动一行。


#qt表格的单元格内的内容对象 是一个 单元格对象 QTableWidgetItem 实例
#如果单元格 没有被设置过 内容,可以这样
from PySide2.QtWidgets import QTableWidgetItem

item = QTableWidgetItem()
item.setText('白月黑羽')
table.setItem(row, 0, item)

#也可以简写为
from PySide2.QtWidgets import QTableWidgetItem
table.setItem(row, 0, QTableWidgetItem('白月黑羽'))

#如果单元格 已经被设置过 文本内容,
#item 方法可以获取指定位置的 QTableWidgetItem ,再调用这个对象的 setText 方法,就可以设置单元格文本内容,比如
table.item(0,0).setText('白月黑羽-江老师')
#就设置了 第1行,第1列 的单元格里面的文本。

table.item(2,4).setText('白月黑羽-江老师')
#就设置了 第3行,第5列 的单元格里面的文本。

#如果希望某个单元格为 只读,不允许修改,可以使用QTableWidgetItem对象的 setFlags 方法,像这样
from PySide2.QtWidgets import QTableWidgetItem
from PySide2.QtCore import Qt

item = QTableWidgetItem('白月黑羽')
item.setFlags(Qt.ItemIsEnabled) # 参数名字段不允许修改
table.setItem(row, 0, item)

#如果想文本内容 居中对齐,每个当对应的QTableWidgetItem 调用 setTextAlignment,如下
from PySide2.QtWidgets import QTableWidgetItem
from PySide2.QtCore import Qt

item = QTableWidgetItem()
item.setText('白月黑羽')
# 文本居中
item.setTextAlignment(Qt.AlignCenter) 
table.setItem(row, 0, item)

#如果想设置 表格框 和 单元格边线 的颜色,可以使用样式如下

QTableWidget {
	border:1px solid green;
    gridline-color: rgb(71, 191, 255);
}
#item 方法可以指定位置的单元格对象(QTableWidgetItem) ,再调用这个对象的 text 方法,就可以获取文本内容,比如
table.item(0,0).text()
#就获取了 第1行,第1列 的单元格里面的文本。

table.item(2,4).text()
#就获取了 第3行,第5列 的单元格里面的文本。

#获取所有行数、列数
#代码中可以使用 rowCount 方法来获取表格所有的 行数 ,比如
rowcount = table.rowCount()
#可以使用 columnCount 方法来获取表格所有的 列数 ,比如
rowcount = table.columnCount()
#获取当前选中是第几行
#代码中可以使用 currentRow 方法来获取当前选中是第几行,比如
currentrow = table.currentRow()
#注意:行数是从0开始的, 第一行的行数是 0

#设置表格行数、列数
#代码中可以使用 setRowCount 方法来设置表格 行数 ,比如
table.setRowCount(10)

#代码中可以使用 setColumnCount 方法来设置表格 列数 ,比如
table.setColumnCount(10)

#清除/删除所有内容
#clearContents 方法可以清除表格所有的内容,比如
table.clearContents()
#清除后,仍然会留下表格栏


#如果连表格栏都要删除,可以使用 setRowCount(0),像这样
table.setRowCount(0)

#设定列宽、宽度自动缩放
Qt Designer 上目前没法拖拽设定 每个列的宽度,只能在代码中指定。如下所示
# 设定第1列的宽度为 180像素
table.setColumnWidth(0, 180)

# 设定第2列的宽度为 100像素
table.setColumnWidth(1, 100)

单选按钮 和 按钮组

QRadioButton 是单选按钮,如下图所示

image

官网介绍

说明

同一个父窗口 里面的多个单选按钮,只能选中一项。

如果你有多组单选按钮, 每组都应该有不同的父控件,或者不同的Layout。

通常建议:多组单选按钮,放到不同的 按钮组 QButtonGroup

具体内容,点击这里,查看视频讲解

信号:选中状态改变

如果用户操作点击了按钮组 QButtonGroup 中的一个按钮, QButtonGroup 就会发出 buttonClicked 信号,可以这样指定处理该信号的函数

buttongroup.buttonClicked.connect(handleButtonClicked)

然后,在处理函数中调用QButtonGroup对象的 checkedButton() 函数,返回值就是被选中的按钮对象。

再调用这个返回的按钮对象的 text() 方法得到界面文本,就可以知道是哪个选项被选中了。

勾选按钮 和 按钮组

QCheckBox 是勾选按钮,如下图所示

image

官网介绍

说明

通常建议:多组勾选按钮,放到不同的 按钮组 QButtonGroup 中,按钮组就是父控件。

可以在 Qt设计师中设置 QButtonGroup 的 exclusive 属性, 来控制 是否 只能单选一个选项。

信号:选中状态改变

如果用户操作点击了按钮组 QButtonGroup 中的一个按钮, QButtonGroup 就会发出 buttonClicked 信号,可以这样指定处理该信号的函数

buttongroup.buttonClicked.connect(handleButtonClicked)

QButtonGroup 设置为 单选 情况下:

在处理函数中调用QButtonGroup对象的 checkedButton() 函数,返回值就是被选中的按钮对象。

再调用这个返回的按钮对象的 text() 方法得到界面文本,就可以知道是哪个选项被选中了。

QButtonGroup 设置为 多选 情况下:

要得知哪些按钮被选中, 可以 对所有该组中的 按钮调用 isChecked 方法 ,来判断。

tab页控件

我们可以通过tab页控件把界面分为好几个页面,如下所示

image

通过Qt designer 只需要拖拽控件到各个页面即可。

要修改tab页的标题,可以先点击该tab页,然后在下图所示处修改

image

tab页中布局Layout

如果我们要在tab页上布局, 你可能会在对象查看器总直接右键点击该tab,可以你会发现 右键菜单里面没有布局项。

这是 Qt designer 非常坑爹的地方,我当时足足花了一个小时才找到方法。

  1. 首先需要你在tab页上添加一个控件

  2. 然后点击 在对象查看器 右键点击上层 TabWidget ,这时,你就会发现有布局菜单了

点击这里,看视频讲解tab页中布局Layout

进度条

QProgressBar 是进度条,如下图所示

qt0001

官网介绍

说明

进度条也是一个常用的控件,当程序需要做一件比较耗费时间的任务(比如统计数据,下载文件等)时,可以用来向用户指示操作的进度。

而且有了进度显示,用户就知道应用程序仍在运行,并没有出问题。

QProgressBar进度条把每个进度称之为一个step(步骤)。

我们可以通过它的 setRange 方法设定步骤个数,比如

progressBar.setRange(0,5)

就设定了,进度分为5步。

然后,通过 setValue 方法,指定当前完成到了哪一步,比如

progressBar.setValue(3)

就表示完成了 3/5, 也就是 60%, 进度条就会显示60%的进度。

可以使用reset()将进度条倒退到开头。

有时候我们的任务没法知道完成了多少,比如下载一个未知大小的文件。

这时,可以把range 范围都设置为0,这样,进度条会显示忙碌指示符,而不是显示进度百分比。

下面是一个进度条程序的示例代码

from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton,  QProgressBar,QMessageBox
from time import sleep
from threading import  Thread

class Stats():
    def __init__(self):
        self.window = QMainWindow()
        self.window.resize(500, 400)
        self.window.move(300, 300)

        self.progressBar = QProgressBar(self.window)
        self.progressBar.resize(300, 20)
        self.progressBar.move(80, 30)
        # 进度是 0 - 5,
        self.progressBar.setRange(0,5)

        self.button = QPushButton('统计', self.window)
        self.button.move(80, 80)

        self.button.clicked.connect(self.handleCalc)

        # 统计进行中标记,不能同时做两个统计
        self.ongoing = False

    def handleCalc(self):
        def workerThreadFunc():
            self.ongoing = True
            for i in range(1,6):
                sleep(1)
                # 设置进度值
                self.progressBar.setValue(i)
            self.ongoing = False

        if self.ongoing:
            QMessageBox.warning(
                self.window,
                '警告','任务进行中,请等待完成')
            return

        # 通常任务执行比较耗时,应该在新的线程中进行
        # 否则会阻塞主线程显示界面
        worker = Thread(target=workerThreadFunc)
        worker.start()

app = QApplication([])
stats = Stats()
stats.window.show()
app.exec_()

上面的代码,运行时,会有很多告警,因为我们在新线程中操作界面对象,容易出问题。

更合理的方法是通过信号,在线程之间传递信息,对界面的操作都在主线程中完成。

如下

from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton,  QProgressBar,QMessageBox
from time import sleep
from threading import  Thread
from PySide2.QtCore import Signal,QObject

# 信号库
class SignalStore(QObject):
    # 定义一种信号
    progress_update = Signal(int)
    # 还可以定义其他作用的信号

# 实例化
so = SignalStore()

class Stats():
    def __init__(self): 
        # 连接信号到处理的slot函数
        so.progress_update.connect(self.setProgress)
        
        self.window = QMainWindow()
        self.window.resize(500, 400)
        self.window.move(300, 300)

        self.progressBar = QProgressBar(self.window)
        self.progressBar.resize(300, 20)
        self.progressBar.move(80, 30)
        # 进度是 0 - 5,
        self.progressBar.setRange(0,5)

        self.button = QPushButton('统计', self.window)
        self.button.move(80, 80)

        self.button.clicked.connect(self.handleCalc)

        # 统计进行中标记,不能同时做两个统计
        self.ongoing = False

    def handleCalc(self):
        def workerThreadFunc():
            self.ongoing = True
            for i in range(1,6):
                sleep(1)
                # 发出信息,通知主线程进行进度处理
                so.progress_update.emit(i)
            self.ongoing = False

        if self.ongoing:
            QMessageBox.warning(
                self.window,
                '警告','任务进行中,请等待完成')
            return

        worker = Thread(target=workerThreadFunc)
        worker.start()

    # 处理进度的slot函数
    def setProgress(self,value):
        self.progressBar.setValue(value)

app = QApplication([])
stats = Stats()
stats.window.show()
app.exec_()

数字输入框

QSpinBox 是数字输入框,可以输入或使用上下箭头选择数字,如下图所示

qt0001

官网介绍

获取数字

通过 value 方法获取编辑框内的文本内容,比如

number = box.value()

注意:返回的是整数对象,不是字符串

设置数字

通过 setValue 方法可以设置提示文本内容,比如

box.setValue(100)

日期控件

QDateEdit 类可以用来选择日期时间,如下图所示

image

官网介绍

获取日期

当用户点击日期时间控件并且选取了 日期和时间,后来程序要获取这个控件里面选定的日期时间,可以使用date方法获取日期对象。

如下所示

# 返回 PySide2.QtCore.QDate 对象
qdate = dateEdit.date()

# 可以转化为 指定格式的字符串
dateStr = qdate.toString('yyyy-MM-dd')

# 也可以获取年月日 对应的数字 ,比如日期是2020年5月2号
year = qdate.year()   # 返回 2020
month = qdate.month() # 返回 5
day = qdate.day()     # 返回 2

QDate 对象的具体说明参考官方文档

选择文件框

QFileDialog 类可以用来选择文件或者目录,如下图所示

image

官网介绍

选择目录

通过 getExistingDirectory 静态方法 选择目录。

该方法,第一个参数是父窗口对象,第二个参数是选择框显示的标题。

比如

from PySide2.QtWidgets import QFileDialog

filePath = QFileDialog.getExistingDirectory(self.ui, "选择存储路径")

返回值即为选择的路径字符串。

如果用户点击了 选择框的 取消选择按钮,返回 空字符串。

选择单个文件

如果你想弹出文件选择框,选择一个 已经存在 的文件,可以使用 QFileDialog 静态方法 getOpenFileName ,比如

from PySide2.QtWidgets import QFileDialog

filePath, _  = QFileDialog.getOpenFileName(
            self.ui,             # 父窗口对象
            "选择你要上传的图片", # 标题
            r"d:\\data",        # 起始目录
            "图片类型 (*.png *.jpg *.bmp)" # 选择类型过滤项,过滤内容在括号中
        )

该方法返回值 是一个元组,第一个元素是选择的文件路径,第二个元素是文件类型,如果你只想获取文件路径即可,可以采用上面的代码写法。

如果用户点击了 选择框的 取消选择按钮,返回 空字符串。

如果你想弹出文件选择框,选择路径和文件名,来 保存一个文件 ,可以使用 QFileDialog 静态方法 getSaveFileName ,比如

from PySide2.QtWidgets import QFileDialog

filePath, _  = QFileDialog.getSaveFileName(
            self.ui,             # 父窗口对象
            "保存文件", # 标题
            r"d:\\data",        # 起始目录
            "json类型 (*.json)" # 选择类型过滤项,过滤内容在括号中
        )

选择多个文件

如果要选择多个文件,使用 getOpenFileNames 静态方法

from PySide2.QtWidgets import QFileDialog

filePaths, _  = QFileDialog.getOpenFileNames(
            self.ui,             # 父窗口对象
            "选择你要上传的图片", # 标题
            r"d:\\data",        # 起始目录
            "图片类型 (*.png *.jpg *.bmp)" # 选择类型过滤项,过滤内容在括号中
        )

上例中 filePaths 对应的返回值是一个列表,里面包含了选择的文件。

如果用户点击了 选择框的 取消选择按钮,返回 空列表。

树控件

QTreeWidget 树控件 树控件, 是和 QTreeWidgetItem 树节点控件 结合使用的。

如下图所示

image

本节讲解 仅 内部学员 可见

提示框

QMessageBox 类可以用来弹出各种提示框

官网介绍

该类可以通过一系列静态方法,显示 如下弹出框

  • 错误报告

image

使用 critical 方法

QMessageBox.critical(
    self.ui,
    '错误',
    '请选择爬取数据存储路径!')
  • 警告

image

使用 warning 方法

QMessageBox.warning(
    self.ui,
    '阅读太快',
    '阅读客户协议必须超过1分钟')
  • 信息提示

image

使用 information 方法

QMessageBox.information(
    self.ui,
    '操作成功',
    '请继续下一步操作')

也可以使用 about 方法

QMessageBox.about(
    self.ui,
    '操作成功',
    '请继续下一步操作')
  • 确认继续

image

使用 question 方法

choice = QMessageBox.question(
    self.ui,
    '确认',
    '确定要删除本文件吗?')

if choice == QMessageBox.Yes:
    print('你选择了yes')
if choice == QMessageBox.No:
    print('你选择了no')

输入对话框

QInputDialog 输入对话框 只让用户输入一行数据信息,比如 姓名、年龄等。

可以方便的用来获取简单的信息。

image

官网介绍

比如

from PySide2.QtWidgets import QInputDialog,QLineEdit

# 返回值分别是输入数据 和 是否点击了 OK 按钮(True/False)
title, okPressed = QInputDialog.getText(
    self, 
    "输入目录名称",
    "名称:",
    QLineEdit.Normal,
    "")

if not okPressed:
    print('你取消了输入')

常用的方法有:

  • getText

    弹出对话框,让用户输入 单行文本

  • getMultiLineText

    弹出对话框,让用户输入 多行文本

  • getInt

    弹出对话框,让用户输入 整数

  • getItem

    弹出对话框,让用户选择 选项

    items = ["春天", "夏天", "秋天", "冬天"]
    
    item, ok = QInputDialog().getItem(self, 
                                      "请选择",
                                      "季节:", 
                                      items, 
                                      0, 
                                      False)
    if ok and not item.isEmpty():
        itemLabel.setText(item)
    

菜单

可以在 Qt Designer上很方便的添加菜单,如下所示

image

点击菜单的信号是 triggered, 处理点击菜单的的代码如下

self.ui.actionOpenFile.triggered.connect(self.openPageFile)

注意:如果菜单和工具栏有 相同的 action ,通常是先在 动作编辑器 创建一个action, 然后分别拖动到 菜单 和 工具栏

工具栏

在 Qt 设计师上添加工具栏,可以右键点击 Main Window 类型的窗体空白处,如下所示

image

选择添加工具栏

注意,只有 Main Window 类型的窗体,才能添加工具栏,如下

image

添加工具栏后,还要在工具栏上添加图标。

方法是点击右下角 动作编辑器,新建动作,如下图所示

image

然后如下图所示进行设置

image

添加动作成功后,就可以直接拖到工具栏上了。

然后,在代码中定义动作触发后的处理函数,如下所示

self.ui.actionAddNote.triggered.connect(self.actionAddNode)

状态栏

官网介绍

要在状态栏显示文本信息,只需要调用 QStatusBar 的 showMessage 方法

self.ui.statusbar.showMessage(f'打开文件{filePath}')

剪贴板

Qt程序可以获取和设置剪贴板内容

官网介绍

from PySide2.QtGui import QGuiApplication

cb = QGuiApplication.clipboard()
# 获取剪贴板内容
originalText = cb.text()
# 设置剪贴板内容
clipboard.setText(newText)

MDI 多个子窗口

QMdiArea 提供了一个主窗口区,里面可以存放多个 QMdiSubWindow 子窗口

如图:

image

本节讲解 仅 内部学员 可见