《快速掌握PyQt5》第二十章 列表视图、树形视图、表格视图

第二十章 列表视图、树形视图、表格视图

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的模型会在讲解数据库时再涉及)。

 

20.1 列表视图QListView

 我们用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表示我们只删除一行。

 

运行截图如下,在左边视图双击可将相应行数据添加到右边视图:

《快速掌握PyQt5》第二十章 列表视图、树形视图、表格视图_第1张图片

双击右边视图中的行可进行删除:

《快速掌握PyQt5》第二十章 列表视图、树形视图、表格视图_第2张图片

 

20.2 树形视图QTreeView

树形视图常被用来显示文件目录:

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显示出来:

 

运行截图如下:

《快速掌握PyQt5》第二十章 列表视图、树形视图、表格视图_第3张图片

 

20.3 表格视图QTableView

这里我们将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()可以获取到当前点击单元格的文本;

 

运行截图如下:

《快速掌握PyQt5》第二十章 列表视图、树形视图、表格视图_第4张图片

 

20.4 小结

1. 注意本章示例的模型和视图搭配并不是固定的,比如QDirModel不是只能用于QTreeView,该模型当然也可以和其他两种视图搭配使用。

2. QListWidget、QTreeWidget和QTableWidget分别是对QListView、QTreeView和QTableView封装后的简便类,所以前三者比后三者使用起来较为简单,不过后三者可以让程序在性能上更好,处理速度跟快。

 

----------------------------------------------------------------------

喜欢的小伙伴可以加入这个Python QQ交流群一起学习:820934083

《快速掌握PyQt5》第二十章 列表视图、树形视图、表格视图_第5张图片

你可能感兴趣的:(《快速掌握PyQt5》)