《快速掌握PyQt5》第十九章 列表控件、树形控件、表格控件

第十九章 列表控件、树形控件、表格控件

19.1 列表控件QListWidget

19.2 树形控件QTreeWidget

19.3 表格控件QTableWidget

19.4 小结


《快速掌握PyQt5》专栏已整理成书出版,书名为《PyQt编程快速上手》,详情请见该链接。感谢大家一直以来的支持!祝大家PyQt用得越来越顺!

列表控件可以让我们以列表形式呈现内容,使界面更加有序美观。QListWidget列表控件应当与QListWidgetItem一起使用,后者作为项被添加入列表控件中,也就是说列表控件中的每一项都是一个QListWidgetItem。这也是为什么我们说QListWidget是一个基于项(Item-based)的控件了。

同样基于项的控件还有QTreeWidget树形控件和QTableWidget表格控件,前者以树状方式呈现内容,并与QTreeWidgetItem搭配使用;后者以表格形式呈现内容,并与QTableWidgetItem一起使用。

19.1 列表控件QListWidget

笔者曾经做了一个可以方便生成报价表的桌面小程序,截图如下:

程序中就用到了QListWidget,当选择一种产品系列后,左边的列表空间就会显示该系列的所有内容,然后双击其中的某项就可以在右边的列表控件中显示所双击的项:

我们就通过实现这个简单的功能来学习下QListWidget。

import sys
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QListWidget, QListWidgetItem, QHBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.pic_label = QLabel(self)                       # 1
        self.pic_label.setPixmap(QPixmap('arrow.png'))

        self.listwidget_1 = QListWidget(self)               # 2
        self.listwidget_2 = QListWidget(self) 
        self.listwidget_1.doubleClicked.connect(lambda: self.change_func(self.listwidget_1))
        self.listwidget_2.doubleClicked.connect(lambda: self.change_func(self.listwidget_2))

        for i in range(6):                                  # 3
            text = 'Item {}'.format(i)
            self.item = QListWidgetItem(text)
            self.listwidget_1.addItem(self.item)

        self.item_6 = QListWidgetItem('Item 6', self.listwidget_1)  # 4

        self.listwidget_1.addItem('Item 7')                         # 5
        str_list = ['Item 9', 'Item 10']
        self.listwidget_1.addItems(str_list)

        self.item_8 = QListWidgetItem('Item 8')                     # 6
        self.listwidget_1.insertItem(8, self.item_8)
        # self.listwidget_1.insertItem(8, 'Item 8')

        self.h_layout = QHBoxLayout()
        self.h_layout.addWidget(self.listwidget_1)
        self.h_layout.addWidget(self.pic_label)
        self.h_layout.addWidget(self.listwidget_2)
        self.setLayout(self.h_layout)

    def change_func(self, listwidget):                              # 7
        if listwidget == self.listwidget_1:
            item = QListWidgetItem(self.listwidget_1.currentItem())
            self.listwidget_2.addItem(item)
            print(self.listwidget_2.count())
        else:
            self.listwidget_2.takeItem(self.listwidget_2.currentRow())
            print(self.listwidget_2.count())


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. pic_label用于显示图片;

2. 实例化两个QListWidget,listwidget_1放在左边用于显示可选的内容,listwidget_2放在右边用于显示被双击的项。然后将这两个QListWidget控件的doubleClicked信号和自定义的槽函数连接起来,每当双击QListWidget中的某项时,就会触发该槽函数。

3. 循环创建六个QListWidgetItem,并通过调用addItem(QListWidgetItem)将其添加到listwidget_1中;

4. 当然也可以通过实例化时直接指定父类的方式进行添加;

5. 也可以不用QListWidgetItem,直接调用addItem(str)方法来添加一项内容。也可以使用addItem(Iterable)来添加一组项内容(不过若要让项呈现更多功能的话,还是应该选择QListWidgetItem);

6. 通过insertItem(row, QListWidgetItem)方法可以在指定行中加入一项内容;

7. 接下来我们讲一下槽函数:

def change_func(self, listwidget):
    if listwidget == self.listwidget_1:
        item = QListWidgetItem(self.listwidget_1.currentItem())
        self.listwidget_2.addItem(item)
        print(self.listwidget_2.count())
    else:
        self.listwidget_2.takeItem(self.listwidget_2.currentRow())
        print(self.listwidget_2.count())

在槽函数中,我们判断信号是哪一个QListWidget发出的,如果是listwidget_1的话,我们先通过currentItem()获取到当前被双击的项,之后实例化为QListWidgetItem,再通过addItem(QListWidgetItem)方法加入listwidget_2中,count()方法用于获取项数量,这里我们打印出listwidget_2中一共有多少项内容。若信号是listwidget_2发出的话,则将当前被双击项的行数传给takeItem(int)方法来进行删除,然后也打印下项数量。

