常用控件4
菜单栏/菜单
菜单栏/菜单 相关的 Python QT 官方文档 链接如下:
需要菜单栏的,通常是 QMainWindow 类型的窗口, 我们可以使用代码创建菜单栏内容,如下
from PySide6 import QtWidgets
from PySide6.QtGui import QIcon
class Window(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.resize(600,200)
# 创建 菜单栏QMenuBar 对象 并返回
menuBar = self.menuBar()
# 一级菜单
fileMenu = menuBar.addMenu("文件")
editMenu = menuBar.addMenu("编辑")
helpMenu = menuBar.addMenu("帮助")
# 一级Action
actionHomePage = menuBar.addAction('主页')
actionHomePage.triggered.connect(self.actionHomePageClicked)
# 一级菜单的 action项
actionAddNode = fileMenu.addAction(QIcon("./Images/folder.png"),"添加")
fileMenu.addSeparator() # 分隔符
actionDelNode = fileMenu.addAction("删除")
actionAddNode.triggered.connect(self.actionAddNodeClicked)
actionDelNode.triggered.connect(self.actionDelNodeClicked)
# 二级菜单
edit_1 = editMenu.addMenu("插入图表")
edit_2 = editMenu.addMenu("插入图片")
# 二级菜单的 action项
action1 = edit_1.addAction("action1")
action2 = edit_1.addAction("action2")
def actionHomePageClicked(self):
print('actionHomePageClicked')
def actionAddNodeClicked(self):
print('actionAddNodeClicked')
def actionDelNodeClicked(self):
print('actionDelNodeClicked')
app = QtWidgets.QApplication()
ex = Window()
ex.show()
app.exec()
大家可以运行一下上面的代码,看一下界面。
菜单层级结构是这样的:
当然,菜单里面还可以有子菜单,就是这样
注意:上面说的子菜单,并不是一种新的类型,也是 QMenu
, 只是从属于其他Qmenu, 就像 QLayout
形成的层级关系一样。
菜单里面 点击能触发操作的条目,称之为 QAction
,中文叫 动作
也可以在 Qt Designer上很方便的为 QMainWindow
类型的窗口添加菜单,如下所示
点击 菜单Action, 会触发信号 triggered
, 处理点击菜单的的代码如下
您需要高效学习,找工作? 点击咨询 报名实战班
点击查看学员就业情况
工具栏
工具栏的 Python QT 官方文档 链接如下:
工具里面的可以点击的条目 也是 QAction
,和菜单栏一样!
可以用代码创建工具栏的内容,如下
from PySide6 import QtWidgets
from PySide6.QtGui import QIcon
class Window(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.resize(600,200)
# 创建 工具栏 对象 并添加
toolbar = QtWidgets.QToolBar(self)
self.addToolBar(toolbar)
# 添加 工具栏 条目Action
actionAddNode = toolbar.addAction(QIcon("./Images/folder.png"),"添加")
actionAddNode.triggered.connect(self.actionAddNodeClicked)
action = toolbar.addAction("删除")
action = toolbar.addAction("修改")
action = toolbar.addAction("查询")
toolbar.addSeparator() # 添加分隔符
action = toolbar.addAction("帮助")
def actionAddNodeClicked(self):
print('actionAddNodeClicked')
app = QtWidgets.QApplication([])
ex = Window()
ex.show()
app.exec()
如果 菜单栏 和 工具栏有 相同的 action
,可以公用一个Action对象。
也可以在 Qt Designer上很方便的为 QMainWindow
类型的窗口添加工具栏。
右键点击 Main Window
类型的窗体空白处,如下所示
选择添加工具栏
注意,只有 Main Window
类型的窗体,才能添加工具栏,如下
添加工具栏后,还要在工具栏上添加 条目Action
(中文称之为: 动作
)。
方法是点击右下角 动作编辑器
,新建动作,如下图所示
然后如下图所示进行设置
添加动作成功后,就可以直接拖到工具栏上了。
然后,在代码中定义动作触发后的处理函数,如下所示
如果菜单和工具栏有 相同的 action
,通常是先在 动作编辑器 创建一个action, 然后分别拖动到 菜单 和 工具栏
状态栏
状态栏通常显示在窗口底部,对应的控件类型是: QStatusBar
需要底部状态栏的,通常是 QMainWindow
类型的窗口, 用 Qt Designer 设计的Qt Designer, 会自带状态栏,缺省属性名称为 statusbar
。
要在状态栏显示文本信息,只需要调用 状态栏 QStatusBar 的 showMessage
方法,如下
提示框
QMessageBox
类可以用来弹出各种提示框
该类可以通过一系列静态方法,显示 如下弹出框
- 错误报告
使用 critical
方法
- 警告
使用 warning
方法
- 信息提示
使用 information
方法
也可以使用 about
方法
- 确认继续
使用 question
方法
from PySide6.QtWidgets import QMessageBox
choice = QMessageBox.question(
self.ui,
'确认',
'确定要删除本文件吗?')
if choice == QMessageBox.Yes:
print('你选择了yes')
if choice == QMessageBox.No:
print('你选择了no')
输入对话框
QInputDialog
输入对话框 只让用户输入一行数据信息,比如 姓名、年龄等。
可以方便的用来获取简单的信息。
比如
from PySide6.QtWidgets import QInputDialog,QLineEdit
# 返回值分别是输入数据 和 是否点击了 OK 按钮(True/False)
title, okPressed = QInputDialog.getText(
self,
"输入目录名称",
"名称:",
QLineEdit.Normal,
"")
if not okPressed:
print('你取消了输入')
常用的方法有:
- getText
弹出对话框,让用户输入 单行文本
- getMultiLineText
弹出对话框,让用户输入 多行文本
- getInt
弹出对话框,让用户输入 整数
- getItem
弹出对话框,让用户选择 选项
items = ["春天", "夏天", "秋天", "冬天"]
item, ok = QInputDialog().getItem(self,
"请选择",
"季节:",
items,
0,
False)
if ok and not item.isEmpty():
itemLabel.setText(item)
剪贴板
Qt程序可以获取和设置剪贴板内容
from PySide6.QtGui import QGuiApplication
cb = QGuiApplication.clipboard()
# 获取剪贴板内容
originalText = cb.text()
# 设置剪贴板内容
clipboard.setText(newText)
树控件
QTreeWidget 树控件
树控件, 是和 QTreeWidgetItem 树节点控件
结合使用的。
如下图所示
说明
树控件用来展示树状层级结构的数据,典型例子有: 文件目录结构、知识点结构
设置属性
如果用 Qt 设计师 工具 制作界面,首先应该设置好 QTreeWidget 的一些属性,如下
其中,常见的设置是 :
- 设置有几个column ,也就是有几列,
双击树控件,就可以编辑列名,如下图
- 可以设置列标头 是否可见
添加节点
设置好 QTreeWidget 的属性以后,代码中加载界面资源文件,就可以在树上添加节点了,如下代码所示
注意:QTreeWidget 有一个不可见根节点 ,树的第一层节点 都是这个 不可见根节点的 子节点,它是整个树的根。 可以通过 invisibleRootItem 方法获得。
from PySide6.QtWidgets import QApplication
from PySide6.QtUiTools import QUiLoader
# 导入 QTreeWidget, QTreeWidgetItem, QIcon
from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem
from PySide6.QtGui import QIcon
class SomeWindow:
def __init__(self):
self.ui = QUiLoader().load('main.ui')
# 调用设置树控件
self.setupTree()
def setupTree(self):
# 获取树控件对象
tree = self.ui.tree
# 隐藏标头栏
tree.setHeaderHidden(True)
# 获取树控件的不可见根节点
root = tree.invisibleRootItem()
# 准备一个folder节点
folderItem = QTreeWidgetItem()
# 创建图标对象
folderIcon = QIcon("./Images/folder.png")
# 设置节点图标
folderItem.setIcon(0, folderIcon)
# 设置该节点 第1个column 文本
folderItem.setText(0, '学员李辉')
# 添加到树的不可见根节点下,就成为第一层节点
root.addChild(folderItem)
# 设置该节点为展开状态
folderItem.setExpanded(True)
# 准备一个 叶子 节点
leafItem = QTreeWidgetItem()
leafIcon = QIcon("./Images/leaf.gif")
# 设置节点图标
leafItem.setIcon(0, leafIcon)
# 设置该节点 第1个column 文本
leafItem.setText(0, '作业 - web自动化1')
# 设置该节点 第2个column 文本
leafItem.setText(1, '提交日期20200101')
# 添加到 叶子节点 到 folerItem 目录节点下
folderItem.addChild(leafItem)
app = QApplication([])
w = SomeWindow()
w.ui.show()
app.exec_()
上面的程序运行后,大概的结果图如下
注意:
-
每个 节点 都是
QTreeWidgetItem
对象 -
添加节点 必须通过 该节点的
父节点
可以使用 addChild
方法,添加到最后
也可以使用 insertChild
方法,插入到指定位置,比如
就插入到 第3个 子节点的位置上,因为第1个子节点的索引是0。
- 如果控件宽度不够,字符串会显示为省略号。
列宽度
列的宽度,可以在界面上 该 QTreeWidget
控件的属性编辑里面, 调整 headerDefaultSectionSize
和 headerStretchLastSection
来控制。
也可以通过代码来控制,如下
def setupTree(self):
# 其他代码...
tree.setColumnWidth(0, 200) # 设置第一列宽度,200px
header = tree.header()
header.setStretchLastSection(True)
如果想让列宽度 自适应 内容,使用 下面的方法
from PySide6.QtWidgets import QHeaderView
def setupTree(self):
# 其他代码...
header = tree.header()
# 让列宽度 自适应 内容
header.setSectionResizeMode(QHeaderView.ResizeToContents)
当前选中节点
我们有时要在当前节点下面添加节点、删除当前节点,这就需要先获取当前节点。
QTreeWidget
的 currentItem
方法可以获取当前节点。
下面是一段 在选中节点下面添加子节点 的示例代码
from PySide6.QtWidgets import QLineEdit, QInputDialog
from PySide6.QtCore import Qt
class SomeWindow:
def __init__(self):
self.ui = QUiLoader().load('main.ui')
self.ui.btn_add.clicked.connect(self.addChildNode)
# 其他部分代码
....
def addChildNode(self):
# 获取树控件对象
tree = self.ui.tree
# 获取当前用户点选的节点
currentItem = tree.currentItem()
# 没有当前选中节点,不可见根节点作为当前节点
if not currentItem:
currentItem = tree.invisibleRootItem()
# 当前节点的第一列文本内容
col1Text = currentItem.text(0)
print(col1Text)
# 让用户输入信息
title, okPressed = QInputDialog.getText(
self.ui, "输入名称", "名称:",
QLineEdit.Normal, "")
if not okPressed or title == '':
return
# 准备一个folder节点
folderItem = QTreeWidgetItem()
# 创建图标对象
folderIcon = QIcon("./images/folder_close.png")
# 设置节点图标
folderItem.setIcon(0, folderIcon)
# 设置该节点 第1个column 文本
folderItem.setText(0, title.strip())
# 设置该节点在以前的flag基础上,多一个可编辑 ItemIsEditable
folderItem.setFlags(folderItem.flags() | Qt.ItemIsEditable)
currentItem.addChild(folderItem)
currentItem.setExpanded(True)
获取父节点
获取一个节点的父节点是调用 该QTreeWidgetItem节点的 parent
方法
如下
# 获取某个节点item的父节点item
parentItem = treeItem.parent()
# 如果返回值为None,其必定为顶层节点,它的父节点是不可见的根节点
if not parentItem:
parentItem = tree.invisibleRootItem()
删除节点
删除 QTreeWidget 的节点, 是 通过其父节点调用 removeChild
方法。
示例代码如下:
# 获取当前用户点选的节点
currentItem = tree.currentItem()
# 如果没有当前选中节点
if not currentItem:
return
# 找到改节点的父节点
parentItem = currentItem.parent()
# 如果没有父节点,就是不可见父节点
if not parentItem:
parentItem = tree.invisibleRootItem()
# 删除该节点
parentItem.removeChild(currentItem)
注意:删除一个节点,同时也就删除了其所有子节点。
删除所有节点
可以使用 QTreeWidget 的方法 clear,如下
也可以删除不可见根节点的所有子节点的方法,如下
root = tree.invisibleRootItem()
for i in reversed(range(root.childCount())):
root.removeChild(root.child(i))
编辑节点文本
假设我们要 实现,双击节点文本 可以 编辑节点文本,就是如下所示
首先必须设置节点flag为 : 可编辑 ItemIsEditable
。
可以在创建节点的时候设置,如下代码所示
from PySide6.QtCore import Qt
# 准备一个folder节点
folderItem = QTreeWidgetItem()
folderItem.setIcon(0, folderIcon)
folderItem.setText(0, '白月黑羽学员李辉')
# 设置该节点在以前的flag基础上,多一个可编辑 ItemIsEditable
folderItem.setFlags(folderItem.flags() | Qt.ItemIsEditable)
信号处理
QTreeWidget 常见的信号有:单击节点、双击节点、当前节点变动 等等。
详细信息参考官方文档
大家可以根据自己的需要,定义信号处理函数
比如,可以这样定义节点 被点击事件
的处理函数
class SomeWindow:
def __init__(self):
self.ui = QUiLoader().load('main.ui')
# 添加 被点击事件 的处理函数
self.ui.tree.itemClicked.connect(self.itemClicked)
# 参数 item 是被点击节点对应的 QTreeWidgetItem 对象
# 参数 column 是被点击的column号
def itemClicked(self, item, column):
# 获取被点击的节点文本
clickedText = item.text(column)
if 'bottle' in clickedText:
self.bottle()
elif 'cube' in clickedText:
self.cube()
再看一个例子,可编辑的节点文本被编辑改变后,其所属 QTreeWidget 就会触发 itemChanged
信号
我们应该定义该信号的处理函数,如下所示
class SomeWindow:
def __init__(self):
self.ui = QUiLoader().load('main.ui')
# 必须先加载树,然后再设置信号处理,
# 否则加载过程也会触发 itemChanged 信号
self.ui.tree.itemChanged.connect(self.itemChanged)
def itemChanged(self, item, column):
# 参数 item 是修改的 节点 QTreeWidgetItem对象
# 参数 column 是修改的 列号
newText = item.text(column)
# 根据改变的 column 得知是哪个数据修改了
# 保存到对应的数据库表记录中
节点关联数据
Python中, 类对象可以动态添加属性,
所以我们可以根据需要,给 QTreeWidgetItem 节点对象 关联 各种类型的数据。
如下所示:
nodeitem = QTreeWidgetItem()
nodeitem.setText(0, filename)
# python 对象可以动态添加属性
nodeitem._my_data1 = '给节点关联的数据,根据需要设定'
nodeitem._my_data2 = ['其它数据1','其它数据2']
这些关联的数据,可以在后续操作节点时,根据需要取出来使用。
关联数据又什么用呢? 想象这样一个案例:
我们用树控件显示一个目录下的文件。
当用户双击节点,修改了文件的名字,代码应该 在 itemChanged 信号处理函数中真正修改该文件名。
但是如果,用户修改的名字已经存在同名文件,就会产生冲突,这时,应该恢复该节点的名字为原来的名字。
但是,界面上改为新名字了,原来名字丢失了怎么办?
这时就可以使用 关联数据的方案:
- 添加节点时,除了 这样 设置文件名,
再给节点动态添加一个属性,比如
备份一个文件名的记录
后面如果修改节点名方法,发现名字冲突了,就改回原来的名字
# 修改节点名
def treeItemNameChanged(self, item, column):
newName = item.text(column)
filepath = os.path.join(self.thisFolderPath, newName)
# 文件名冲突,还原
if os.path.exists(filepath):
QMessageBox.information(
self.ui, '错误',
f'文件 {filepath} 已经存在,请重新修改')
# 原来的名字保存在自己定义的 _my_filename 属性中
item.setText(0, item._my_filename)
return
# 不冲突的后续处理
oriFilePath = os.path.join(self.thisFolderPath, item._my_filename)
# 修改文件名
os.rename(oriFilePath, filepath)
获取节点的子节点
QTreeWidgetItem
的 childCount()
方法可以获取子节点的个数
QTreeWidgetItem
的 child()
方法可以获取子节点对应的 QTreeWidgetItem
对象,参数就是子节点的编号,从0开始。
tree.child(0)
就是获取第1个子节点
下面这段代码就是打印某个树节点的 所有直接子节点
文本
def printChildren(parent):
child_count = parent.childCount()
for i in range(child_count):
item = parent.child(i)
print(item.text(0))
如果要打印某个树节点的 所有后代节点
文本,最方便的就是用递归
def printChildren(parent):
child_count = parent.childCount()
for i in range(child_count):
item = parent.child(i)
print(item.text(0))
printChildren(item) # 调用自身,进行递归
<!-- 下面是一段 递归的遍历树节点,保存数据的示例代码
# 递归调用函数,完成整个树的遍历
def iterateFunc(parent,dataTree):
child_count = parent.childCount()
for i in range(child_count):
item = parent.child(i)
# 保存这个节点item的信息到字典对象中
subDictNode = {}
dataTree.append(subDictNode)
subDictNode['data'] = item.data
subDictNode['children'] = []
# 对该子节点递归调用遍历处理函数
iterateFunc(item,subDictNode['children'])
# 获取不可见根节点
root = tree.invisibleRootItem()
# 用嵌套列表来对应树的数据结构,方便保存到文件
dataTree = []
iterateFunc(root,dataTree)
# 序列化到json文件,保存
jsonStr = json.dumps(dataTree,ensure_ascii=False,indent=2)
with open('data.json','w',encoding='utf8') as f:
f.write(jsonStr)
``` -->
### 上下文菜单
要用 树节点右键菜单功能,可以这样处理:
先这样做初始化设置
```py
from PySide6.QtWidgets import QMenu, QAction
from PySide6.QtCore import Qt
# 其它代码
...
def __init__(self):
# 其它初始化代码
...
# 设置上下文菜单策略,也可以在 Qt Designer上设置
self.ui.tree.setContextMenuPolicy(Qt.CustomContextMenu)
# 定义信号处理方法
self.ui.tree.customContextMenuRequested.connect(
self.show_context_menu_onfiletree)
然后,定义信号处理方法
def show_context_menu_onfiletree(self, position):
tree = self.ui.tree
# 获取当前用户点选的节点
curItem = tree.currentItem()
# 没有当前选中节点
if not curItem:
print('没有选中节点,返回')
return
# 创建 上下文菜单 和 菜单项Action
menu = QMenu(tree)
action_delnode = QAction("删除")
action_delnode.triggered.connect(self.action_delnode)
action_addnode = QAction("添加子节点")
action_addnode.triggered.connect(self.action_addChildNode)
menu.addAction(action_delnode)
menu.addAction(action_addnode)
# 在鼠标点击处展示上下文菜单
menu.exec_(tree.mapToGlobal(position))
# 菜单项Action对应的处理函数
def action_delnode(self, position):
# 这些写处理代码
# 菜单项Action对应的处理函数
def action_addChildNode(self, position):
# 这些写处理代码
MDI 多个子窗口
QMdiArea
提供了一个主窗口区,里面可以存放多个 QMdiSubWindow
子窗口
如图:
本节讲解 仅 内部学员 可见