19.1 列表控件QListWidget
19.2 树形控件QTreeWidget
19.3 表格控件QTableWidget
19.4 小结
列表控件可以让我们以列表形式呈现内容,是界面更加有序美观。QListWidget列表控件应当与QListWidgetItem一起使用,后者作为项被添加入列表控件中,也就是说列表控件中的每一项都是一个QListWidgetItem。这也是为什么我们说QListWidget是一个基于项(Item-based)的控件了。
同样基于项的控件还有QTreeWidget树形控件和QTableWidget表格控件,前者以树状方式呈现内容,并与QTreeWidgetItem搭配使用;后者以表格形式呈现内容,并与QTableWidgetItem一起使用。
笔者曾经做了一个可以方便生成报价表的桌面小程序,截图如下:
程序中就用到了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/
运行截图如下,双击左边的某项,会发现右边的列表控件会显示出来:
双击右边的某项将其删除:
通常我们用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,则设为无选中状态。
运行截图如下:
核心都是类似的,搭配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即可了解各种匹配方式:
运行截图如下:
1. 列表控件QListWidget,树状控件QTreeWidget以及表格控件QTableWidget这三种控件都是基于项(item-based)的控件,应搭配QListWidgetItem,QTreeWidgetItem和QTableWidgetItem使用;
2. 多使用文档。
----------------------------------------------------------------------
喜欢的小伙伴可以加入这个Python QQ交流群一起学习:820934083