bug report:currentItem()的返回值是QListWidgetItem,照理来说应该是可以直接被添加的,也就是说下方这种写法应该也是可以的,但是却没有用:

self.listwidget_2.addItem(self.listwidget_1.currentItem())

图片下载地址:

arrow.png: https://www.easyicon.net/download/png/3980/64/

运行截图如下,双击左边的某项,会发现右边的列表控件会显示出来:

双击右边的某项将其删除:

19.2 树形控件QTreeWidget

通常我们用QTreeWIdget来显示文件目录结构,比如下图:

但这里我们将用QTreeWidget实现在第七章讲解QCheckBox按钮时展示的安装程序树状结构:

我们取以下部分进行说明并实现:

可以看出根节点为Preview,其子节点为Qt5.11.2 snapshot,它一共有三种状态:全选中、无选中和半选中。而该子节点下面又有macOS、Android x86、Android ARMv7、Sources和iOS子节点。

下面开始实现,请看代码:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QTreeWidget, QTreeWidgetItem, QLabel, QHBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(500, 300)
        self.label = QLabel('No Click')                         # 1

        self.tree = QTreeWidget(self)                           # 2
        self.tree.setColumnCount(2)
        self.tree.setHeaderLabels(['Install Components', 'Test'])
        self.tree.itemClicked.connect(self.change_func)

        self.preview = QTreeWidgetItem(self.tree)               # 3
        self.preview.setText(0, 'Preview')

        # self.preview = QTreeWidgetItem()
        # self.preview.setText(0, 'Preview')
        # self.tree.addTopLevelItem(self.preview)

        self.qt5112 = QTreeWidgetItem()                         # 4
        self.qt5112.setText(0, 'Qt 5.11.2 snapshot')
        self.qt5112.setCheckState(0, Qt.Unchecked)
        self.preview.addChild(self.qt5112)

        choice_list = ['macOS', 'Android x86', 'Android ARMv7', 'Sources', 'iOS']
        self.item_list = []
        for i, c in enumerate(choice_list):                     # 5
            item = QTreeWidgetItem(self.qt5112)
            item.setText(0, c)
            item.setCheckState(0, Qt.Unchecked)
            self.item_list.append(item)

        self.test_item = QTreeWidgetItem(self.qt5112)           # 6
        self.test_item.setText(0, 'test1')
        self.test_item.setText(1, 'test2')

        self.tree.expandAll()                                   # 7

        self.h_layout = QHBoxLayout()
        self.h_layout.addWidget(self.tree)
        self.h_layout.addWidget(self.label)
        self.setLayout(self.h_layout)

    def change_func(self, item, column):
        self.label.setText(item.text(column))                   # 8

        print(item.text(column))
        print(column)
        if item == self.qt5112:                                 # 9
            if self.qt5112.checkState(0) == Qt.Checked:
                [x.setCheckState(0, Qt.Checked) for x in self.item_list]
            else:
                [x.setCheckState(0, Qt.Unchecked) for x in self.item_list]
        else:                                                   # 10
            check_count = 0
            for x in self.item_list:
                if x.checkState(0) == Qt.Checked:
                    check_count += 1

            if check_count == 5:
                self.qt5112.setCheckState(0, Qt.Checked)
            elif 0 < check_count < 5:
                self.qt5112.setCheckState(0, Qt.PartiallyChecked)
            else:
                self.qt5112.setCheckState(0, Qt.Unchecked)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. QLabel控件用于显示每个QTreeWidgetItem的文本;

2. 实例化一个QTreeWidget,通过setColumnCount(int)将该树状控件的列数设为2(默认为1列)。通过setHeaderLabels(Iterable)设置每列的标题,如果只有一列的话,则应该通过setHeaderLabel(str)方法设置。接着我们将itemClicked信号与自定义的槽函数连接起来,每当点击QTreeWidget中的任意一项时,都会触发itemClicked信号。QtAssistant中对该信号的说明如下:

我们发现这是一个带参数的信号,而文档中解释说:每当一项内容被点击时,参数item就是被点击的项,参数column就是被点击项所在的列。也就是说当该信号被触发时,参数item保存被点击的项,column保存列数,而这两个参数会自动传给我们的槽函数。为与之对应,我们的槽函数也带了两个参数,这样我们就可以知道被点击的项和列数了。

