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)
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于