3.8 QListWidget和QToolButton
3.8.1 功能概述
PyQt5中用于项(Item)处理的组件有两大类:一类是Item Views,包括QListView、QTreeView、QTableView、QColumnView等;另一类是Item Widgets,包括QListWidget、QTreeWidget、QTableWidget。
Item Views基于模型/视图(Model/View)结构,视图(View)与模型数据(Model Data)关联实现数据的显示和编辑,模型/视图结构的使用在第4章详细介绍。
Item Widgets直接将数据存储在每一个项里,例如,QListWidget的每行是一个项,QTreeWidget的每个节点是一个项,QTableWidget的每个单元格是一个项。一个项存储了文字、文字的格式、自定义数据等。
Item Widgets是GUI设计中常用的组件,也是功能稍微复杂一点的组件。本节通过示例Demo3_8先介绍QListWidget以及其他一些组件的用法,后面两节再分别介绍QTreeWidget和QTableWidget。示例Demo3_8运行时界面如图3-16所示。
图3-16 示例Demo3_8运行时界面
本示例不仅介绍QListWidget的使用,还介绍如下一些功能的实现。
· 使用QTabWidget设计多页界面,工作区右侧是一个有3个页面的TabWidget组件。
· 使用QToolBox设计分组工具箱,工作区左侧是一个有3个分组的ToolBox组件。
· 使用分割条(QSplitter)设计可以左右分割的界面,工作区的ToolBox组件和TabWidget组件之间有一个水平分割条,运行时可以分割调整两个组件的大小。
· 创建Action,用Action设计主工具栏。
· 使用QToolButton按钮,设置与Action关联,设计具有下拉菜单功能的ToolButton按钮,在主工具栏上添加具有下拉菜单的ToolButton按钮。
· 使用QListWidget,演示如何创建和添加项、为项设置图标和复选框、如何遍历列表进行选择。
· 介绍QListWidget的主要信号currentItemChanged()的功能,编写响应槽函数。
· 为ListWidget组件利用已设计的Action创建自定义快捷菜单。
示例Demo3_8是从mainWindowApp项目模板创建的。窗体业务逻辑类的实现文件myMainWindow.py的import部分,以及QmyMainWindow的构造函数部分的代码如下(省略了窗体测试部分的代码):
import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QListWidgetItem, QMenu, QToolButton) from PyQt5.QtGui import QIcon, QCursor from PyQt5.QtCore import pyqtSlot, Qt from ui_MainWindow import Ui_MainWindow class QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui=Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI self.setCentralWidget(self.ui.splitter) self.__setActionsForButton() self.__createSelectionPopMenu() self.__FlagEditable =(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsEditable) self.__FlagNotEditable =( Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
构造函数调用了两个自定义函数,其中__setActionsForButton()为窗体上的ToolButton按钮设置关联的Action, __createSelectionPopMenu()创建工具栏上按钮的用于列表项选择的下拉菜单。
最后定义了两个私有变量self.__FlagEditable和self.__FlagNotEditable,它们用于创建列表项时设置项的标志,表示可编辑和不可编辑的项。定义为变量后便于在后面重复使用。
3.8.2 窗体可视化设计
1.窗体可视化设计完成效果
窗体MainWindow.ui可视化设计完成后的界面效果如图3-17所示。工具栏上的按钮是用设计好的Action创建的,其他按钮都使用QToolButton组件,在设计时只为这些按钮命名并设置一些属性,图中按钮上显示的文字就是按钮的objectName。
图3-17 可视化设计时完成的窗体界面
QToolButton有一个setDefaultAction()函数,可以使其与一个Action关联,按钮的文字、图标、ToolTip都将自动设置为与关联的Action一致,单击一个QToolButton按钮就会执行Action的槽函数,与工具栏上的按钮一样。实际上,主工具栏上的按钮就是根据Action自动创建的QToolButton按钮。
QToolButton还有一个setMenu()函数,可以为其设置一个下拉式菜单,配合QToolButton的一些属性设置,可以有不同的下拉菜单效果。在图3-16中,工具栏上的“项选择”按钮直接显示下拉菜单,而在列表框上方的“项选择”按钮,只有单击右侧的向下箭头才弹出下拉菜单,直接单击按钮会执行按钮关联的Action的槽函数。
要实现图3-16的运行时的窗体界面效果,还需要用代码完成部分界面创建和设置,这就是QmyMainWindow类的构造函数中调用的两个自定义函数实现的功能,主要就是为界面上的各ToolButton按钮设置关联的Action,在工具栏上动态添加一个ToolButton按钮,并设置其下拉菜单功能。
2.工具箱(QToolBox)组件
在主窗体工作区的左侧是一个QToolBox组件。在ToolBox组件上调出右键快捷菜单,可以使用“Insert Page”“Delete Page”等菜单项实现分组的添加或删除。在属性编辑器里可以设置如下常用属性的值。
· currentIndex:当前分组的编号,第一个分组的编号是0,通过改变这个值,可以选择不同的分组页面。
· currentItemText:当前分组的标题。
· currentItemName:当前分组的对象名称。
· currentItemIcon:为当前分组设置一个图标,显示在文字标题的左侧。
在一个ToolBox内可以放置任何界面组件,如QGroupBox、QLineEdit、QPushButton等。在图3-17里,第一个分组里放置了几个QToolButton按钮,并设置为网格状布局。注意不要使用水平布局,因为使用水平布局时组内的ToolButton按钮都是自动左对齐,而使用网格状布局则是自动居中。
3.多页(QTabWidget)组件
QTabWidget是一个多页的容器类组件。在窗体上放置一个QTabWidget组件,通过其快捷菜单的“Insert Page”“Delete Page”等菜单项实现页面的添加或删除。在属性编辑器里可以设置如下一些常用属性的值。
· tabPosition:页标签的位置,东西南北四个方位中选择一个。
· currentIndex:当前页的编号。
· currentTabText:当前页的标题。
· currentTabName:当前页的对象名称。
· currentTabIcon:可以为当前页设置一个图标,显示在文字标题的左侧。
· tabsClosable:页面是否可以被关闭。若设置为True,则每个页面的标题栏上会出现一个关闭按钮,点击关闭按钮可以关闭页面。
4.使用QSplitter设计分割界面
具有分割效果的典型界面是Windows的资源管理器,QSplitter用于设计具有分割效果的界面,可以左右或上下分割。
本示例主窗体工作区的两个主要组件是toolBox和tabWidget,希望这两个组件设计为左右分割的效果。同时选中这两个组件,单击主窗体工具栏上的“Lay Out Horizontally in Splitter”按钮,就可以为这两个组件创建一个水平分割的布局组件splitter。
在QmyMainWindow的构造函数里使用下面一行语句就可以使splitter充满整个工作区:
self.setCentralWidget(self.ui.splitter)
在使用分割条调整大小时,如果不希望toolBox的宽度变得太小而影响按钮的显示,可以通过设置toolBox的minimumSize.Width属性设置一个最小宽度。
5.QListWidget组件
在TabWidget组件的第一个页面上放置一个QListWidget组件,以及其他几个按钮和编辑框,组成如图3-17所示的界面。QListWidget是存储多个项的列表组件,每个项是一个QListWidgetItem类型的对象。
在窗体可视化设计时双击ListWidget组件,可以打开其列表项编辑器,如图3-18所示。在这个编辑器里可以增加、删除、上移、下移列表项,可以设置每个项的属性,包括文字内容、字体、文字对齐方式、背景色、前景色等。
图3-18 QListWidget组件的列表项编辑器
比较重要的是其flags属性(如图3-18所示),用于设置项的一些标志,以下这些标志是枚举类型Qt.ItemFlag的值的组合。
· Selectable:项可被选择,对应枚举值Qt.ItemIsSelectable。
· Editable:项可被编辑,对应枚举值Qt.ItemIsEditable。
· DragEnabled:项可以被拖动,对应枚举值Qt.ItemIsDragEnabled。
· DropEnabled:项可以接收拖放的项,对应枚举值Qt.ItemIsDropEnabled。
· UserCheckable:项可以被复选,若为True,项前面出现一个CheckBox,对应枚举值Qt.ItemIsUserCheckable。
· Enabled:项被使能,对应枚举值Qt.ItemIsEnabled。
· Tristate:允许Check的第三种状态,若为False,则只有checked和unchecked两种状态,对应枚举值Qt.ItemIsAutoTristate。
QListWidget的列表项一般是在程序里动态创建,后面会演示如何用程序完成添加、删除列表项等操作。
6.创建Action
本示例采用Action设计工具栏,并且将Action用于QToolButton按钮。创建的Action列表如图3-19所示。利用这些Action创建主工具栏按钮,设计时完成的主工具栏如图3-17所示。
图3-19 本示例创建的Action
actSelPopMenu用于“项选择”的ToolButton按钮,也就是窗体上具有下拉菜单的两个按钮。将actSelPopMenu的功能设置为与actSel_Invs(“反选”)完全相同,在信号与槽编辑器里设置这两个Action关联(如图3-20所示),这样,执行actSelPopMenu就相当于执行actSel_Invs。
图3-20 在信号与槽编辑器中设置的关联
3.8.3 QToolButton与下拉式菜单
1.QToolButton关联QAction
在图3-17所示的界面上,在ToolBox里放置了几个ToolButton按钮,希望它们实现工具栏上的按钮完成的功能;列表框上方放置了几个ToolButton按钮,希望它们完成列表项选择的功能。这些功能都已经有相应的Action实现,要让ToolButton按钮实现这些功能,无须再为其编写代码,只需设置一个关联的QAction对象即可。
QToolButton有一个函数setDefaultAction(),其函数原型为:
setDefaultAction(self, QAction)
使用setDefaultAction()函数为一个ToolButton按钮设置一个Action之后,将自动获取Action的文字、图标、ToolTip等设置作为按钮的相应属性,所以,在界面设计时无须为ToolButton按钮做过多的设置。
在QmyMainWindow类里定义一个私有函数__setActionsForButton()用于为界面上的ToolButton按钮设置关联的Action,并在构造函数里调用,其代码如下:
def __setActionsForButton(self): ##为ToolButton按钮设置Action self.ui.btnList_Ini.setDefaultAction(self.ui.actList_Ini) self.ui.btnList_Clear.setDefaultAction(self.ui.actList_Clear) self.ui.btnList_Insert.setDefaultAction(self.ui.actList_Insert) self.ui.btnList_Append.setDefaultAction(self.ui.actList_Append) self.ui.btnList_Delete.setDefaultAction(self.ui.actList_Delete) self.ui.btnSel_ALL.setDefaultAction(self.ui.actSel_ALL) self.ui.btnSel_None.setDefaultAction(self.ui.actSel_None) self.ui.btnSel_Invs.setDefaultAction(self.ui.actSel_Invs)
在程序启动后,界面上的ToolButton按钮自动根据关联的Action设置其按钮文字、图标和ToolTip。单击某个ToolButton按钮,就执行其关联的Action的槽函数代码。使用Action集中设计功能代码,然后用于菜单、工具栏、ToolButton的设计,是避免重复编写代码的一种方式。
2.为ToolButton按钮设计下拉菜单
还可以为ToolButton按钮设计下拉菜单,在图3-16的运行时窗口中,单击工具栏上的“项选择”按钮,会在按钮的下方弹出一个菜单,有3个菜单项用于项选择。
在QmyMainWindow类里定义一个私有函数__createSelectionPopMenu()用于创建按钮的下拉菜单,并在构造函数里调用,其代码如下:
def __createSelectionPopMenu(self): ##创建ToolButton按钮的下拉菜单 menuSelection=QMenu(self) #下拉菜单 menuSelection.addAction(self.ui.actSel_ALL) menuSelection.addAction(self.ui.actSel_None) menuSelection.addAction(self.ui.actSel_Invs) ##listWidget上方的btnSelectItem按钮 self.ui.btnSelectItem.setPopupMode(QToolButton.MenuButtonPopup) ## self.ui.btnSelectItem.setPopupMode(QToolButton.InstantPopup) self.ui.btnSelectItem.setToolButtonStyle( Qt.ToolButtonTextBesideIcon) self.ui.btnSelectItem.setDefaultAction(self.ui.actSelPopMenu) self.ui.btnSelectItem.setMenu(menuSelection) #设置下拉菜单 ##工具栏上的下拉式菜单按钮 toolBtn=QToolButton(self) toolBtn.setPopupMode(QToolButton.InstantPopup) toolBtn.setDefaultAction(self.ui.actSelPopMenu) toolBtn.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) toolBtn.setMenu(menuSelection) #设置下拉菜单 self.ui.mainToolBar.addWidget(toolBtn) ##工具栏添加分隔条和"退出"按钮 self.ui.mainToolBar.addSeparator() self.ui.mainToolBar.addAction(self.ui.actQuit)
这段代码首先创建一个QMenu对象menuSelection,将3个用于选择列表项的Action添加作为菜单项。
使用QToolButton的setPopupMode()函数为一个ToolButton按钮的下拉式菜单设置不同的弹出方式,此函数的原型是:
setPopupMode(self, mode)
参数mode是枚举类型QToolButton.ToolButtonPopupMode,有以下两种模式。
· QToolButton.MenuButtonPopup模式:在这种模式下,按钮右侧有一个向下的小箭头,必须单击这个小箭头才会弹出下拉菜单,如果直接单击按钮会执行按钮关联的Action,而不会弹出下拉菜单。
· QToolButton.InstantPopup模式:在这种模式下,按钮右下角有一个向下的小箭头,单击按钮时直接弹出下拉菜单,按钮关联的Action不会被触发。
创建好菜单后,用QToolButton的setMenu()函数为一个ToolButton按钮指定下拉菜单。
在QmyMainWindow的构造函数里执行了__setActionsForButton()函数和__createSelectionPop Menu()函数,程序启动后才具有图3-16的运行时界面效果。
3.8.4 QListWidget的操作
1.初始化列表
actList_Ini(“初始化列表”)实现listWidget的列表项初始化,其槽函数代码如下:
@pyqtSlot() ##初始化列表 def on_actList_Ini_triggered(self): icon = QIcon(":/icons/images/724.bmp") editable=self.ui.chkBoxList_Editable.isChecked() if (editable == True): Flag=self.__FlagEditable #可编辑 else: Flag=self.__FlagNotEditable #不可编辑 self.ui.listWidget.clear() #清除列表 for i in range(10): itemStr="Item %d"%i aItem=QListWidgetItem() aItem.setText(itemStr) aItem.setIcon(icon) aItem.setCheckState(Qt.Checked) aItem.setFlags(Flag) #项的flags self.ui.listWidget.addItem(aItem)
列表框里一行是一个项(item),每个项是一个QListWidgetItem类型的对象,如果要向列表框添加一个项,需要先创建一个QListWidgetItem类型的实例aItem,然后设置aItem的一些属性,再用QListWidget的addItem()函数将该aItem添加到列表框里。
QListWidgetItem有许多函数方法,可以设置项的很多属性,例如设置文字、图标、选中状态,还可以设置flags,这些函数方法就是图3-18对话框里设置功能的代码化。
2.插入项和添加项
插入项使用QListWidget的insertItem()函数,它有两种函数原型,第一种是:
insertItem(self, row, itemText)
这个insertItem()函数在第row行前面插入项,项的标题由str型参数itemText指定,QListWidget将自动为这个项创建QListWidgetItem对象,但无法做更多的属性设置。
另一种函数原型是:
insertItem(self, row, item)
其功能是在第row行前面插入一个QListWidgetItem对象item,需要先创建这个item,并设置其属性。actList_Insert(“插入项”)实现这个功能,其槽函数代码如下:
@pyqtSlot() ##插入一项 def on_actList_Insert_triggered(self): icon = QIcon(":/icons/images/724.bmp") editable=self.ui.chkBoxList_Editable.isChecked() if (editable == True): Flag=self.__FlagEditable #可编辑 else: Flag=self.__FlagNotEditable #不可编辑 aItem=QListWidgetItem() aItem.setText("Inserted Item") aItem.setIcon(icon) aItem.setCheckState(Qt.Checked) aItem.setFlags(Flag) #项的flags curRow=self.ui.listWidget.currentRow() #当前行 self.ui.listWidget.insertItem(curRow, aItem)
在列表末尾添加一项用QListWidget.addItem()函数,使用方法在初始化列表的代码里有演示。actList_Append是“添加项”的Action,其槽函数代码里就用到addItem()函数添加一个项,代码与槽函数on_actList_Insert_triggered()的基本相同,在此不再列出。
3.删除当前项和清空列表
删除当前项和清空列表的两个Action的槽函数代码如下:
@pyqtSlot() ##删除当前项 def on_actList_Delete_triggered(self): row=self.ui.listWidget.currentRow() self.ui.listWidget.takeItem(row) #移除当前项,Python自动删除 @pyqtSlot() ##清空列表 def on_actList_Clear_triggered(self): self.ui.listWidget.clear()
QListWidget.takeItem(int)函数只是移除一个项,并不删除项对象,但是Python有垃圾内存自动回收机制,所以无须手工删除移除的项。
4.遍历并选择项
界面上有“全选”“全不选”“反选”3个按钮,其功能由3个Action实现,用于遍历列表框里的项并设置选择状态。这3个Action的槽函数代码如下:
@pyqtSlot() ##全选 def on_actSel_ALL_triggered(self): for i in range(self.ui.listWidget.count()): aItem=self.ui.listWidget.item(i) aItem.setCheckState(Qt.Checked) @pyqtSlot() ##全不选 def on_actSel_None_triggered(self): for i in range(self.ui.listWidget.count()): aItem=self.ui.listWidget.item(i) aItem.setCheckState(Qt.Unchecked) @pyqtSlot() ##反选 def on_actSel_Invs_triggered(self): for i in range(self.ui.listWidget.count()): aItem=self.ui.listWidget.item(i) if (aItem.checkState() ! = Qt.Checked): aItem.setCheckState(Qt.Checked) else: aItem.setCheckState(Qt.Unchecked)
QListWidgetItem.setCheckState()函数设置列表项的复选状态,Qt.Checked和Qt.Unchecked是Qt中的枚举类型Qt.CheckState的两个值,分别表示选中和不选中。
5.QListWidget的常用信号
QListWidget在当前项切换时发射以下两个信号,传递的参数不同。
· currentRowChanged(int),传递当前项的行号作为参数。
· currentItemChanged(current, previous),两个参数都是QListWidgetItem对象,current表示当前项,previous表示前一项。
当前项的内容发生变化时发射信号currentTextChanged(str)。
为listWidget的currentItemChanged()信号编写槽函数,代码如下:
def on_listWidget_currentItemChanged(self, current, previous): strInfo="" if (current! =None): if (previous==None): strInfo="当前:"+current.text() else: strInfo="前一项:"+previous.text()+";当前项:"+current.text() self.ui.editCurItemText.setText(strInfo)
代码里需要判断current和previous是否为空,否则运行时可能出现访问错误。
3.8.5 创建右键快捷菜单
每个从QWidget继承的类都有信号customContextMenuRequested(),这个信号在鼠标右键单击时发射,为此信号编写槽函数,可以创建和运行右键快捷菜单。
本示例为组件listWidget的customContextMenuRequested()信号创建槽函数,实现快捷菜单的创建与显示,代码如下:
def on_listWidget_customContextMenuRequested(self, pos): ##右键快捷菜单 menuList=QMenu(self) #创建菜单 menuList.addAction(self.ui.actList_Ini) menuList.addAction(self.ui.actList_Clear) menuList.addAction(self.ui.actList_Insert) menuList.addAction(self.ui.actList_Append) menuList.addAction(self.ui.actList_Delete) menuList.addSeparator() menuList.addAction(self.ui.actSel_ALL) menuList.addAction(self.ui.actSel_None) menuList.addAction(self.ui.actSel_Invs) menuList.exec(QCursor.pos()) #显示菜单
在这段代码里,首先创建一个QMenu类型的对象menuList,然后利用QMenu的addAction()方法添加已经设计的Action作为菜单项。创建完菜单后,使用QMenu的exec()函数显示快捷菜单,即:
menuList.exec(QCursor.pos()) #显示菜单
这样会在鼠标当前位置显示弹出式菜单,类函数QCursor.pos()获得鼠标光标当前位置。快捷菜单的运行效果如图3-21所示。
图3-21 组件listWidget的右键快捷菜单的运行效果