3. 实例化一个QTreeWidgetItem,并将其父类设为self.tree,表示self.preview为最外层(最顶层)的项,接着通过setText(int, str)方法来设置文本,第一个int类型参数为该文本所在的列,0表示放在第一列。当然我们也可以在实例化时不指定父类,并让self.tree调用addTopLevelItem()方法来设置最顶层的项;

4. setCheckState(int, CheckState)方法可以让该项以复选框形式呈现出来,addChild(QTreeWidgetItem)方法可以添加子项,这里让self.preview添加一个self.qt5112选项;

5. 实例化5个子项,将他们添加到self.qt5112中,并以复选框形式显示;

6. 这里的self.test项只是拿来作为对比,好让读者知道将QTreeWidget设为两列时的样子;

7. 调用expandAll()方法可以让QTreeWidget所有的项都是以打开状态显示的。注意必须要在所有项都已经实例化好之后再调用该方法,如果一开始就调用则会没有效果;

8. 在槽函数中,self.label显示对应的项文本,item就是被点击的项,我们调用text(int)传入列数,获得文本(该text()函数用法跟我们之前使用的有所不同)。

9. 如果被点击的项为qt5112,则我们判断是否其被选中,若是的话,将它的所有子项都设为选中状态,若为无选中状态的话,则将其子项全部设为无选中状态;

10. 若被点击是qt5112的子项时,我们判断有多少个子项已经被选中了,若数量为5,则设置qt5112为选中状态,若为0-5之间,则设为半选中状态,若等于0,则设为无选中状态。

运行截图如下:

19.3 表格控件QTableWidget

核心都是类似的,搭配QTableWidgetItem使用,下面就来讲一下常用的方法:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem


class Demo(QTableWidget):                               # 1
    def __init__(self):
        super(Demo, self).__init__()
        self.setRowCount(6)                             # 2
        self.setColumnCount(6)
        # self.table = QTableWidget(6, 6, self)

        print(self.rowCount())                          # 3
        print(self.columnCount())

        self.setColumnWidth(0, 30)                      # 4
        self.setRowHeight(0, 30)

        self.setHorizontalHeaderLabels(['h1', 'h2', 'h3', 'h4', ' h5', 'h6'])   # 5
        self.setVerticalHeaderLabels(['t1', 't2', 't3', 't4', 't5', 't6'])

        # self.setShowGrid(False)                       # 6

        self.item_1 = QTableWidgetItem('Hi')            # 7
        self.setItem(0, 0, self.item_1)

        self.item_2 = QTableWidgetItem('Bye')           # 8
        self.item_2.setTextAlignment(Qt.AlignCenter)
        self.setItem(2, 2, self.item_2)

        self.setSpan(2, 2, 2, 2)                        # 9

        print(self.findItems('Hi', Qt.MatchExactly))    # 10
        print(self.findItems('B', Qt.MatchContains))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 这里我们直接继承QTableWidget来实现程序;

2. setRowCount(int)设置表格的行数,setColumnCount(int)设置列数。或者可以选择在实例化的时候直接指定行列数;

3. rowCount()获取行数,columnCount()获取列数;

4. setColumnWidth(int, int)设置列款,第一个参数填列序号,第二个参数填宽度值。setRowCount(int, int)设置行宽,参数同理;

5. setHorizontalHeaderLabels(iterable)设置行的标题,而setVerticalHeaderLabels设置列的标题;

6. setShowGird(bool)设置是否显示表格上的网格线,True为显示,False不显示;

7. 实例化一个单元格,并用setItem(int, int, QTableWidgetItem)将该单元格添加到表格中。前两个int类型参数分别为行序号和列序号;

8. setTextAlignment()设置单元格的文本对齐方式,QtAssistant中输入Qt::Alignment就可以找到各种对齐方式:

9. setSpan(int, int, int, int)方法用来合并单元格,前两个int参数分别为行序号和列序号,后两个分别为要合并的行数和列数;

10. findItems(str, Qt.MatchFlag)方法用来进行查找,前一个参数为用来匹配的字符串,后一个参数为匹配方式。在代码中我们用了两种匹配方式,第一种为Qt.MatchExactly,表示精确匹配。第二种为Qt.MatchContains,表示包含匹配。在QtAssistant中输出Qt::MatchFlag即可了解各种匹配方式:

运行截图如下:

若要更加个性化地控制界面,请大家阅读:《PyQt5高级编程实战》学会使用视图委托

19.4 小结

 1. 列表控件QListWidget,树状控件QTreeWidget以及表格控件QTableWidget这三种控件都是基于项(item-based)的控件,应搭配QListWidgetItem,QTreeWidgetItem和QTableWidgetItem使用;

2. 多使用文档。

欢迎关注我的微信公众号,发现更多有趣内容:

  • 16
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

la_vie_est_belle

谢谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值