PyQt5 高级界面控件
5.1 表格与树
- 表格与树解决的问题是
如何在一个控件中有规律地呈现更多的数据
5.1.1 QTableView 表格结构视图
-
一个应用需要和一批数据(比如数组、列表)进行交互,然后以表格的形式输出这些信息,这时就需要用到 QTableView 视图类来使用数据模型显示内容,通过 setModel 来绑定数据源
-
QTableWidget 继承自 QTableView,主要的区别在于 QTableView 可以使用自定义的数据模型,而 QTableWidget 只能使用标准的数据模型,一般来说标准的模型已经可以满足我们的需求
-
使用 QTableWidget 时,其单元格数据是通过 QTableWidgetItem 对象来设置实现的---不需要自己再去建立数据模型
-
常用的标准数据模型
- QStringListModel 存储一组字符串
- QStandardItemModel 存储任意层次结构的数据模型
- QDirModel 文件目录模型(对文件系统进行封装)
- QSqlTableModel 对 SQL 中的表格进行封装
- QSqlQueryModel 对 SQL 中的查询结果进行封装
- QSqlRelationalTableModel 对带有 foreign key 的 SQL 表进行封装
- QSortFilterProxyModel 对模型中的数据进行排序或者过滤
案例 5-1 QTableView 表格视图类的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class TableViewDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-1 QTableView表格视图类的使用")
self.resize(500, 300)
# 建立要绑定的数据模型
self.model = QStandardItemModel(4, 4)
self.model.setHorizontalHeaderLabels(['标题1', '标题2', '标题3', '标题4'])
for row in range(4):
for column in range(4):
item = QStandardItem("row {}, column {}".format(row, column))
# 设置模型单元格中的条目数据
self.model.setItem(row, column, item)
self.model.appendRow([
self.model.appendRow([
QStandardItem("row {}, column {}".format(11, 11)),
QStandardItem("row {}, column {}".format(11, 11)),
QStandardItem("row {}, column {}".format(11, 11)),
QStandardItem("row {}, column {}".format(11, 11))
])
self.tableView = QTableView()
# 绑定数据模型到表格视图
self.tableView.setModel(self.model)
self.tableView.horizontalHeader().setStretchLastSection(True)
self.tableView.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
layout = QVBoxLayout()
layout.addWidget(self.tableView)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = TableViewDemo()
win.show()
sys.exit(app.exec_())
解析
- 此例中表格填满了窗口,水平方向上到了显示的末尾部分,垂直方向上占满
# 设置tableView的水平头伸展到最后内容的最后一部分
self.tableView.horizontalHeader().setStretchLastSection(True)
# 设置tableView的垂直头部域重构大小模式为,头部视图尽可能伸展
self.tableView.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
- 追加数据
self.model.appendRow([
QStandardItem("row {}, column {}".format(11, 11)),
QStandardItem("row {}, column {}".format(11, 11)),
QStandardItem("row {}, column {}".format(11, 11)),
QStandardItem("row {}, column {}".format(11, 11))
])
- 删除当前选中的数据
# 第一种方法
indexs = self.tableView.selectionModel().selection().indexs()
if len(indexs) > 0:
self.model.removeRows(indexs[0].row(), 1)
# 第二种方法
index = self.tableView.currentIndex()
# 索引对应的那一行数据
print(index.row())
self.model.removeRow(index.row())
注意
- 如果在表格中什么也不选默认就删除第一行,也就是索引为 0 的那一行数据
- 选中一行时就删除那一行
- 选中多行时,就删除焦点所在的那一行数据
5.1.2 QListView 列表视图
- QListView 列表视图类用于展示数据,子类是 QListWidget
- QListView 是基于模型的(Model)的,需要程序来建立模型,然后再保存数据
- QListWidget 是对 QListView 优化升级,本身已经存在一个封装好的数据存储模型(QListWidgetItem),可以直接调用 addItem 函数添加条目
常用的方法 - setModel() 用来设置 View 所关联的 Model,可以使用 Python 原生的 list 作为数据源模型 model
- selectedItem() 获取已选中 Model 中的条目
- isSelected() 判断 Model 中的某个条目是否被选中
常用的信号 - clicked 当点击 Model 中的某个条目 Item 时,发射该信号
- doubleClicked 当双击 Model 中的某项时,发射该信号
案例 5-2 QListView 列表视图的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ListViewDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-2 QListView列表视图的使用")
self.resize(300, 270)
layout =QVBoxLayout()
listView = QListView()
# 建立数据模型 空对象
stringListModel = QStringListModel()
self.strList = ['Item1', 'Item2', 'Item3', 'Item4']
# 填充空对象数据模型
stringListModel.setStringList(self.strList)
# 列表视图绑定数据源模型
listView.setModel(stringListModel)
listView.clicked.connect(self.listViewItemClicked)
layout.addWidget(listView)
self.setLayout(layout)
def listViewItemClicked(self, ListModelIndex):
print(ListModelIndex)
print(ListModelIndex.row())
QMessageBox.information(self, "ListWidget", self.tr("你选择了:" + self.strList[ListModelIndex.row()]))
if __name__ == '__main__':
app = QApplication(sys.argv)
win = ListViewDemo()
win.show()
sys.exit(app.exec_())
解析
- 当用户点击 QListView 控件的 Model 中的某一个条目时会弹出消息对话框,显示选择的是哪一个条目
def listViewItemClicked(self, ListModelIndex):
print(ListModelIndex)
print(ListModelIndex.row())
QMessageBox.information(self, "ListWidget", self.tr("你选择了:" + self.strList[ListModelIndex.row()]))
注意
- ListModelIndex 是 PyQt5.QtCore.QModelIndex 类的实例化对象
- ListModelIndex.row()这个才是真正的列表中的条目对应的索引
5.1.3 QListWidget 列表视图窗口控件
- QListWidget 是一个集成好数据模型的接口,可以直接绑定数据源
- QListWidget 可以设置为多重选择
- QListWidget 中的条目 Item 都是 QListWidgetItem 对象
常用的方法 - addItem() 在列表中添加 QListWidgetItem 对象或者字符串
- addItems() 在列表中添加多个条目,参数是可迭代对象
- insertItem() 在指定的索引处插入条目
- clear() 删除列表中的所有内容
- setCurrentItem()设置当前所选中的条目
- sortItems() 按升序重新排列列表的所有条目
常用的信号 - itemClicked 当点击列表中的某项条目时,发射该信号
- currentItemCahnged 当当前选中条目发生变化时,发射该信号
案例 5-3 QListWidget 列表控件的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ListWidgetDemo(QListWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-3 QListWidget列表控件的使用")
self.resize(300, 120)
self.addItem("Item1")
self.addItem("Item2")
self.addItem("Item3")
self.addItem("Item4")
self.itemClicked.connect(self.clicked)
def clicked(self, item):
QMessageBox.information(self, "ListWidget", self.tr("你选择了:" + item.text()))
if __name__ == '__main__':
app = QApplication(sys.argv)
win = ListWidgetDemo()
win.show()
sys.exit(app.exec_())
5.1.4 QTableWidget 表格控件窗口
-
QTableWidget 是 QTableView 的子类,它自带有标准的数据模型,不需要再去建立,直接使用 QTableWidgetItem 对象来表示表格中的每一个单元格条目 Item
常用的方法 -
setRowCount(int row) 设置 QTableWidget 表格控件的行数
-
setColumnCount(int col) 设置 QTableWidget 的列数
-
setHorizontalHeaderLabels() 设置 QTableWidget 的水平头部标题标签
-
setVerticalHeaderLabels() 设置 QTableWidget 的垂直标题标签
-
setItem(int,int,QTableWidgetItem) 设置每一个单元格的 Item
-
horizontalHeader() 获取 QTableWidget 的水平表头
-
rowCount() 获取 QTableWidget 行数
-
columnCount() 获取 QTableWidget 列数
-
setEditTriggers(EditTriggers triggers) 设置表格是否可编辑,并设置编辑规则,枚举值如下
- QAbstractItemView.NoEditTriggers0No 0
不能对表格内容进行修改操作 - QAbstractItemView.CurrentChanged1Editing 1
任何时候都能对单元格进行编辑修改 - QAbstractItemView.DoubleClicked2Editing 2
可以双击单元格修改 - QAbstractItemView.SelectedClicked4Editing 4
单击已选中的内容 - QAbstractItemView.EditKeyPressed8Editing 8
当修改键按下时对单元格进行修改 - QAbstractItemView.AnyKeyPressed16Editing 16
任意键被按下时对单元格进行修改 - QAbstractItemView.AllEditTriggers31Editing 31
包括以上所有的条件
- QAbstractItemView.NoEditTriggers0No 0
-
setSelectionBehavior() 设置表格的选择行为
- QAbstractItemView.SelectItem0Selecting 0 选中单项
- QAbstractItemView.SelectRows1Selecting 1 选中一行
- QAbstractItemView.SelectColumns2Selecting 2 选中一列
-
setTextAlignment() 设置单元格内的文本对齐方式
- Qt.AlignLeft 左对齐
- Qt.AlignRight 右对齐
- Qt.AlignHCenter 水平居中对齐
- Qt.AlignJustify 从左到右对齐
- Qt.AlignTop 顶部对齐
- Qt.AlignBottom 底部对齐
- Qt.AlignVCenter 垂直居中对齐
- Qt.AlignBaseline 与基线对齐
-
setSpan(int row,int column,int rowSpanCount, int columnSpanCount) 设置单元格的合并形式
-
setShowDrid() 设置网格是否显示,默认是 True 显示
-
setColumnWidth() 设置单元格的宽度
-
setRowHeight() 设置单元格的高度
实操案例 -
基本用法
mport sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class tableWidgetDemo(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("QTableWidget 表格控件窗口的基本用法")
self.resize(400, 300)
layout = QHBoxLayout()
tableWidget = QTableWidget()
tableWidget.setRowCount(4)
tableWidget.setColumnCount(3)
tableWidget.setHorizontalHeaderLabels(['姓名', '性别', '体重(Kg)'])
newItem = QTableWidgetItem("张三")
tableWidget.setItem(0, 0, newItem)
newItem = QTableWidgetItem("男")
tableWidget.setItem(0, 1, newItem)
newItem = QTableWidgetItem("160")
tableWidget.setItem(0, 2, newItem)
layout.addWidget(tableWidget)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = tableWidgetDemo()
win.show()
sys.exit(app.exec_())
解析
- QTableWidget 与 QTableView 的实现等价代码
# QTableWidget
tableWidget = QTableWidget(4,3)
# QTableView
self.model = QStandardItemModel(4, 3)
self.tableView = QTableView()
self.tableView.setModel(self.model)
- QTableWidget 本身就就有数据模型,条目对象就是 QTableWidgetItem,而 QTableView 需要程序本身创建数据源模型,条目对象是 QStandardItem,并且需要使用 setModel 函数来绑定数据模型
- 默认时,表格中的字符串是可以修改的
- 表头标签的设置必须要在整个表格初始化完成后,不然是没有效果的,即就是要先初始化行号和列号(row/column)
- 通过使用 QTableWidget 对象的 horizontalHeader 设置表格为自适应的伸缩模式,即可以根据窗口大小来改变网格的大小
tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
tableWidget.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
- 设置单元格的文本对齐方式
newItem.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
- 表格头的隐藏与显示
tableWidget.horizontalHeader().setVisible(False)
- 在单元格中放置控件
# 在单元格中放置控件
# 放置一个下拉列表框
comBox = QComboBox()
comBox.addItem("男")
comBox.addItem("女")
# 设置样式表 距离表格单元格的外边距为3px
comBox.setStyleSheet("QComBox{margin:3px};")
tableWidget.setCellWidget(1, 1, comBox)
# 放置一个按钮
serchBtn = QPushButton("修改")
serchBtn.setDown(True)
serchBtn.setStyleSheet("QPushButton{margin:3px};")
# 在表格中添加小部件
tableWidget.setCellWidget(1, 2, serchBtn)
- 在表格中快速定位到指定行
_应用场景_当 tableWidget 表格的行数很多时,可以通过输入行号进行直接定位并显示,比如输入 10,就直接显示第 10 行
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class TableWidgetDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("在表格中快速定位到指定行")
self.resize(600, 800)
layout = QHBoxLayout()
tableWidget = QTableWidget(30, 4)
layout.addWidget(tableWidget)
for i in range(30):
for j in range(4):
itemContent = '(%d,%d)' % (i,j)
tableWidget.setItem(i, j, QTableWidgetItem(itemContent))
self.setLayout(layout)
# 遍历表格查找对应选项
text = "(10,1)"
items = tableWidget.findItems(text, Qt.MatchExactly)
item = items[0]
# 选中查找到的单元格 选中的颜色是颜色,所以下面设置的单元格的背景颜色就看不出是红色的了
item.setSelected(True)
# 设置单元格的背景颜色为红色
item.setForeground(QBrush(QColor(255, 0, 0)))
# 获取到选中的那一行的索引
row = item.row()
print(item)
print("row=", row)
tableWidget.verticalScrollBar().setSliderPosition(row)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = TableWidgetDemo()
win.show()
sys.exit(app.exec_())
- 设置单元格的文本颜色
item.setForeground(QBrush(QColor(255, 0, 0)))
- 将单元格文本字体加粗
item.setFont(QFont("Times", 12, QFont.Black))
- 设置单元格的排序方式
# 升序
tableWidget.sortItems(2, Qt.AscendingOrder)
# 降序
tableWidget.sortItems(2, Qt.DescendingOrder)
- 设置单元格的对齐方式
# 设置单元格文本的对齐方式 ---此处设置为居中
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
- 合并单元格效果的实现
# 合并单元格---设置第一行第一列占据三行一列
tableWidget.setSpan(0, 0, 3, 1)
- 设置单元格的大小
# 设置单元格的大小---设置第一行高为120,列宽为150
tableWidget.setColumnWidth(0, 120)
tableWidget.setRowHeight(0, 150)
- 在表格中不显示分割线
# 在表格中设置不显示分割线
tableWidget.setShowGrid(False)
- 只显示水平表头,不显示垂直表头
# 在表格中只显示水平表头,不显示垂直表头
tableWidget.verticalHeader().setVisible(False)
- 为单元格添加图片
# 为单元格添加图片
newitem = QTableWidgetItem(QIcon("./images/open.png"), "背包")
tableWidget.setItem(0, 3, newitem)
- 改变单元格中显示图片的大小
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class TableWidgetDemo3(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("改变单元格中显示图片的大小")
self.resize(600, 800)
layout = QHBoxLayout()
tableWidget = QTableWidget()
tableWidget.setColumnCount(3)
tableWidget.setRowCount(5)
tableWidget.setHorizontalHeaderLabels(['图片1', '图片2', '图片3'])
# 设置表格不可编辑
tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 设置图标的大小
tableWidget.setIconSize(QSize(300, 200))
# 设置单元格的大小与图片的大小相同
for i in range(3):
tableWidget.setColumnWidth(i, 300)
for i in range(5):
tableWidget.setRowHeight(i, 200)
# 这里使用一个简单的算法来遍历表格,除此之外当然可以使用嵌套循环
for k in range(15): # 模拟产生15个数据
i = k/3
j = k%3
item = QTableWidgetItem()
# 用户点击表格时,图片被选中
item.setFlags(Qt.ItemIsEnabled)
icon = QIcon(r"./images/bao%d.png" % k)
item.setIcon(icon)
print('e/icons/%d.png i=%d j=%d' % (k, i, j))
tableWidget.setItem(i, j, item)
tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
tableWidget.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
layout.addWidget(tableWidget)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = TableWidgetDemo3()
win.show()
sys.exit(app.exec_())
- 支持右键菜单
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class tableWidgetDemo4(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("支持右键菜单")
self.resize(500, 300)
layout = QHBoxLayout()
self.tableWidget = QTableWidget(5, 3)
layout.addWidget(self.tableWidget)
self.tableWidget.setHorizontalHeaderLabels(['姓名', '性别', '体重'])
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
newItem = QTableWidgetItem("张三")
self.tableWidget.setItem(0, 0, newItem)
newItem = QTableWidgetItem("男")
self.tableWidget.setItem(0, 1, newItem)
newItem = QTableWidgetItem("160")
self.tableWidget.setItem(0, 2, newItem)
newItem = QTableWidgetItem("李四")
self.tableWidget.setItem(1, 0, newItem)
newItem = QTableWidgetItem("女")
self.tableWidget.setItem(1, 1, newItem)
newItem = QTableWidgetItem("170")
self.tableWidget.setItem(1, 2, newItem)
# 设置允许右键产生对应的菜单
self.tableWidget.setContextMenuPolicy(Qt.CustomContextMenu)
self.tableWidget.customContextMenuRequested.connect(self.generateMenu)
self.setLayout(layout)
def generateMenu(self, position):
print(position)
print("我是生成的菜单")
row_num = -1
indexes = self.tableWidget.selectionModel().selection().indexes()
print(indexes)
# 这里表示下面的显示行索引为在tableWidget控件上所有选择的最后选中项
for i in indexes:
row_num = i.row()
print(row_num)
if row_num < 2:
menu = QMenu(self)
item1 = menu.addAction("选项一")
item2 = menu.addAction("选项二")
item3 = menu.addAction("选项三")
# 这里是进入了生成菜单的消息循环中,action一直在等待接收用户的右键菜单选择条目
# 使用坐标系统的映射算法,将菜单显示在右键的大致位置mapToGlobal()
# 应用程序的右键点击坐标映射到全局屏幕坐标
action = menu.exec_(self.tableWidget.mapToGlobal(position))
print(action)
if action == item1:
print(action)
print(item1)
print(row_num)
print("你选择了选项一,当前行的文字内容是:",
self.tableWidget.item(row_num, 0).text(),
self.tableWidget.item(row_num, 1).text(),
self.tableWidget.item(row_num, 2).text())
elif action == item2:
print(row_num)
print("你选择了选项二,当前行的文字内容是:",
self.tableWidget.item(row_num, 0).text(),
self.tableWidget.item(row_num, 1).text(),
self.tableWidget.item(row_num, 2).text())
elif action == item3:
print(row_num)
print("你选择了选项三,当前行的文字内容是:",
self.tableWidget.item(row_num, 0).text(),
self.tableWidget.item(row_num, 1).text(),
self.tableWidget.item(row_num, 2).text())
else:
return
if __name__ == '__main__':
app = QApplication(sys.argv)
win = tableWidgetDemo4()
win.show()
sys.exit(app.exec_())
解析
- 选中某个单元行,单击右键,弹出的菜单显示位置是右键点击的坐标的全局映射
# 应用程序的右键点击坐标映射到全局屏幕坐标
action = menu.exec_(self.tableWidget.mapToGlobal(position))
- 当选择弹出菜单的条目时,会根据用户选择来显示相关行内容,行索引的选取是根据用户鼠标选择时的最后一个选中项
for i in indexes:
row_num = i.row()
if action == item1:
print(action)
print(item1)
print(row_num)
print("你选择了选项一,当前行的文字内容是:",
self.tableWidget.item(row_num, 0).text(),
self.tableWidget.item(row_num, 1).text(),
self.tableWidget.item(row_num, 2).text())
5.1.5 QTreeView 树形视图
- QTreeWideget 类是原生 QTreeView 的派生类,这个类实现了树形显示,并自带有绑定的数据模型,不必像原生类要自定义数据模型
- 常用的方法
- setColumnWidth(int column,int width) 设置指定列的宽度
- insertTopLevelItems() 在视图的顶层插入项目列表
- expandAll() 展开所有的树形节点
- invisibleRootItem() 返回树形控件中不可见的根选项
- selectedItems() 返回所有选中的非隐藏项目列表
- QTreeWideget 的数据模型 QTreeWidegetItem 中的常用方法
- addChild() 将子项追加到子列表中
- setText() 设置显示的节点文本
- Text() 返回显示的节点文本
- setCheckState(int column,int state)设置指定列的选中状态
- Qt.Checked 节点选中
- Qt.Unchecked 节点未选中
- setIcon(int column,QIcon icon) 在指定列中显示图标
- 树形结构的实现
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class treeWidget(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("treeWidget树形结构的实现")
layout = QHBoxLayout()
# 实例化一个树形控件
self.tree = QTreeWidget()
# 设置列数
self.tree.setColumnCount(2)
# 设置树形控件的头部标题
self.tree.setHeaderLabels(['Key', 'Value'])
# 设置根节点
root = QTreeWidgetItem()
root.setText(0, 'root')
root.setIcon(0, QIcon("./images/root.png"))
# 设置树形控件的列宽度
self.tree.setColumnWidth(0, 160)
# 设置子节点1
child1 = QTreeWidgetItem(root)
child1.setText(0, 'child1')
child1.setText(1, 'ios')
child1.setIcon(0, QIcon("./images/IOS.png"))
# 设置子节点2
child2 = QTreeWidgetItem(root)
child2.setText(0, 'child2')
child2.setText(1, '')
child2.setIcon(0, QIcon("./images/android.png"))
# 设置子节点3
child3 = QTreeWidgetItem()
child3.setText(0, 'child3')
child3.setText(1, 'music')
child3.setIcon(0, QIcon("./images/music.png"))
child2.addChild(child3)
# 将创建的所有条目列表添加到树形控件中
# self.tree.addTopLevelItem(root)
self.tree.insertTopLevelItem(0, root)
# 节点全部展开
self.tree.expandAll()
layout.addWidget(self.tree)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = treeWidget()
win.show()
sys.exit(app.exec_())
解析
- 可以使用继承来明确层级关系
child2 = QTreeWidgetItem(root)
- 也可以使用 QTreeWidgetItem 类的方法
child3 = QTreeWidgetItem()
child2.addChild(child3)
- 可以使用 insertTopLevelItems 方法添加节点
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class treeWidget(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("treeWidget树形结构的实现")
layout = QHBoxLayout()
# 实例化一个树形控件
self.tree = QTreeWidget()
# 设置列数
self.tree.setColumnCount(2)
# 设置树形控件的头部标题
self.tree.setHeaderLabels(['Key', 'Value'])
# 设置根节点
root = QTreeWidgetItem()
root.setText(0, 'root')
root.setIcon(0, QIcon("./images/root.png"))
# 设置树形控件的列宽度
self.tree.setColumnWidth(0, 160)
rootList = []
rootList.append(root)
# 设置子节点1
child1 = QTreeWidgetItem()
child1.setText(0, 'child1')
child1.setText(1, 'ios')
child1.setIcon(0, QIcon("./images/IOS.png"))
root.addChild(child1)
self.tree.insertTopLevelItems(0, rootList)
# 节点全部展开
self.tree.expandAll()
layout.addWidget(self.tree)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = treeWidget()
win.show()
sys.exit(app.exec_())
解析
- 设置节点的状态
child1.setCheckState(0, Qt.Checked)
- 设置节点指定列的背景颜色
brush_red = QBrush(Qt.red)
brush_green = QBrush(Qt.green)
root.setBackground(0, brush_red)
root.setBackground(1, brush_green)
- 给节点添加响应事件
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class treeWidget(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("treeWidget树形结构的实现")
# 实例化一个树形控件
self.tree = QTreeWidget()
# 设置列数
self.tree.setColumnCount(2)
# 设置树形控件的列宽度
self.tree.setColumnWidth(0, 160)
# 设置树形控件的头部标题
self.tree.setHeaderLabels(['Key', 'Value'])
# 设置根节点
root = QTreeWidgetItem()
root.setText(0, 'root')
root.setText(1, '0')
root.setIcon(0, QIcon("./images/root.png"))
child1 = QTreeWidgetItem(root)
child1.setText(0, "child1")
child1.setText(1, "1")
child2 = QTreeWidgetItem(root)
child2.setText(0, "child2")
child2.setText(1, "2")
child3 = QTreeWidgetItem(root)
child3.setText(0, "child3")
child3.setText(1, "3")
child4 = QTreeWidgetItem(child3)
child4.setText(0, "child4")
child4.setText(1, "4")
child5 = QTreeWidgetItem(child3)
child5.setText(0, "child5")
child5.setText(1, "5")
self.tree.addTopLevelItem(root)
self.tree.clicked.connect(self.onTreeClicked)
# 节点全部展开
self.tree.expandAll()
self.setCentralWidget(self.tree)
def onTreeClicked(self, modelIndex):
item = self.tree.currentItem()
print("key=%s, value=%s" % (item.text(0), item.text(1)))
if __name__ == '__main__':
app = QApplication(sys.argv)
win = treeWidget()
win.show()
sys.exit(app.exec_())
- 系统定制模式
概念 QTreeView 原生树形控件类可以使用操作系统提供的定制模式,比如文件系统盘的树形列表(QTreeWidget 不可以使用自定义的数据模型,而 QTreeView 可以绑定基于元数据的所有数据模型)
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
if __name__ == '__main__':
app = QApplication(sys.argv)
# Window系统的定制模式
model = QDirModel()
# 创建一个QTreeView视图控件
tree = QTreeView()
tree.setWindowTitle("QTreeView绑定系统的目录列表数据模型")
tree.resize(640, 480)
tree.setColumnWidth(0, 300)
# 为控件绑定数据模型
tree.setModel(model)
tree.show()
sys.exit(app.exec_())
5.2 容器:装载更多的控件
应用场景
5.2.1 QTabWidget
概念 QTabWidget 控件提供了一个选项卡和一个页面区域,默认显示第一个选项卡的页面,通过切换选项卡可以查看对应的页面
QTabWidget 常用的方法
- addTab() 将一个控件添加到 Tab 控件的选项卡中
- insertTab() 插入选项卡到 Tab 控件的指定位置
- removeTab() 根据指定的索引删除 Tab 控件的选项卡
- setCurrentIndex() 设置当前可见的选项卡的索引
- setCurrentWidget() 设置当前可见的页面
- setTabBar() 设置选项卡栏的小控件
- setTabPosition() 设置选项卡的位置
- QTabWidget.North 显示在页面的上方
- QTabWidget.South 显示在页面的下方
- QTabWidget.West 显示在页面的左侧
- QTabWidget.East 显示在页面的右侧
- setTabText() 设置 Tab 选项卡的显示文本
QTabWidget 常用的信号 - currentCahnged 切换当前选项卡页面时发射该信号
案例 5-4 QTabWidget 选项卡的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class TabWidgetDemo(QTabWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-4 QTabWidget选项卡的使用")
self.tab1 = QWidget()
self.tab2 = QWidget()
self.tab3 = QWidget()
self.addTab(self.tab1, "Tab 1")
self.addTab(self.tab2, "Tab 2")
self.addTab(self.tab3, "Tab 3")
self.tab1UI()
self.tab2UI()
self.tab3UI()
def tab1UI(self):
layout = QFormLayout()
layout.addRow("姓名", QLineEdit())
layout.addRow("地址", QLineEdit())
self.setTabText(0, "联系方式")
self.tab1.setLayout(layout)
def tab2UI(self):
layout = QFormLayout()
sex = QHBoxLayout()
sex.addWidget(QRadioButton("男"))
sex.addWidget(QRadioButton("女"))
layout.addRow(QLabel("性别"), sex)
layout.addRow("生日", QLineEdit())
self.setTabText(1, "个人详细信息")
self.tab2.setLayout(layout)
def tab3UI(self):
layout = QHBoxLayout()
layout.addWidget(QLabel("科目"))
layout.addWidget(QCheckBox("物理"))
layout.addWidget(QCheckBox("高数"))
self.setTabText(2, "教育程度")
self.tab3.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = TabWidgetDemo()
win.show()
sys.exit(app.exec_())
解析
- 在 QTabWidget 控件中添加了三个选项卡
self.tab1 = QWidget()
self.tab2 = QWidget()
self.tab3 = QWidget()
self.addTab(self.tab1, "Tab 1")
self.addTab(self.tab2, "Tab 2")
self.addTab(self.tab3, "Tab 3")
5.2.2 QStackedWidget 堆栈控件的使用
概念 QStackedWidget 是一个堆栈窗口,可以填充一些小控件,使用的是 QStackedLayout 布局
案例 5-5 QStackedWidget 堆栈控件的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class stackedWidgetDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-5 QStackedWidget堆栈控件的使用")
self.setGeometry(300, 50, 10, 10)
self.leftList = QListWidget()
firstItem = QListWidgetItem("联系方式")
self.leftList.insertItem(0, firstItem)
self.leftList.insertItem(1, "个人信息")
self.leftList.insertItem(2, "教育程度")
self.leftList.setCurrentItem(firstItem)
self.stack1 = QWidget()
self.stack2 = QWidget()
self.stack3 = QWidget()
self.stack1UI()
self.stack2UI()
self.stack3UI()
self.stackedWidget = QStackedWidget(self)
self.stackedWidget.addWidget(self.stack1)
self.stackedWidget.addWidget(self.stack2)
self.stackedWidget.addWidget(self.stack3)
layout = QHBoxLayout()
layout.addWidget(self.leftList)
layout.addWidget(self.stackedWidget)
self.setLayout(layout)
self.leftList.currentRowChanged.connect(self.dispaly)
def stack1UI(self):
layout = QFormLayout()
layout.addRow("姓名", QLineEdit())
layout.addRow("地址", QLineEdit())
self.stack1.setLayout(layout)
def stack2UI(self):
layout = QFormLayout()
sex = QHBoxLayout()
sex.addWidget(QRadioButton("男"))
sex.addWidget(QRadioButton("女"))
layout.addRow(QLabel("性别"), sex)
layout.addRow("生日", QLineEdit())
self.stack2.setLayout(layout)
def stack3UI(self):
layout = QHBoxLayout()
layout.addWidget(QLabel("科目"))
layout.addWidget(QCheckBox("物理"))
layout.addWidget(QCheckBox("高数"))
self.stack3.setLayout(layout)
def dispaly(self, i):
self.stackedWidget.setCurrentIndex(i)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = stackedWidgetDemo()
win.show()
sys.exit(app.exec_())
解析
- 使用了 QListWidget 的 currentRowChanged 信号来绑定用户的点击行为和右侧的 stackedWidget 子视图
self.leftList.currentRowChanged.connect(self.dispaly)
5.2.3 QDockWidget
概念 QDockWidget 是一个可以停靠在_QMainWindow_内的窗口控件,它可以保持浮动状态,也可以在指定的位置上作为子窗口附加到主窗口中
- QMainWindow 类的主窗口对象保留一个用户停靠的区域,就在控件的中央区域
- QDockWidget 停靠控件在主窗口内可以移到新的区域
常用的方法 - setWidget() 在 Dock 窗口区域设置 QWidget
- setFloating() 设置 Dock 窗口可以浮动,True 为可以浮动
- setAllowedAreas() 设置窗口可以停靠的区域
- LeftDockWidgetArea 左边停靠区域
- RightDockWidgetArea 右边停靠区域
- TopDockWidgetArea 顶部停靠区域
- BottomDockWidgetArea 底部停靠区域
- NoDockWidgetArea 没有可以停靠的区域(不显示 Widget)
- setFeatures() 设置停靠窗口的功能属性
- DockWidgetClosable 可关闭
- DockWidgetMovable 可移动
- DockWidgetFloatable 可漂浮
- DockWidgetVerticalTitleBar 在左边显示垂直的标题栏
- AllDockWidgetFeatures 具有前三种属性的所有功能
- NoDockWidgetFeatures 不具备前三者的功能
案例 5-6 QDockWidget 停靠控件的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class dockWidgetDemo(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-6 QDockWidget停靠控件的使用")
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.addAction("Save")
file.addAction("quit")
self.dockWidget =QDockWidget("Dockable")
self.listWidget = QListWidget()
self.listWidget.addItem("item1")
self.listWidget.addItem("item2")
self.listWidget.addItem("item3")
self.dockWidget.setWidget(self.listWidget)
self.dockWidget.setFloating(False)
self.setCentralWidget(QTextEdit())
self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidget)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = dockWidgetDemo()
win.show()
sys.exit(app.exec_())
解析
- 在顶层窗口中添加一个中央小控件
self.setCentralWidget(QTextEdit())
- 在停靠窗口内添加 QListWidget 对象
self.listWidget = QListWidget()
self.listWidget.addItem("item1")
self.listWidget.addItem("item2")
self.listWidget.addItem("item3")
self.dockWidget.setWidget(self.listWidget)
- 在主窗口中添加停靠窗口控件并指定默认的停靠区域
self.setCentralWidget(QTextEdit())
self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidget)
5.2.4 多文档界面 QMdiArea
案例 5-7 多文档界面 QMdiArea 控件的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class mdiAreaDemo(QMainWindow):
count = 0
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-7 多文档界面QMdiArea控件的使用")
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.addAction("cascade")
file.addAction("Tiled")
file.triggered[QAction].connect(self.windowAction)
def windowAction(self, action):
print("Triggered")
if action.text() == "New":
mdiAreaDemo.count = mdiAreaDemo.count + 1
sub = QMdiSubWindow()
sub.setWidget(QTextEdit())
sub.setWindowTitle("subWindow" + str(mdiAreaDemo.count))
self.mdi.addSubWindow(sub)
sub.show()
# 使子窗口的排布为级联模式
elif action.text() == "cascade":
self.mdi.cascadeSubWindows()
# 使子窗口的排布为平铺模式
elif action.text() == "Tiled":
self.mdi.tileSubWindows()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = mdiAreaDemo()
win.show()
sys.exit(app.exec_())
解析
- 主窗口拥有一个菜单和一个 QMdiArea 控件
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.addAction("cascade")
file.addAction("Tiled")
- 设置了菜单栏的 action 的触发事件
file.triggered[QAction].connect(self.windowAction)
- 在触发事件中设置了 mdi 多文档界面的子窗口,并设置了排布模式
if action.text() == "New":
mdiAreaDemo.count = mdiAreaDemo.count + 1
sub = QMdiSubWindow()
sub.setWidget(QTextEdit())
sub.setWindowTitle("subWindow" + str(mdiAreaDemo.count))
self.mdi.addSubWindow(sub)
sub.show()
# 使子窗口的排布为级联模式
elif action.text() == "cascade":
self.mdi.cascadeSubWindows()
# 使子窗口的排布为平铺模式
elif action.text() == "Tiled":
self.mdi.tileSubWindows()
5.2.5 QScrollBar
概念通过提供水平垂直滚动条来扩大当前窗口的有效装载面积
常用的信号
- valueCahnged 当滚动条的值发生改变时发射此信号
- sliderMoved 当滑块被用户拖动时发射此信号
案例 5-8 QScrollBar 滚动条的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class scrollBarDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-8 QScrollBar滚动条的使用")
layout = QHBoxLayout()
self.l1 = QLabel("拖动滑块改变颜色")
self.l1.setFont(QFont("Arial", 16))
layout.addWidget(self.l1)
self.s1 = QScrollBar()
self.s1.setMaximum(255)
self.s1.sliderMoved.connect(self.sliderval)
self.s2 = QScrollBar()
self.s2.setMaximum(255)
self.s2.sliderMoved.connect(self.sliderval)
self.s3 = QScrollBar()
self.s3.setMaximum(255)
self.s3.sliderMoved.connect(self.sliderval)
layout.addWidget(self.s1)
layout.addWidget(self.s2)
layout.addWidget(self.s3)
self.setGeometry(300, 300, 300, 300)
self.setLayout(layout)
def sliderval(self):
print(self.s1.value(), self.s2.value(), self.s3.value())
palette = QPalette()
color = QColor(self.s1.value(), self.s2.value(), self.s3.value())
palette.setColor(QPalette.Foreground, color)
self.l1.setPalette(palette)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = scrollBarDemo()
win.show()
sys.exit(app.exec_())
解析
- 设置了三个滚动条来控制标签字体的颜色,通过移动滑块来关联槽函数
self.s3.sliderMoved.connect(self.sliderval)
- 在设置调色板时,注意前景色和背景色的设置
# 前景色指的是设置主体自身的颜色
palette.setColor(QPalette.Foreground, color)
# 背景色指的是设置主体的底色也就是背景颜色
palette.setColor(QPalette.Background, color)
5.3 多线程
应用背景执行一个很耗时的操作,会使得程序卡顿很久,导致系统认为程序运行出错自动关闭程序
- 一般来说,多线程技术大致有三种形式
- 使用定时器模块 QTimer
- 使用多线程模块 QThread
- 使用事件处理的功能
5.3.1 QTimer 定时器模块
使用创建 QTimer 实例,将其 timeout 信号关联到槽函数然后周期性运行槽函数的 start 函数
常用的方法
- start(ms) 启动或者重启定时器,时间间隔为毫秒,如果已经在运行就先停止再去重启,如果_singleShot_信号为真,定时器仅激活一次
- stop() 停止定时器
常用的信号 - singleSlot 在给定的时间间隔后调用一个槽函数时发射该信号
- timeout 当定时器超时时发射该信号
案例 5.3.1 QTimer 定时器模块的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class timerDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5.3.1 QTimer 定时器模块的使用")
layout = QGridLayout()
self.listFile = QListWidget()
self.label = QLabel("显示当前时间")
self.startBtn = QPushButton("开始")
self.endBtn = QPushButton("结束")
# 初始化一个定时器
self.timer = QTimer(self)
# showTime()方法
self.timer.timeout.connect(self.showTime)
layout.addWidget(self.label, 0, 0, 1, 2)
layout.addWidget(self.startBtn, 1, 0)
layout.addWidget(self.endBtn, 1, 1)
self.startBtn.clicked.connect(self.startTimer)
self.endBtn.clicked.connect(self.endTimer)
self.setLayout(layout)
def showTime(self):
# 获取系统的当前时间
time = QDateTime.currentDateTime()
# 设置系统时间的显示格式
timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd")
# 在标签上显示时间
self.label.setText(timeDisplay)
def startTimer(self):
# 设置时间间隔并启动定时器
self.timer.start(1000)
self.startBtn.setEnabled(False)
self.endBtn.setEnabled(True)
def endTimer(self):
self.timer.stop()
self.startBtn.setEnabled(True)
self.endBtn.setEnabled(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = timerDemo()
win.show()
sys.exit(app.exec_())
解析
- 将 timer 的 timeout 信号关联 showTime 槽函数显示当前时间并显示在标签上
self.timer.timeout.connect(self.showTime)
def showTime(self):
# 获取系统的当前时间
time = QDateTime.currentDateTime()
# 设置系统时间的显示格式
timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd")
# 在标签上显示时间
self.label.setText(timeDisplay)
- 单击开始,启动定时器并使 startBtn 失效
def startTimer(self):
# 设置时间间隔并启动定时器
self.timer.start(1000)
self.startBtn.setEnabled(False)
self.endBtn.setEnabled(True)
案例 5.3.1 QTimer 定时器模块的使用(二)
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class timerDemo2(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5.3.1 QTimer 定时器模块的使用(二)")
layout = QVBoxLayout()
self.label = QLabel("<font color=red size=128><b>Hello PyQt,窗口将在10秒后消失</b></font>")
self.timer = QTimer(self)
self.timer.singleShot(10000, app.quit)
layout.addWidget(self.label)
# 设置无边框窗口
self.setWindowFlags(Qt.SplashScreen | Qt.FramelessWindowHint)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = timerDemo2()
win.show()
sys.exit(app.exec_())
解析
- 弹出窗口在 10 秒后消失,模仿启动画面并设置窗口边框为无
self.label = QLabel("<font color=red size=128><b>Hello PyQt,窗口将在10秒后消失</b></font>")
# 设置无边框窗口
self.setWindowFlags(Qt.SplashScreen | Qt.FramelessWindowHint)
- 使用定时器的单槽模式来完成程序的 10 秒退出
self.timer = QTimer(self)
self.timer.singleShot(10000, app.quit)
5.3.2 QThread 多线程
概念 QThread 是 Qt 线程类中最核心的底层类,由于 PyQt 的跨平台性,QThread 隐藏了所有与平台相关的代码
应用场景
可以在自定义的 QThread 实例中自定义信号,并将信号连接到指定的槽函数,当满足一定业务条件后发射该信号
QThread 常用的方法
- start() 启动线程
- wait() 阻止线程,直到满足如下条件之一
- 等待时间超时返回 False,单位是毫秒
- 不超时(线程必须从 run()返回)或者线程尚未启动,返回 True
- sleep() 强制当前线程睡眠几秒
QThread 常用的信号 - started 在启动线程执行 run()之前,一般用来资源的初始化
- finished 在线程完成业务逻辑之后,一般用来资源的释放
__
案例 5.3.2 QThread 多线程的基本使用
_使用技巧_在简单数据读取时可以直接在窗口初始化时完成;在复杂数据读取时(比如网络请求数据时间比较长)则可以将这部分逻辑放在 QThred 线程中
概念 MVC(模型-视图-控制器)实现界面的数据显示和数据读取的分离
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class myThread(QThread):
# 自定义一个信号
sinOut = pyqtSignal(str)
def __init__(self):
super().__init__()
self.working = True
self.num = 0
def __del__(self):
self.working = False
self.wait() # 阻塞程序,直到线程执行完毕
def run(self):
while self.working == True:
file_str = "File index {0}".format(self.num)
self.num += 1
# 发射该信号
self.sinOut.emit(file_str)
# 线程休眠2秒
self.sleep(2)
class threadWidgetDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5.3.2 QThread多线程的基本使用")
layout = QGridLayout()
self.thread = myThread()
self.listFile = QListWidget()
self.startBtn = QPushButton("开始")
layout.addWidget(self.listFile, 0, 0, 1, 2)
layout.addWidget(self.startBtn, 1, 1)
self.startBtn.clicked.connect(self.slotStart)
self.thread.sinOut.connect(self.slotAdd)
self.setLayout(layout)
def slotAdd(self, file_index):
self.listFile.addItem(file_index)
def slotStart(self):
self.startBtn.setEnabled(False)
self.thread.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = threadWidgetDemo()
win.show()
sys.exit(app.exec_())
解析
- 单击开始按钮,启动线程并使按钮失效
self.startBtn.clicked.connect(self.slotStart)
def slotStart(self):
self.startBtn.setEnabled(False)
self.thread.start()
- 比较复杂的是线程中自定义的信号,sinOut 信号连接槽函数 slotAdd()会从线程发射信号中定时读取数据,并把返回的数据动态添加到列表
# 发射该信号
self.sinOut.emit(file_str)
# 主线程接收信号的触发
self.thread.sinOut.connect(self.slotAdd)
# 槽函数响应,动态添加条目到列表中
def slotAdd(self, file_index):
self.listFile.addItem(file_index)
案例 5.3.2 主 UI 的卡死现象
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
global sec
sec = 0
def setTime():
global sec
sec += 1
# LCD显示数字+1
lcdNumber.display(sec)
def work():
# 计时器每秒计数
TestBtn.setEnabled(False)
timer.start(1000)
for i in range(200000000):
pass
timer.stop()
if __name__ == '__main__':
app = QApplication(sys.argv)
top = QWidget()
top.resize(300, 120)
# 垂直布局
layout = QVBoxLayout()
# 添加一个显示面板
lcdNumber = QLCDNumber()
layout.addWidget(lcdNumber)
TestBtn = QPushButton("测试")
layout.addWidget(TestBtn)
timer = QTimer()
# 每次计时结束,触发setTime函数
timer.timeout.connect(setTime)
TestBtn.clicked.connect(work)
top.setLayout(layout)
top.show()
sys.exit(app.exec_())
解析
- 在 PyQt 中所有的窗口都在 UI 主线程中(就是执行 app.exec_()的线程),在这个线程中执行耗时的操作会阻塞 UI 线程,从而让窗口停止响应严重的会导致程序崩溃
案例 5-9 应用案例:分离主 UI 线程和工作线程
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
global sec
sec = 0
class myThread(QThread):
trigger = pyqtSignal()
def __init__(self):
super().__init__()
def run(self):
for i in range(200000):
pass
# 循环完毕发射信号
self.trigger.emit()
def setTime():
global sec
sec += 1
# LCD显示数字+1
lcdNumber.display(sec)
def work():
# 计时器每秒计数
timer.start(1000)
thread.start()
thread.trigger.connect(timeStop)
def timeStop():
timer.stop()
print("运行线程中的业务用时" + lcdNumber.value())
global sec
sec = 0
if __name__ == '__main__':
app = QApplication(sys.argv)
top = QWidget()
top.resize(300, 120)
# 垂直布局
layout = QVBoxLayout()
# 添加一个显示面板
lcdNumber = QLCDNumber()
layout.addWidget(lcdNumber)
TestBtn = QPushButton("测试")
layout.addWidget(TestBtn)
timer = QTimer()
thread = myThread()
TestBtn.clicked.connect(work)
# 每次计时结束,触发setTime函数
timer.timeout.connect(setTime)
top.setLayout(layout)
top.show()
sys.exit(app.exec_())
5.3.3 事件处理
概念 PyQt 为事件处理提供了两种机制:高级的信号与槽机制,以及低级的事件处理程序 processEvents()---简单说就是_刷新页面_
案例 5.3.3 事件处理的低级处理程序的使用
import sys
import time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class processEventDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5.3.3 事件处理的低级处理程序的使用")
self.listFile = QListWidget()
self.startBtn = QPushButton("开始")
layout = QGridLayout()
layout.addWidget(self.listFile, 0, 0, 1, 2)
layout.addWidget(self.startBtn, 1, 1)
self.startBtn.clicked.connect(self.slotAdd)
self.setLayout(layout)
def slotAdd(self):
for i in range(10):
str_i = "File index {}".format(i)
self.listFile.addItem(str_i)
QApplication.processEvents()
time.sleep(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = processEventDemo()
win.show()
sys.exit(app.exec_())
5.4 网页交互
- PyQt5 使用 QWebEngineView 控件来展示 HTML5 页面,使用的是 Chrom 内核
- 可以通过 PyQt5.QtWebEngineWidgets.QWebEngineView 类来使用网页控件
- QWebEngineView 常用的方法
- load(QUrl url) 加载指定的 URL 并显示(本质是使用 HTTP GET 方法加载 Web 页面)
- setHtml(QString &html) 在网页视图中设置指定的 HTML 内容
案例 5-10 加载并显示外部的 WEB 页面
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWebEngineWidgets import *
class loadUrlDemo(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-10 加载并显示外部的WEB页面")
self.setGeometry(5, 30, 1355, 730)
self.browser = QWebEngineView()
# 加载外部的WEB页面
self.browser.load(QUrl("http://www.cnblogs.com/wangshuo1"))
self.setCentralWidget(self.browser)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = loadUrlDemo()
win.show()
sys.exit(app.exec_())
案例 5-11 加载并显示本地的 WEB 页面
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWebEngineWidgets import *
class loadUrlDemo(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-10 加载并显示外部的WEB页面")
self.setGeometry(5, 30, 1900, 1000)
self.browser = QWebEngineView()
# 加载本地的WEB页面
URL = r"D:/大背景VIP解析站html源码/index.html"
self.browser.load(QUrl(URL))
self.setCentralWidget(self.browser)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = loadUrlDemo()
win.show()
sys.exit(app.exec_())
案例 5-12 加载并显示嵌入的 HTML 代码
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWebEngineWidgets import *
class loadUrlDemo(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("案例5-10 加载并显示外部的WEB页面")
self.setGeometry(5, 30, 1900, 1000)
self.browser = QWebEngineView()
# 加载HTML代码
self.browser.setHtml('''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>起风了视频解析</title>
<meta name="keywords" content="vip视频解析,爱奇艺vip解析,腾讯vip解析,芒果TVvip解析"/>
<meta name="descriptipn" content="视频站"/>
<link href="css/kz.css" rel="stylesheet" type="text/css">
<button class="sy" id="bybf" type="button" onclick="dihejk3()"><div style="color:#ffffff">友情链接</div></button>
<script type="text/javascript">
function dihejk(){
var diz=document.getElementById("dz").value;
var jkdz=document.getElementById("jieko");
var jk=document.getElementById("jieko").selectedIndex;
var jkv=jkdz.options[jk].value;
var cljdz=document.getElementById("bfq");
cljdz.src=jkv+diz;
}
function dihejk2(){
var diz=document.getElementById("dz").value;
var jkdz=document.getElementById("jieko");
var jk=document.getElementById("jieko").selectedIndex;
var jkv=jkdz.options[jk].value;
//var cljdz=document.getElementById("bfq");
window.open(jkv+diz);
}
function dihejk3(){
window.open("index.php")
}
window.onload=function() {
var canvas = document.getElementById("tianxian");
var context = canvas.getContext("2d");
//弧线
context.beginPath();
context.moveTo(55, 20);
context.lineTo(155, 150);
context.lineTo(255, 20);
context.lineJoin = "bevel";
context.lineCap="butt";
context.lineWidth="5";
context.strokeStyle="#fff"
context.stroke();
}
</script>
</head>
<body>
<div id="zhenti">
<canvas id="tianxian">你的浏览器太旧不支持</canvas>
<div id="wbk">
<div id="bt">
<span class="font-display">QFL SPIP</span>
<br/>
<a href="http://www.ismallcolor.com" target="_blank"></a>
</div>
<div id="kj">
<iframe src="Explain.html" id="bfq" width="100%" height="100%"
frameborder="0" scrolling="no">
</iframe>
</div>
<div id="an">
<select class="jieko" id="jieko" name="接口">
<option value="http://api.nepian.com/ckparse/?url=">通用解析1</option>
<option value="http://q.z.vip.totv.72du.com/?url=">通用解析2</option>
<option value="http://www.wmxz.wang/video.php?url=">通用解析3</option>
<option value="http://api.wlzhan.com/sudu/?url=">通用解析4</option>
<option value="http://aikan-tv.com/?url=">爱奇艺高清</option>
<option value="http://apiv.ga/magnet/" >磁力链接</option>
</select>
<input class="dzsr" type="search" placeholder="输入视频地址:例如(http://www.iqiyi.com/v_19rr9k5h1s.html)" size="80" id="dz">
<button class="bfann" style="background-color:transparent" id="bf" type="button" onclick="dihejk()">
<img src="1.png" >
</button>
<button class="byan" id="bybf" type="button" onclick="dihejk2()"><div style="color:#ffffff">网页打开</div></button>
</div>
</div>
</div>
<style type="text/css">
select.jieko
{
position:absolute;
left:620px;
top:715px
}
input.dzsr
{
position:absolute;
left:720px;
top:715px
}
button.bfann
{
position:absolute;
left:1240px;
top:715px
}
button.byan
{
position:absolute;
left:1290px;
top:718px
}
button.sy
{
position:absolute;
left:1360px;
top:718px
}
</style>
</body>
</html>
''')
self.setCentralWidget(self.browser)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = loadUrlDemo()
win.show()
sys.exit(app.exec_())
解析
- 使用 QWebEngineView 对象 setHtml()函数渲染 html 代码时,如果页面中使用的 JS 代码超过_2MB_,程序渲染就会失败,页面渲染后将会显示大面积的空白
案例 5-13 PyQt 调用 JavaScript 代码
_使用_通过 QWebEnginePage 类的 runJavaScript(str, Callable)函数可以很方便地实现 PyQt 和 HTML/JavaScript 的双向通信,也实现了 Python 代码和 HTML/JavaScript 代码的解耦
- QWebEnginePage.runJavaScript(str, Callable)
学习小技巧
- 明显 QWebEngineView 实例的.page()方法也是 QWebEnginePage 类对象,所以也可以使用 view.page().runJavaScript(str, Callable)
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWebEngineWidgets import *
class webViewDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PyQt调用JavaScript代码")
layout = QVBoxLayout()
self.view = QWebEngineView()
self.view.setHtml('''
<html>
<head>
<title>A Demo Page</title>
<script language="JavaScript">
// 获取输入的姓名,然后在页面中显示提交按钮
function completeAndReturnName() {
var fname = document.getElementById('fname').value;
var lname = document.getElementById('lname').value;
var full = fname + ' ' + lname;
document.getElementById('fullname').value = full;
document.getElementById('submit-btn').style.display = 'block';
}
</script>
</head>
<body>
<form>
<label for="fname">First Name:</label>
<input type="text" name="fname" id="fname">
<br>
<label for="lname">Last Name:</label>
<input type="text" name="lname" id="lname">
<br>
<label for="fullname">Full Name:</label>
<input disabled type="text" name="fullname" id="fullname">
<br>
<input style="display: none;" type="submit" id="submit-btn">
</form>
</body>
</html>
''')
# 创建一个按钮用于调用JavaScript代码
self.btn = QPushButton("设置全名")
self.btn.clicked.connect(self.complete_name)
layout.addWidget(self.view)
layout.addWidget(self.btn)
self.setLayout(layout)
def js_callback(self, result):
print(result)
def complete_name(self):
self.view.page().runJavaScript('completeAndReturnName();', self.js_callback)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = webViewDemo()
win.show()
sys.exit(app.exec_())
解析
- 初始化一个 QWebEngineView 对象 view,通过 view.page()函数获得 QWebEnginePage 对象
def js_callback(self, result):
print(result)
def complete_name(self):
self.view.page().runJavaScript('completeAndReturnName();', self.js_callback)
案例 5-14 JavaScript 调用 PyQt 代码
_概念_JavaScript 调用 pyqt 代码,指的是 pyqt 可以与加载的 WEB 页面进行双向的数据交互
- 使用 QWebEngineView 加载 WEB 页面,就可以获取到页面中表单输入数据,在 WEB 页面通过 js 代码收集用户提交的数据
- 在 WEB 页面中,js 通过桥接方式传递数据给 pyqt
- pyqt 接收到数据,经过业务处理后,还可以把处理的数据返回给 WEB 页面
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWebChannel import *
# from mySharedObject import MySharedObject
class MySharedObject(QWidget):
def __init__(self):
super().__init__()
def _getStrValue(self):
# 设置参数
return '100'
def _setStrValue(self, str):
# 获取参数
print("获得页面的参数是: %s" % str )
QMessageBox.information(self, "Infomation", "获得页面参数:%s" % str, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
# 需要定义对外发布的方法
strValue = pyqtProperty(str, fget=_getStrValue, fset=_setStrValue)
# 主函数部分切记不要写成一个类,不然会出错,原因大概是数据管道的双向连接问题
if __name__ == '__main__':
app = QApplication(sys.argv)
win = QWidget()
win.setWindowTitle("案例5-14 JavaScript调用PyQt代码")
layout = QVBoxLayout()
view = QWebEngineView()
htmlUrl = r"D:/Python_集合/PyQt5_Projects/PyQt5快速开发与实战/Pycharm_PyQt/Chapter01/test.html"
view.load(QUrl(htmlUrl))
# 创建一个QWebChannel对象,用来传递PyQt的参数到JavaScript---搭建数据管道
channel = QWebChannel()
myObj = MySharedObject()
channel.registerObject("bridge", myObj)
view.page().setWebChannel(channel)
# 把QWebEngineView控件和button控件添加到布局中
layout.addWidget(view)
win.setLayout(layout)
win.show()
sys.exit(app.exec_())
----------------------------------------配套的 HTML 代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>A Demo Page</title>
<script type="text/javascript" src="./qwebchannel.js"></script>
<script type="text/javascript">
// 添加一个事件监听器,事件触发响应函数为参数中的function
document.addEventListener("DOMContentLoaded", function () {
// 在WEB页面也建立起一个数据管道桥接PyQt5中建立的那个管道进行数据的双向通信
new QWebChannel(qt.webChannelTransport, function (channel) {
window.bridge = channel.objects.bridge;
alert("bridge=" + bridge + '\n从pyqt传来的参数=' + window.bridge.strValue);
});
});
function onshowMsgBox() {
console.log(window.bridge);
if (window.bridge) {
var fname = document.getElementById("fname").value;
window.bridge.strValue = fname
}
}
</script>
</head>
<body>
<form action="#">
<label for="fname">User Name:</label>
<input type="text" name="fname" id="fname">
<br>
<input type="button" value="传递参数到pyqt" onclick="onshowMsgBox();">
<input type="reset" value="重置">
</form>
</body>
</html>
解析
_注意_自定义的 MySharedObject 类可以必须要继承 QObject 和 QWidget 其中之一,继承 QObject 类可以使用的父类方法和属性就比较少,因为 QObject 类是大多数类的基类
- 创建 QWebChannel 对象,注册一个桥接对象,以便 PyQt 与 WEB 页面进行交互
# 创建一个QWebChannel对象,用来传递PyQt的参数到JavaScript
self.channel = QWebChannel()
myObj = MySharedObject()
self.channel.registerObject("bridge", myObj)
self.view.page().setWebChannel(self.channel)
- 创建共享数据的 PyQt 对象,便于交互
class MySharedObject(QWidget):
def __init__(self):
super().__init__()
def _getStrValue(self):
# 设置参数
return '100'
def _setStrValue(self, str):
# 获取参数
print("获得页面的参数是: %s" % str )
QMessageBox.information(self, "Infomation", "获得页面参数:%s" % str, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
# 需要定义对外发布的方法
strValue = pyqtProperty(str, fget=_getStrValue, fset=_setStrValue)
pyqtProperty
对外提供的 PyQt 对象方法,需要使用 pyqtProperty()函数让它暴露出来
小知识点在 PyQt5 中使用 pyqtProperty 函数定义 PyQt 对象中的属性与 Python 中的 property 函数相同
- pyqtProperty 的参数
- type 必填 属性的类型
- fget 选填 用于获取属性的值
- fset 用于设置属性的值
- freset 用于将属性的值重置为默认值
- fdel 用于删除属性
- Doc 属性的文档字符串
- .......... 还有一些基本不会用到
pyqtProperty 函数的测试用例
from PyQt5.QtCore import QObject, pyqtProperty
class MyObject(QObject):
def __init__(self, inVal=20):
super().__init__()
self.val = inVal
def readVal(self):
print('readVal={}'.format(self.val))
return self.val
def setVal(self, val):
print('setVal={}'.format(val))
self.val = val
ppVal = pyqtProperty(int, readVal, setVal)
if __name__ == '__main__':
obj = MyObject()
print("\n#1")
obj.ppVal = 10
print("\n#2")
print("obj.ppVal={}".format(obj.ppVal))
print("obj.readVal={}".format(obj.readVal()))
- 建立双向通信的数据管道---搭桥
// 在WEB页面也建立起一个数据管道桥接PyQt5中建立的那个管道进行数据的双向通信
document.addEventListener("DOMContentLoaded", function () {
new QWebChannel(qt.webChannelTransport, function (channel) {
window.bridge = channel.objects.bridge;
alert("bridge=" + bridge + '\n从pyqt传来的参数=' + window.bridge.strValue);
});
});
# 在PyQt中搭建桥
channel = QWebChannel()
myObj = MySharedObject()
# 给共享数据对象取别名为bridge
channel.registerObject("bridge", myObj)
view.page().setWebChannel(channel)
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于