列表视图QListView
QListView 是 Qt 中用来存储列表的纯视图类。
在此之前,列表视图和图标视图由QListBox和QIconView 提供,QListView 基于 Qt的模型/视图架构提供更灵活的方法QListView 实现了 QAbstractItemView 定义的接口,可以显示从 QAbstractItemModel派生的模型提供的数据。
QListView 一般可以使用QStringListModel管理数据。
列表视图控件QListView 用于显示文本列表模型 QStringListModel 中的文本数据
用QListView创建列表视图控件的方法如下,其中 parent 是继承自QWidget 的窗口或容器控件。
from PySide6.QtWidgets import QListView
QListView(parent: Union[PySide6.QtWidgets.QWidget,NoneType]= None)-> None
列表视图控件QListView 的常用方法
列表视图控件 QListView 用于显示数据模型中某数据项下的所有子数据项的显示角色的文本。
列表视图控件没有表头,可以把数据显示成一列,也可以显示成一行。列表视图控件不仅可以显示文本列表模型中的数据,也可显示其他模型中的数据。
列表视图控件的常用方法如表 所示,主要方法介绍如下。
QListView的方法及参数类型 | 说 明 |
---|---|
setModel(QAbstractItemModel) | 设置数据模型 |
setSelectionModel(QItemSelectionModel) | 设置选择模型 |
selectionModel() | 获取选择模型 QItemSelectionModel |
setSelection(rect: QRect,command: QItemSelectionModel.SelectionFlags) | 选择指定范围内的数据项 |
indexAt(QPoint) | 获取指定位置处数据项的模型数据 索引 |
selectedIndexes() | 获取选中的数据项的索引列表List Cint] |
clearSelection() | 取消选择 |
clearPropertyFlags() | 清空属性标志 |
contentsSize() | 获取包含的内容所占据的尺寸QSize |
resizeContents(width:int,height: int) | 重新设置尺寸 |
scrollTo(QModelIndex) | 使数据项可见 |
setModelColumn(int) | 设置数据模型中要显示的列 |
modelColumn() | 获取模型中显示的列 |
setFlow(QListView.Flow) | 设置显示的方向 |
setGridSize(QSize) | 设置数据项的尺寸 |
setItemAlignment(Qt.Alignment) | 设置对齐方式 |
setLayoutMode(QListView.LayoutMode) | 设置数据的显示方式 |
setBatchSize(int) | 设置批量显示的数量,默认为100 |
setMovement(QListView.Movement) | 设置数据项的移动方式 |
setResizeMode(QListView.ResizeMode) | 设置尺寸调整模式 |
setrootIndex(QModelIndex) | 设置根目录的数据项索引 |
setRowHidden(int,bool) | 设置是否隐藏 |
setSpacing(int) | 设置数据项之间的间距 |
setUniformItemSizes(bool) | 设置数据项是否统一尺寸 |
setViewMode(QListView.ViewMode) | 设置显示模式 |
setWordWrap(bool) | 设置单词是否可以写到两行上 |
setWrapping(bool) | 设置文本是否可以写到两行 |
setAlternatingRowColors() | 设置是否用交替颜色 |
setSelectionMode(QAbstractItemView.SelectionMode) | 设置选择模式 |
setSelectionModel(QItemSelectionModel) | 设置选择模型 |
selectionModel() | 获取选择模型 |
setPositionForIndex(position:QPoint,index:QModelIndex) | 将指定索引的项放到指定位置处 |
-
用setModel(QAbstractItemModel)方法可以给列表视图控件设置关联的数据模型,
- 用setrootIndex(QModelIndex)方法设置列表视图控件
- 需要显示的数据索引下的子数据项如果数据项由多列构成则用setModelColumn(int)方法设置数据模型中要显示的列。
-
用selectedIndexes()方法取选中的数据项的行索引 List[int];
- 用setCurrentIndex(QModelIndex)方法设置当前的模型数据索引;
- 用currentIndex()方法获取当前项的模型数据索引;用
- indexAt(QPoint)方法获取指定位置处的数据项的模型数据索引。
-
用setFlow(QListView.Flow)方法设置数据项的排列方向,其中 QListView.Flow可以取:
- QListView,LeftToRight(值是0)
- QListView.TopToBottom(值是1)。
-
用setLayoutMode(QListView.LayoutMode)方法设置数据的显示方式,其中QListView.LayoutMode 可取:
- QListView.SinglePass(值是0,全部显示)
- QListView,Batched(值是1,分批显示);
- 用setBatchSize(int)方法设置分批显示的个数。
-
用setMovement(QListView.Movement)方法设置数据项的拖拽方式,其中QListView.Movement 可取:
- QListView.Static(不能移动)
- QListView.Free(可以自由移动)
- QListView.Snap(捕捉到数据项的位置)。
-
用setViewMode(QListView.ViewMode)方法设置显示模式,参数 QListViewViewMode
- 如果取QListView.ListMode,则采用QListView.TopToBottom 排列小尺寸和QListView.Static不能移动方式;
- 如果取 QListView.IconMode,则采用QListView.LeftToRight 排列、大尺寸和QListView.Free自由移动方式。
-
用setResizeMode(QListView.ResizeMode)方法设置尺调整模式,参数可取QListView.Fixed或QListView.Adjust。
-
用setSelectionMode(QAbstractItemView.SelectionMode)方法可以设置选择模式其中参数QAbstractItemView.SelectionMode 的取值如表所示
QAbstractItemView.SelectionMode的取值 值 说 明 QAbstractItemView.NoSelection 0 禁止选择 QAbstractItemView.SingleSelection 1 单选,当选择一个数据项时,其他任何已经选中的数 据项都变成未选中项 QAbstractItemView.MultiSelection 2 多选,当单击一个数据项时,将改变选中状态,其他还 未单击的数据项状态不变 QAbstractItemView.ExtendedSelection 3 当单击某数据项时,清除已选择的数据项;当按住 Ctrl键选择时,会改变被单击数据项的选中状态;当 按住 Shift 键选择两个数据项时,这两个数据项之间 的数据项的选中状态发生改变 QAbstractItemView.ContiguousSelection 4 当单击一个数据项时,清除已经选择的项;当按住 Shift 键或Ctrl键选择两个数据项时,这两个数据项 之间的选择状态发生改变
绑定模型和初始化数据
QListView 需要绑定模型,这里使用 QStringListModel。QStringListModel 是一个可编辑模型,提供了可编辑模型的所有标准功能,可以用于在视图小部件中显示多个字符串的简单情况,如 QListView或OComboBox。
模型既可以在实例化时传递字符串列表初始化数据,也可以使用 setStringList0函数设置字符串,而绑定模型使用 setModel()函数。示例如下:
self.listView = QListView()
self.model = QStringListModel()
self.model.setStringList(['row'+str(i)for i in range(6)])
self.listView.setModel(self.model)
模型在实例化时传递字符串列表也是可以的。示例如下
self.model = QStringListModel(['row'+str(i)for i in range(6)])
增、删、改、查、移
关于数据的操作要通过模型来完成,这里介绍的是 QStringListModel 的相关函数。
- 使用 index()函数可以获取 item 对应的模型索引;
- 使用 flags()函数可以获取 item 的flag 信息,该信息决定了 item 是否可以被选择、编辑及交互等。如果 item 的 flag 为Qt.NoItemFlags,那么该 item 将无法被选择、编辑、拖动等,更详细的信息请参考QListWidget的相关内容。
- data()函数用于获取项目数据,setData()函数用于设置数据,
- insertRows()函数用于插入行,removeRows()函数用于删除行,
- rowCount()函数用于获取列表的长度,
- stringList()函数用于获取字符串列表的内容,
- moveRow()函数用于移动行。
列表视图布局
视图布局功能由QListView 提供,这里介绍QListView 的相关函数viewMode 属性决定了列表视图的显示模式,有两种模式:
- 在 ListMode(默认)中项目以简单列表的形式显示;
- 在IconMode 中,视图采用图标视图的形式,其中项目与文件管理器中的文件等图标一起显示。
可以使用 setViewMode()函数修改视图模式,该函数的参数如表所示。
参 数 | 值 | 描述 |
---|---|---|
QListView.ListMode | 0 | 使用TopToBottom流布局,尺寸小,静态移动 |
QListView.IconMode | 1 | 使用LeftToRight流布局,尺寸大,自由移动 |
不同的布局模式对应不同的布局流,这个属性可以使用 flow()函数获取,也可以使用seflow()函数手动布局,如表所示:
参 数 | 值 | 描 述 |
---|---|---|
QListView.LeftToRight | 0 | 项目在视图中从左到右排列。如果isWrapping的属性为True(默认为 False),则布局将在到达可见区域的右侧时换行 |
QListView.TopToBottom | 1 | 项目在视图中从上到下排列。如果isWrapping的属性为True(默认为 False),则布局将在到达可见区域的底部时进行环绕 |
resizeMode 属性决定了调整视图大小时是否需要重新布置项目。可以用setResizeMode0函数修改默认设置,该函数的参数如表所示
- 如此属性为Adiust,则在调整视图大小时将重新布置项目。
- 如果此属性为 Fixed,则只会在第 1 次调整视图大小时布置项目,后续不会重复布置。在默认情况下,resizeMode 属性设置为 Fixed。
参 数 | 值 | 描 述 |
---|---|---|
QListView.Fixed | 0 | 这些项目只会在第1次显示视图时布置 |
QListView.Adjust | 1 | 每次调整视图大小时都会布置项目 |
layoutMode属性保存了项目的布局模式。当模式为SinglePass(默认)时,项目将一次性全部布局。当模式为 Batched 时,项目以 batchSize 的批次布局,同时处理事件。可以使用setLayoutMode0函数修改默认设置,该函数的参数如表所示:
参 数 | 值 | 描 述 |
---|---|---|
QListView.SinglePass | 0 | 所有项目一次性展示出来 |
QListView.Batched | 1 | 项目分批展示 |
movement 属性决定了项目是否可以自由移动、对齐到网格或根本不能移动。此属性确定用户如何移动视图中的项目。如果值为 Static(默认)则意味着用户不能移动项目。如果值为 Free 则意味着用户可以将项目拖到视图中的任何位置。如果值为 Snap 则意味着用户可以拖放项目,但只能拖到由 gridSize 属性表示的名义网格中的位置。可以使用setMovement0函数修改默认设置,该函数的参数如表所示。
参 数 | 值 | 描 述 |
---|---|---|
QListView.Static | 0 | 用户不能移动项目 |
QListView.Free | 1 | 项目可以由用户自由移动 |
QListView.Snap | 2 | 项目在移动时对齐到指定的网格,可以参考setGridSize) |
spacing 属性决定了布局中项目周围的空间大小,在默认情况下,此属性的值为 0,可以使用 setSpacing0函数修改默认设置。
gridSize 属性决定了项目所在的网格大小,默认值为 None,表示没有网格并且布局不是在网格中完成的。修改默认值会开启网格布局功能(当开启网格布局功能时,spacing属性将被忽略)。可以使用 setGridSize(size:QSize)修改默认设置
iconSize 属性决定了项目的图标大小,使用 setIconSize0函数可以修改默认值uniformItemSizes属性决定了列表视图中的项目是否具有相同的大小,默认为False可以通过setUniformItemSizes0函数设置为True,在这种情况下对性能有一定的优化,适用于显示大量数据。
selectedIndexes0函数返回选中的项目 index,selectA11 表示全选,clearSelection 表示取消选择。
列表视图控件QListView的信号
列表视图控件QListView的信号如表所示
QListView的信号及参数类型 | 说明 |
---|---|
activated(QModelIndex) | 数据项活跃时发送信号 |
clicked(QModelIndex) | 单击数据项时发送信号 |
doubleClicked(QModelIndex) | 双击数据项时发送信号 |
entered(QModelIndex) | 光标进入数据项时发送信号 |
iconSizeChanged(QSize) | 图标尺寸发生变化时发送信号 |
indexesMoved(List[QModelIndex]) | 数据索引发生移动时发送信号 |
pressed(QModelIndex) | 按下鼠标按键时发送信号 |
viewportEntered() | 光标进人视图时发送信号 |
文本列表模型OStringListModel和列表视图控件QListView 的应用实例
这个案例创建了两个OListView,上面是默认的ListMode显示,下面是IconMode 显示。绝大部分操作都基于前者,后者作为对照组,当前者发生变化时后者会自动改变,这也体现了使用模型/视图框架的优越性,不用维护两套数据
# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/15 13:42
# File_name: 04-QListView例子.py
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
import sys
import os
os.chdir(os.path.dirname(__file__))
class QListViewDemo(QWidget):
addCount = 0
insertCount = 0
def __init__(self,parent=None):
super(QListViewDemo,self).__init__(parent)
self.setWindowTitle("QListView案例")
self.text = QPlainTextEdit('用来显示QListView相关信息:')
self.listView = QListView()
self.model = QStringListModel(['row'+ str(i)for i in range(6)])
# self.model.setStringList(['row'+str(i)for i in range(6)])
self.listView.setModel(self.model)
# 作为对照组
self.listView2 = QListView()
self.listView2.setModel(self.model)
self.listView2.setMaximumHeight(80)
# 增删
self.buttonDelete = QPushButton('删除')
self.buttonAdd = QPushButton('增加')
self.buttonUp = QPushButton('上移')
self.buttonDown = QPushButton('下移')
self.buttonInsert = QPushButton('插入')
layoutH = QHBoxLayout()
layoutH.addWidget(self.buttonAdd)
layoutH.addWidget(self.buttonInsert)
layoutH.addWidget(self.buttonUp)
layoutH.addWidget(self.buttonDown)
layoutH.addWidget(self.buttonDelete)
self.buttonAdd.clicked.connect(self.onAdd)
self.buttonInsert.clicked.connect(self.onInsert)
self.buttonUp.clicked.connect(self.onUp)
self.buttonDown.clicked.connect(self.onDown)
self.buttonDelete.clicked.connect(self.onDelete)
# 选择
self.buttonSelectAll = QPushButton('全选')
self.buttonSelectClear = QPushButton('清除选择')
self.buttonSelectOutput = QPushButton('输出选择')
layoutH2 = QHBoxLayout()
layoutH2.addWidget(self.buttonSelectAll)
layoutH2.addWidget(self.buttonSelectClear)
layoutH2.addWidget(self.buttonSelectOutput)
self.buttonSelectAll.clicked.connect(self.onSelectAll)
self.buttonSelectClear.clicked.connect(self.onSelectClear)
self.buttonSelectOutput.clicked.connect(self.onSelectOutput)
layout = QVBoxLayout(self)
layout.addWidget(self.listView)
layout.addLayout(layoutH)
layout.addLayout(layoutH2)
layout.addWidget(self.text)
layout.addWidget(self.listView2)
self.setLayout(layout)
# selection
# self.listWidget.setSelectionMode(QAbstractItemView.SingleSelection)
self.listView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.listView.setSelectionBehavior(QAbstractItemView.SelectRows)
# 上下文菜单
self.menu = self.generateMenu()
self.listView.setContextMenuPolicy(Qt.CustomContextMenu)######允许右键产生子菜单
self.listView.customContextMenuRequested.connect(self.showMenu)####右键菜单
# 列表视图布局
self.listView.setResizeMode(self.listView.ResizeMode.Adjust)
self.listView.setLayoutMode(self.listView.LayoutMode.Batched)
self.listView.setMovement(self.listView.Movement.Snap)
self.listView.setUniformItemSizes(True)
self.listView.setGridSize(QSize(10,20))
self.listView2.setViewMode(self.listView.ViewMode.IconMode)
self.listView2.setSpacing(1)
self.listView2.setFlow(self.listView2.Flow.LeftToRight)
self.listView2.setIconSize(QSize(2,3))
def generateMenu(self):
menu = QMenu(self)
menu.addAction('增加',self.onAdd,QKeySequence(Qt.CTRL | Qt.Key_N))
menu.addAction('插入',self.onInsert,QKeySequence(Qt.CTRL | Qt.Key_I))
menu.addAction(QIcon("images/close.png"),'删除',self.onDelete,QKeySequence(Qt.CTRL | Qt.Key_D))
menu.addSeparator()
menu.addAction('全选',self.onSelectAll,QKeySequence(Qt.CTRL | Qt.Key_A))
menu.addAction('清空选择',self.onSelectClear,QKeySequence(Qt.CTRL | Qt.Key_R))
menu.addAction('输出选择',self.onSelectOutput)
menu.addSeparator()
# menu.addAction(self.actionHelp)
return menu
def showMenu(self,pos):
self.menu.exec(QCursor.pos())# 显示菜单
def contextMenuEvent(self,event):
menu = QMenu(self)
menu.addAction('选项1')
menu.addAction('选项2')
menu.addAction('选项3')
menu.exec(event.globalPos())
def onAdd(self):
self.addCount += 1
text = f'新增-{self.addCount}'
num = self.model.rowCount()
self.model.insertRow(num)
index = self.model.index(num)
self.model.setData(index,text)
self.text.appendPlainText(f'新增item:"{text}"')
def onInsert(self):
self.insertCount += 1
index = self.listView.currentIndex()
row = index.row()
text = f'插入-{self.insertCount}'
self.model.insertRow(row)
self.model.setData(index,text)
self.text.appendPlainText(f'row:{row},新增item:"{text}"')
def onUp(self):
index = self.listView.currentIndex()
row = index.row()
if row > 0:
self.model.moveRow(QModelIndex(),row,QModelIndex(),row - 1)
def onDown(self):
index = self.listView.currentIndex()
row = index.row()
if row <= self.model.rowCount()- 1:
self.model.moveRow(QModelIndex(),row + 1,QModelIndex(),row)
def onDelete(self):
index = self.listView.currentIndex()
text = self.model.data(index)
row = index.row()
self.model.removeRow(row)
self.text.appendPlainText(f'row:{row},删除item:"{text}"')
def onSelectAll(self):
self.listView.selectAll()
def onSelectClear(self):
self.listView.clearSelection()
def onSelectOutput(self):
indexList = self.listView.selectedIndexes()
for index in indexList:
row = index.row()
data = self.model.data(index)
self.text.appendPlainText(f'row:{row},data:{data}')
if __name__ =="__main__":
app = QApplication(sys.argv)
demo = QListViewDemo()
demo.show()
sys.exit(app.exec())
下面的程序建立两个QListView 控件,并分别关联两个 QStringListModel。
程序初始从Excel文件"学生 IDxlsx"中的ID工作页中读取学生名单,在学生名单中选择学生姓名后,单击"添加"按钮,数据会从学生名单中删除,并移到三好学生中;
单击"删除"按钮,数据会从三好学生中移到学生名单中,并插人到原来的位置。左侧选择1个或多个学生姓名,右侧只有 1个选中时,可以使用"插人"按钮。
程序运行界面如图所示
# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/6 0:22
# File_name: 02-文本列表模型OStringListModel和列表视图控件QListView 的应用实例.py
import sys,os
from PySide6.QtWidgets import QApplication,QWidget,QListView,QHBoxLayout,QLabel,QPushButton,QVBoxLayout
from PySide6.QtCore import QStringListModel,QModelIndex,Qt
from openpyxl import load_workbook
class MyWindow(QWidget):
def __init__(self,parent=None):
super().__init__(parent)
self.fileName ="./学生ID.xlsx"
self.reference_Model = QStringListModel(self)# 从 Excel中读数据存储数据的模型
self.selection_Model = QStringListModel(self)# 选择数据后,存储选择数据的模型
self.setup_Ui()# 建立界面
self.data_import()# 从Excel中读取数据
self.view_clicked()# 单击视图控件,判断按钮是否激活或失效
def setup_Ui(self):
label1 = QLabel("学生名单")
self.listView_1 = QListView()# 列表视图控件,显示Exce1 中的数据的控件
v1 = QVBoxLayout()
v1.addWidget(label1)
v1.addWidget(self.listView_1)
label2 = QLabel("三好学生")
self.listView_2 = QListView()# 列表视图控件,显示选中的数据
self.btn_add = QPushButton("添加")
self.btn_insert = QPushButton("插人")
self.btn_delete = QPushButton("删除")
h1 = QHBoxLayout()
h1.addWidget(self.btn_add)
h1.addWidget(self.btn_insert)
h1.addWidget(self.btn_delete)
v2 = QVBoxLayout()
v2.addWidget(label2)
v2.addWidget(self.listView_2)
v2.addLayout(h1)
h2 = QHBoxLayout(self)
h2.addLayout(v1)
h2.addLayout(v2)
self.listView_1.setModel(self.reference_Model)# 设置模型
self.listView_2.setModel(self.selection_Model)# 设置模型
self.listView_1.setSelectionMode(QListView.SelectionMode.ExtendedSelection)# 设置选择模式
self.listView_2.setSelectionMode(QListView.SelectionMode.ExtendedSelection)# 设置选择模式
self.btn_add.clicked.connect(self.btn_add_clicked)
self.btn_insert.clicked.connect(self.btn_insert_clicked)
self.btn_delete.clicked.connect(self.btn_delete_clicked)
self.listView_1.clicked.connect(self.view_clicked)
self.listView_2.clicked.connect(self.view_clicked)
def data_import(self):
if os.path.exists(self.fileName):
wbook = load_workbook(self.fileName)
if"ID"in wbook.sheetnames:
wsheet = wbook["ID"]
cell_range = wsheet[wsheet.dimensions] # 取 Excel中数据存储范围
student = list()
for cell_row in cell_range:
string =""
for cell in cell_row: # cell_row.Excel行单元格元组
string = string + str(cell.value)+""# 取Excel单元格中数据
student.append(string.strip())
self.reference_Model.setStringList(student)# 在模型中添加数据列表
def btn_add_clicked(self): # 添加按钮的槽函数
while len(self.listView_1.selectedIndexes()):
selectedIndexes = self.listView_1.selectedIndexes()
index = selectedIndexes[0]
string = self.reference_Model.data(index,Qt.DisplayRole)# 获取数据
self.reference_Model.removeRow(index.row(),QModelIndex())
count = self.selection_Model.rowCount()# 获取行的数量
self.selection_Model.insertRow(count)# 在末尾插人数据
last_index = self.selection_Model.index(count,0,QModelIndex())# 获取末尾索引
self.selection_Model.setData(last_index,string,Qt.DisplayRole)# 设置末尾的数据
self.view_clicked()# 控制按钮的激活与失效
def btn_insert_clicked(self):
while len(self.listView_1.selectedIndexes()):
selectedIndexs_1 = self.listView_1.selectedIndexes()# 获取选中数据项的索引
selectedIndex_2 = self.listView_2.selectedIndexes()# 获取选中数据项的索引
index = selectedIndexs_1[0]
string = self.reference_Model.data(index,Qt.DisplayRole)
self.reference_Model.removeRow(index.row(),QModelIndex())
row = selectedIndex_2[0].row()
self.selection_Model.insertRow(row)
index = self.selection_Model.index(row)
self.selection_Model.setData(index,string,Qt.DisplayRole)
self.view_clicked()# 控制按钮的激活与失效
def btn_delete_clicked(self): # 删除按钮的槽函数
while len(self.listView_2.selectedIndexes()):
selectedIndexes = self.listView_2.selectedIndexes()
index = selectedIndexes[0]
string = self.selection_Model.data(index,Qt.DisplayRole)
self.selection_Model.removeRow(index.row(),QModelIndex())
count = self.reference_Model.rowCount()
self.reference_Model.insertRow(count)
last_index = self.reference_Model.index(count,0,QModelIndex())# 获取末尾索引
self.reference_Model.setData(last_index,string,Qt.DisplayRole)
self.view_clicked()
self.reference_Model.sort(0)# 排序
def view_clicked(self):
n1 = len(self.listView_1.selectedIndexes())# 获取选中数据项的数量
n2 = len(self.listView_2.selectedIndexes())# 获取选中数据项的数量
self.btn_add.setEnabled(n1)
self.btn_insert.setEnabled(n1 and n2 == 1)
self.btn_delete.setEnabled(n2)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec())