20.1 列表视图QListView
20.2 树形视图QTreeView
20.3 表格视图QTableView
20.4 小结
在前一张所讲的列表控件QListWidget,树形控件QTreeWidget和表格控件QTableWidget是基于项(item-based)的控件,它们分别与QListWidgetItem,QTreeWidgetItem以及QTableWidgetItem一起使用。在基于项的控件中,数据是存储于项中再由对应的控件添加进去并显示的。而我们这章所讲的列表视图QListView,树形视图QTreeView和表格视图QTableView是基于模型的(model-based),也就是说有关数据的获取或者存储操作都是跟模型有关。当我们在处理大量数据的时候,采用基于模型的控件可以让程序的处理速度更快,性能更好。
需要一提的是QListView,QTreeView和QTableView分别是QListWidget,QTreeWidget和QTableWidget的父类,后三者的目的主要是为了使用方便,让开发更加快速(准确来说后三者也可以是视图,不过笔者还是习惯将前三者称作视图,后三者称为控件,比较好区分)。在高端复杂的程序中,还是建议使用前三者。
在分别讲解三个视图前,我们先来了解一下PyQt5中所提供的模型种类及用处:
QStringListModel | 存储一个字符串列表 |
QStandardItemModel | 存储任意的分层次的数据 |
QDirModel | 封装本地文件系统 |
QSqlQueryModel | 封装一个SQL结果集 |
QSqlTableModel | 封装一个SQL表 |
QSqlRelationalTableModel | 利用外键封装一个SQL表 |
QSortFilterProxyModel | 对模型数据进行排序或过滤 |
接下来我们结合模型和视图进行讲解(有关SQL的模型会在讲解数据库时再涉及)。
我们用QStringListModel和QListView来实现前一章中讲解QListWidget时所完成的程序:
import sys
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import QStringListModel
from PyQt5.QtWidgets import QApplication, QWidget, QListView, QLabel, QHBoxLayout, QAbstractItemView
class Demo(QWidget):
def __init__(self):
super(Demo, self).__init__()
self.item_list = ['item %s' % i for i in range(11)] # 1
self.model_1 = QStringListModel(self)
self.model_1.setStringList(self.item_list)
self.model_2 = QStringListModel(self) # 2
self.listview_1 = QListView(self) # 3
self.listview_1.setModel(self.model_1)
self.listview_1.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.listview_1.doubleClicked.connect(lambda: self.change_func(self.listview_1))
self.listview_2 = QListView(self) # 4
self.listview_2.setModel(self.model_2)
self.listview_2.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.listview_2.doubleClicked.connect(lambda: self.change_func(self.listview_2))
self.pic_label = QLabel(self) # 5
self.pic_label.setPixmap(QPixmap('arrow.png'))
self.h_layout = QHBoxLayout()
self.h_layout.addWidget(self.listview_1)
self.h_layout.addWidget(self.pic_label)
self.h_layout.addWidget(self.listview_2)
self.setLayout(self.h_layout)
def change_func(self, listview):
if listview == self.listview_1: # 6
self.model_2.insertRows(self.model_2.rowCount(), 1)
data = self.listview_1.currentIndex().data()
index = self.model_2.index(self.model_2.rowCount()-1)
self.model_2.setData(index, data)
else: # 7
self.model_2.removeRows(self.listview_2.currentIndex().row(), 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
1. 首先用列表推导式生成11个item,然后实例化一个QStringListModel并用setStringList(iterable)方法将数据添加到到模型中;
2. model_1用于listview_1,model_2用于listview_2;
3. 实例化一个列表视图listview_1,并调用setModel(model)方法将model_1作为参数传入,此时,列表视图就会显示出模型中的数据,模型数据发生任何改变,视图也会自动作出相应改变。setEditTriggers()方法可设置视图的编辑规则,可传入的参数如下(当然QListWidget也有相同方法):
常量 |
值 |
描述 |
QAbstractItemView::NoEditTriggers |
0 |
无法编辑 |
QAbstractItemView::CurrentChanged |
1 |
当前选择项发生改变时可编辑 |
QAbstractItemView::DoubleClicked |
2 |
双击时可编辑 |
QAbstractItemView::SelectedClicked |
4 |
单击一选中的内容时可编辑 |
QAbstractItemView::EditKeyPressed |
8 |
当编辑键被按下时可编辑 |
QAbstractItemView::AnyKeyPressed |
16 |
按下任意键可编辑 |
QAbstractItemView::AllEditTriggers |
31 |
包括以上全部触发条件 |
接着我们将视图的doubleClicked信号与自定义的槽函数change_func()连接了起来;
4. 同理,实例化一个listview_2,设置模型和编辑规则并将信号和槽函数进行连接;
5. pic_label用于显示图片;
6. 在槽函数中我们判断触发doubleClicked信号的视图,如果是listview_1的话,则我们通过insertRows(int, int)方法先在model_2中插入一个空行,该方法的第一个参数为行序号,即要将数据插入哪一行,第二个为要插入的行数量。在这里rowCount()方法获取总行数(在这里正好可以当做行序号,表示我们将新行添加到最后一行),1表示我们在每次双击时都只插入一行。
由于插入的只是空行,所以我们要调用setData(QModelindex, data)方法将该空行设为相应的内容。通过currentIndex().data()方法可以获取到被双击行的数据,比如双击在'Item 0'上,那么我们就获取了‘Item 0’这个文本。而调用QStringListModel的index(int)方法获取到指定的QModelIndex,传入的参数为行序号(请注意序号都是从0开始计算);
7. 如果是listview_2触发doubleClicked信号的话,那我们就通过调用removeRows(int, int),方法将被双击的行给删除掉。currentIndex().row()方法获取被双击行的行序号,1表示我们只删除一行。
运行截图如下,在左边视图双击可将相应行数据添加到右边视图:
双击右边视图中的行可进行删除:
树形视图常被用来显示文件目录:
import sys
from PyQt5.QtCore import QDir
from PyQt5.QtWidgets import QApplication, QWidget, QTreeView, QDirModel, QLabel, QVBoxLayout
class Demo(QWidget):
def __init__(self):
super(Demo, self).__init__()
self.resize(600, 300)
self.model = QDirModel(self) # 1
self.model.setReadOnly(False)
self.model.setSorting(QDir.Name | QDir.IgnoreCase)
self.tree = QTreeView(self) # 2
self.tree.setModel(self.model)
self.tree.clicked.connect(self.show_info)
self.index = self.model.index(QDir.currentPath())
self.tree.expand(self.index)
self.tree.scrollTo(self.index)
self.info_label = QLabel(self) # 3
self.v_layout = QVBoxLayout()
self.v_layout.addWidget(self.tree)
self.v_layout.addWidget(self.info_label)
self.setLayout(self.v_layout)
def show_info(self): # 4
index = self.tree.currentIndex()
file_name = self.model.fileName(index)
file_path = self.model.filePath(index)
file_info = 'File Name: {}\nFile Path: {}'.format(file_name, file_path)
self.info_label.setText(file_info)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
1. 实例化一个QDirModel,通过setReadOnly(False)方法可以让我们在QTreeView中直接对文件进行编辑,若设为True,则表示只读模式。setSorting()方法可以让显示的文件或文件夹按照指定要求进行排序,可传入的参数如下:
常量 |
值 |
描述 |
QDir::Name |
0x00 |
按照名称排序 |
QDir::Time |
0x01 |
按照修改时间排序 |
QDir::Size |
0x02 |
按照文件大小排序 |
QDir::Type |
0x80 |
按照文件类型排序 |
QDir::Unsorted |
0x03 |
无排序 |
QDir::NoSort |
-1 |
不排序(默认) |
QDir::DirsFirst |
0x04 |
将文件夹置于文件前 |
QDir::DirsLast |
0x20 |
将文件置于文件夹前 |
QDir::Reversed |
0x08 |
反向排序 |
QDir::IgnoreCase |
0x10 |
无视大小写排序 |
QDir::LocaleAware |
0x40 |
按照本地设置进行排序 |
2. 实例化一个树形视图,将其模型设为self.model。接着将clicked信号和自定义的槽函数连接起来。然后我们通过调用self.model的index()方法传入一个QDir.currentPath()来获取当前路径的QModelIndex索引值,再调用self.tree的expand()方法进行展开显示,scrollTo()方法则是将当前视图的视口滚动到self.index索引处。
3. info_label用于显示文件信息;
4. 在槽函数中,我们首先通过currentIndex()方法获取当前鼠标所点击的QModelIndex索引值,然后分别传入self.model的fileName()和filePath()函数中,最后用info_label显示出来:
运行截图如下:
这里我们将QStandItemModel和QTableView搭配使用:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QAbstractItemView, QLabel, QVBoxLayout
class Demo(QWidget):
def __init__(self):
super(Demo, self).__init__()
self.resize(650, 300)
self.model = QStandardItemModel(6, 6, self) # 1
# self.model = QStandardItemModel(self)
# self.model.setColumnCount(6)
# self.model.setRowCount(6)
for row in range(6): # 2
for column in range(6):
item = QStandardItem('({}, {})'.format(row, column))
self.model.setItem(row, column, item)
self.item_list = [QStandardItem('(6, {})'.format(column)) for column in range(6)]
self.model.appendRow(self.item_list) # 3
self.item_list = [QStandardItem('(7, {})'.format(column)) for column in range(6)]
self.model.insertRow(7, self.item_list) # 4
self.table = QTableView(self) # 5
self.table.setModel(self.model)
self.table.horizontalHeader().setStretchLastSection(True)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.clicked.connect(self.show_info)
self.info_label = QLabel(self) # 6
self.info_label.setAlignment(Qt.AlignCenter)
self.v_layout = QVBoxLayout()
self.v_layout.addWidget(self.table)
self.v_layout.addWidget(self.info_label)
self.setLayout(self.v_layout)
def show_info(self): # 7
row = self.table.currentIndex().row()
column = self.table.currentIndex().column()
print('({}, {})'.format(row, column))
data = self.table.currentIndex().data()
self.info_label.setText(data)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
1. 实例化一个QStandItemModel,可直接在实例化时传入行数和列数,或者也可以通过setRowCount()和setColumn()方法来设置。
2. QStandItemModel与QStandardItem搭配使用,这里通过循环实例化36个QStandardItem(每一个item代表一个单元格),接着调用setItem()方法将每一个Item放在相应的位置;
3. appendRow()方法可以把新行添加到表格最后,也就是说目前有7行;
4. insertRow()方法可以在指定方法添加一行,这里我们在最后一行插入一行,现在有8行;
5. 实例化一个QTableView,设置模型。self.table.horizontalHeader().setStretchLastSection(True)可以让表格填满整个窗口,如果拉伸窗口的话则为了填满窗口表格最后列会改变尺寸。setEditTriggers()方法设置编辑规则,这里我们设置无法编辑。最后将clicked信号和自定义的槽函数连接起来;
6. info_label用于显示单元格文本;
7. currentIndex().row()可以获取当前点击单元格所在的行序号,currentIndex().column()可以获取当前点击单元格所在的列序号。currentIndex().data()可以获取到当前点击单元格的文本;
运行截图如下:
1. 注意本章示例的模型和视图搭配并不是固定的,比如QDirModel不是只能用于QTreeView,该模型当然也可以和其他两种视图搭配使用。
2. QListWidget、QTreeWidget和QTableWidget分别是对QListView、QTreeView和QTableView封装后的简便类,所以前三者比后三者使用起来较为简单,不过后三者可以让程序在性能上更好,处理速度跟快。
----------------------------------------------------------------------
喜欢的小伙伴可以加入这个Python QQ交流群一起学习:820934083