一、菜单QMenu
1.菜单QMenu的介绍(官翻)
QMenu继承自QWidget
菜单小部件是一个选择性质的菜单。它可以是菜单栏中的下拉菜单,也可以是独立的上下文菜单。当用户单击相应的项目或按下指定的快捷键时,下拉菜单由菜单栏显示。使用addMenu()将菜单插入到菜单栏中。上下文菜单通常通过一些特殊的键盘键或右键单击来调用。它们可以通过popup()异步执行,也可以通过exec()同步执行。也可以在按下按钮时调用菜单;除了调用方式不同之外,它们类似的弹出内容。
菜单由一系列操作项组成。有四种操作项:分隔符、显示子菜单的操作、部件和执行操作的操作。分隔符是用addSeparator()插入的,子菜单是用addMenu()插入的,所有其他项都被认为是操作项。
动作是通过addAction()、addActions()和insertAction()函数添加的。一个动作由QStyle垂直表示并渲染。此外,动作可以有一个文本标签,在最左边绘制一个可选的图标,以及快捷键序列,如“Ctrl+X”。
可以用actions()找到菜单中现有的操作。
当插入动作项时,你通常需要指定一个接收器和一个插槽。每当动作项目被triggered()信号触发时,接收器就会收到通知。此外,QMenu提供了两个信号:triggered()和hover(),它们表示从菜单中触发的QAction。(这里所说的接收器通常指的是接收到信号的控件)
使用clear()清除菜单,使用removeAction()删除单个操作项。
QMenu还可以提供一个可撕下的菜单。可撕下菜单是一个顶层窗口,其中包含该菜单的副本。这使得用户可以“撕下”经常使用的菜单,并将它们放置在屏幕上方便的位置。如果你想为特定的菜单提供此功能,可以使用setTearOffEnabled()插入一个撕下句柄。当使用可撕式菜单时,请记住这个概念通常在Microsoft Windows上不使用,所以一些用户可能不熟悉它。考虑改用QToolBar。
小部件可以用QWidgetAction类插入到菜单中。该类的实例用于保存小部件,并通过addAction()重载插入到菜单中,该重载接受一个QAction。如果QWidgetAction触发了triggered()信号,菜单将关闭。
解释:QAction是一个抽象概念,不是一个可见的控件,通俗理解就是一个操作行为。这种行为可以把他具象为剪切、复制、粘贴、打开、关闭、新建。。。。等等,那么把这些行为抽象一下创建一个类就是QAction(学语言的时候,面向对象都讲过这个概念)。它通常被添加到菜单和工具栏中使用,在菜单中外观上就是菜单项,工具栏中外观上就表现为按钮。
QMenu菜单的概念就很直观,前面我们也都再例子中见过,通常就是一个能够弹出并展开的面板,它分为两种情况:一种是放在菜单栏中的弹出菜单,另一种是上下文菜单(右键菜单)。
2.QMenu和QAction的创建与基本用法
这里必须把QAction的创建一并学习,因为对于QMenu来说,除非其作为顶层窗口(这么做没啥意义),通常在没有添加QAction的情况下,即使其被添加到父控件中,也不会弹出。
API函数 | 参数说明 | 返回值 | 功能作用 |
QAction(self,icon,text,parent) | icon:Union[QCion,QPixma] text:str,parent:QObject | None | 创建动作 |
QAction(self, parent) | parent:QWidget | None | 创建动作 |
QAction(self, text,parent) | text:str parent:QObject | None | 创建动作 |
QMenu(self, parent) | parent:QWidget | None | 创建菜单 |
QMenu (self, title,parent) | ttitle:str parent:QObject | None | 创建菜单 |
addAction(self,action) | action:QAction | None | 在菜单中添加新动作 |
addAction(self,text) | text:str | QAction | 在菜单中添加新动作 |
addAction(self,icon,text) | icon:QIcon text:str | QAction | 在菜单中添加新动作 |
popup(pos,at=None) | pos:QPoint at:QAction | None | 在指定位置弹出菜单,如果指定at参数,则让指定的动作显示在指定位置 |
exec(self) | None | QAction | 显示菜单,返回被触发的动作,如果没有则返回None |
exec(self,point,at) | pos:QPoint at:QAction | QAction | 在指定位置显示菜单,如果指定at参数,则让指定的动作显示在指定位置 |
exec(self,Sequence[QAction],point,at=None,parent) | pos:QPoint at:QAction parent:QWidget | QAction | 在指定位置显示菜单,当pos无法确定位置时,用父控件辅助确定位置。 |
popup()与exec()都是弹出菜单,区别在于popup()方法是异步的,弹出速度快,通常不需要指定参数;exec()方法是同步的,大多数情况下需要指定弹出位置,弹出窗口是模态的(模态非模态将在Dialog章节介绍,模态即当前窗口运行口,无法与其他应用程序窗口交互)。同时,当菜单中的action被触发会将此action作为返回值返回。目前,直接使用popup()就行。
当然,popup()也可以指定弹出位置,需要注意的是,弹出的位置QPoint通常需要进行坐标映射,使用mapToParent()或者mapToGlobal()进行转换。
还需说明的是,如上一章QPushButton中的例子,为QPushButton添加的菜单无论是使用QPushButton.showMenu()方法弹出菜单还是QMenu.exec()、QMenu.popup()方法弹出都无法调整弹出位置,默认是在按钮的下方。
from PySide6.QtWidgets import QMenu,QApplication,QWidget,QPushButton,QMenuBar
from PySide6.QtGui import QAction
from PySide6.QtCore import QPoint,Qt
import sys
class testWindow(QWidget):
def __init__(self):
super(testWindow, self).__init__()
self.resize(500,500)
# 为按钮设置菜单
btn = QPushButton(self)
btn.move(200,200)
menu = QMenu(btn)
menu.addActions([QAction("新建",self),QAction("打开",self),QAction("保存",self)])
btn.setMenu(menu)
# 设置弹出位置没有效果
btn.clicked.connect(lambda :menu.exec(btn.mapToGlobal(QPoint(300,100))))
# 为菜单栏设置菜单
menuBar = QMenuBar(self)
# 创建文件菜单并编辑动作
menu_file = QMenu("文件",menuBar)
action_new = QAction("新建",self)
action_open = QAction("打开",self)
action_save = QAction("保存",self)
menu_file.addActions([action_new,action_open,action_open])
# 创建编辑菜单并添加动作
menu_edit = QMenu("编辑",menuBar)
action_copy = QAction("复制",self)
action_paste = QAction("粘贴",self)
action_cut = QAction("剪切",self)
menu_edit.addAction(action_copy)
menu_edit.addAction(action_paste)
menu_edit.addAction(action_cut)
# 菜单添加到菜单栏
menuBar.addMenu(menu_file)
menuBar.addMenu(menu_edit)
# 再搞一个窗口的右键菜单
self.addActions([action_copy,action_paste,action_cut,action_new,action_open,action_save])
self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = testWindow()
win.show()
sys.exit(app.exec())
运行结果:
20240928-130813
3.QMenu其他的常用方法
API函数 | 参数说明 | 返回值 | 功能作用 |
addMenu(self,menu) | menu:QMenu | QAction | 添加一个子菜单 |
addMenu(self,title) | title: str | QMenu | 添加一个子菜单 |
addMenu(self,icon,title) | icon:union[QIcon,QPixmap] | QMenu | 添加一个子菜单 |
addSection(self,icon,text) | icon:QIcon text:str | QAction | 添加一个分割线 |
addSection(self, text) | text:str | QAction | 添加一个分割线 |
addSeparator(self) | None | QAction | 添加一个分割线 |
insertMenu(self, before, menu) | before:QAction menu:QMenu | QAction | 在指定动作前插入子菜单 |
insertSection(self, before, icon, text) | before: QAction, icon: QIcon, text: str | QAction | 在指定动作前插入分隔条 |
insertSection(self, before, text) | before: QAction, text: str | QAction | 在指定动作前插入分隔条 |
insertSection(self, before) | before: QAction, | QAction | 在指定动作前插入分隔条 |
insertSeparator(self, before) | before: QAction, | QAction | 在指定动作前插入分隔条 |
removeAction(self, a) | a:QAction | None | 从菜单中中移除一个指定动作 |
clear(self) | None | None | 清空菜单 |
actions(self) | None | list[QAction] | 获取添加至菜单中的动作列表 |
isEmpty(self) | None | None | 获取菜单是否为空 |
actionAt(self, arg__1) | arg_1:QPoint | QAction | 获取指定坐标处的动作 |
columnCount(self) | None | int | 获取菜单的总列数 |
menuAction(self) | None | QAction | 返回子菜单所属的动作 |
menuInAction(self, action) | action:QAction | QMenu | 返回此菜单中的指定动作中所包含的子菜单,如果不包含返回None |
setDefaultAction(self, arg__1) | arg_1: QAction | None | 设置默认动作,以粗体字显示 |
defaultAction(self) | None | QAction | 获取菜单的默认动作 |
setSeparatorsCollapsible(self, collapse) | collapse:bool | None | 合并相邻的分隔条,开始和结尾的分隔条不可见 |
setTitle(self, title) | title:str | None | 设置菜单的标题 |
title(self) | None | str | 获取菜单的标题 |
setActiveAction(self, act) | act:QAction | None | 设置指定动作为活跃动作,高亮显示 |
activeAction(self) | None | QAction | 获取活跃的动作 |
actionGeometry(self, arg__1) | arg_1: QAction | QRect | 返回动作的几何矩形 |
setTearOffEnabled(self, arg__1) | arg_1:bool | None | 设置菜单是否成为可撕扯菜单 |
isTearOffEnabled(self) | None | bool | 获取菜单是否为可撕扯菜单 |
showTearOffMenu(self) | None | None | 强制显示可撕扯菜单 |
showTearOffMenu(self,p) | p:QPoint | None | 强制在指定点显示可撕扯菜单 |
hideTearOffMenu(self) | None | None | 强制隐藏可撕扯菜单 |
1)设置和插入分割线
分割线:看图
但是,QMenu中分割线是作为一个QAction存在的,所以,addSection()和addSeparator()的返回值都是QAction。这么设计的好处是,当需要的时候(比如临时需要添加动作命令),随时可以将分割线转化动作条目。转化后,如果还希望有分割线,就可以使用insertSeparator(self, before),这样又有一条作为QAction的线存在备用。
2)添加和插入子菜单
子菜单:还是看图
从addMenu(self,menu)->QAction中我们也看到,实际上子菜单标题位置还是一个QAction条目,只是在将子菜单再次设置这个位置QAction,多以返回值是这个位置的action。而内部实现过程则是点击这个动作action条目的时候,触发了action的信号,从而popup()弹出子菜单。在下一小节中的QAction学习中,我们可以明显看到QAction有一个方法就是QAction.setMenu(),就是这个原理。需要说明的是,popup()与exec()都是函数方式弹出,通常用于槽函数中调用,在实际使用时,我们只要将action添加到menu中,并将menu设置给菜单栏,控件已经为我们实现了鼠标点击弹出功能。
在Pyside6中,拥有子菜单的action,与上图的形式一样,这是在尾部拥有扩展的右箭头的。
3)设置默认动作和活跃动作
使用setDefaultAction()和setActiveAction()分别设置默认动作和被激活的动作,默认动作在外观上会被着重显示(字体加粗),活动动作的动作条会高亮,但这些外观都应用程序设置的QStyle有配套。个人认为这两基本没啥用,官方解释中setDefaultAction()在接收放置事件上可能会有所指示,但QMenu根本就不不接受放置(setAcceptDrops(True)也不行)。setActiveAction()会高亮的同时,也会获取键盘焦点,点击Enter键会触发被激活动作的triggered信号。其他的可能在鼠标互动事件上能做些文字。
4)设置可撕扯菜单
所谓的可撕扯菜单,就是能够将展开的菜单面板进行悬浮。可撕扯菜单会在最上方显示“撕扯线”。点击并激活撕扯线后,展开的菜单面板就变为悬浮面板。
对这个“撕扯菜单”的悬浮面板的显示与隐藏操作就是showTearOffMenu(self)和hideTearOffMenu(self)。
5)其它
columncount()方法返回显示的列数,通常来说返回的都是1(不包含子菜单);如果是空菜单,则返回0。只有action太多,一行显示不下的时候,才会自动显示为两行,此时返回2。这种太丑了,它不是默认以主界面的范围为限制,而是以整个屏幕为范围,如果需要调整,就需要在自定义的QStyle中修改。
4.QMenu的信号
信号 | 参数说明 | 返回值 | 功能作用 |
aboutToHide() | None | None | 菜单隐藏前发射此信号 |
aboutToShow() | None | None | 菜单显示前发射此信号 |
hovered(QAction) | QAction | None | 光标划过菜单时发射此信号 |
triggered(QAction) | QAction | None | 当菜单被触发时发射此信号,参数为引起信号发出的动。 |
hovered和triggered这两个信号都是针对菜单中具体的actionItem来说的。
而aboutToHide与aboutToShow这个就是针对菜单本身,比如在菜单栏中的菜单会显示其title,那么点击title标志就可以触发这两个信号,这两个信号的作用,看下图:
上图是WPS的文件菜单界面,按照Pyside的菜单外观来说,点击后只会弹出菜单部分,右侧的最近使用的快捷面板就需要我们使用这两个方法进行同步弹出和同步隐藏。
二、动作QAction
1.动作QAction的介绍(官翻)
在应用程序中,许多常用命令可以通过菜单、工具栏按钮和键盘快捷键调用。由于用户希望以相同的方式执行每个命令,而不管使用什么用户界面,因此将每个命令表示为一个操作是有用的。
可以将操作添加到用户界面元素,如菜单和工具栏,并自动保持UI同步。例如,在文字处理器中,如果用户按下“Bold”工具栏按钮,粗体动作项将自动被选中。
一个QAction可能包含一个图标,描述性文本,图标文本,一个键盘快捷键,状态文本,“what‘s This’?”文本和工具提示。所有属性都可以通过setIcon()、setText()、setIconText()、setShortcut()、setStatusTip()、setWhatsThis()和setToolTip()独立设置。Icon和text这两个最重要的属性也可以在构造函数中设置。可以使用setFont()来设置单个字体,例如,当将操作显示为菜单项时,菜单会遵循此操作。
我们建议将操作创建为使用它们的窗口的子窗口。在大多数情况下,actions是应用程序主窗口的子窗口。
一旦创建了QAction,它应该被添加到相关的菜单和工具栏,然后连接到将要执行该操作的插槽。
可以使用QWidget.addAction()或qgraphicwidget::addAction()将操作添加到部件中。请注意,QAction必须在被添加到widget之后,才能够被使用。当设置全局的快捷方式时(即Qt.ApplicationShortcut为Qt.ShortcutContext)时也应如此。
动作可以被创建为独立的对象。但它们也可能是在构建菜单时创建的。QMenu类包含用于创建适合作为菜单项使用的操作的便捷函数。
2.QAction的常用方法
QAction的创建已经在上一节学习过了,下面看其他的相关方法:
API函数 | 参数说明 | 返回值 | 功能作用 |
setText(self, text) | text:str | None | 设置动作的显示文本 |
text(self) | None | str | 获取动作的显示文本 |
setIcon(self, icon) | icon: Union[QIcon, QPixmap] | None | 设置动作的图标 |
icon(self) | None | QIcon | 获取动作的图标 |
setIconText(self, text) | text:str | None | 设置动作的图标文本 当使用setText()时会被覆盖 |
iconText(self) | None | str | 获取动作的图标文本 |
setCheckable(self, arg__1) | arg_1:bool | None | 设置动作是否可以选中或标记 |
isCheckable(self) | None | bool | 获取动作是否可以选中或标记 |
setChecked(self, arg__1) | arg_1:bool | None | 设置动作是否处于选中状态 |
isChecked(self) | None | bool | 获取动作是否处于选中状态 |
setAutoRepeat(self, arg__1) | arg_1:bool | None | 设置动作是否开启自动重复 |
autoRepeat(self) | None | bool | 获取动作是否开启自动重复 |
setMenu(self, menu) | menu:QMenu | None | 将动作添加到菜单 |
menu(self) | None | None | 获取动作所在的菜单 |
setIconVisibleInMenu(self, visible) | visable:bool | None | 设置在动作栏中图标是否可见 |
isIconVisibleInMenu(self) | None | bool | 获取在菜单中图标是否可见 |
setFont(self, font) | font: Union[PySide6.QtGui.QFont, str, Sequence[str]] | None | 设置字体 |
font(self) | None | QFont | 获取字体 |
setShortcut(self, arg__1) | arg__1: union(str,QKeySequence) | None | 为动作设置快捷键 |
shortcut(self) | None | QKeySequence | 获取快捷键 |
setDisabled(self, b) | b:bool | None | 设置是否禁用 |
setEnabled(self, arg__1) | arg_1:bool | None | 设置是否可用 |
isEnabled(self) | None | bool | 获取是否可用 |
resetEnabled(self) | None | None | 重置可用状态 |
setVisible(self, arg__1) | arg_1:bool | None | 设置是否可见 |
isVisible(self) | None | bool | 获取是否可见 |
setSeparator(self, b) | b:bool | None | 设置是否将动作当做分割线使用 |
isSeparator(self) | None | bool | 获取是否将动作当做分割线使用 |
setShortcutVisibleInContextMenu(self, show) | show:bool | None | 设置动作的快捷键在右键菜单中是否显示 |
isShortcutVisibleInContextMenu(self) | None | bool | 获取动作的快捷键在右键菜单中是否显示 |
setShortcuts(self, arg__1) | arg_1: Sequence[PySide6.QtGui.QKeySequence] | None | 为动作设置多个快捷键 |
shortcuts(self) | None | list( QKeySequence) | 获取动作的快捷键列表 |
setShortcutContext(self, context) | context: PySide6.QtCore.Qt.ShortcutContext | None | 设置快捷键的上下文环境 |
shortcutContext(self) | None | Qt.ShortcutContext | 获取快捷键的上下文环境 |
setData(self, var) | var:any | None | 给动作设置任意的附加数据 |
data(self) | None | any | 获取动作的附加数据 |
setPriority(self, priority) | priority: PySide6.QtGui.QAction.Priority | None | 设置动作的优先级 |
priority(self) | None | QAction.Priority | 获取动作的优先级 |
setStatusTip(self, arg__1) | arg_1:str | None | 设置提示信息,再状态栏显示 |
statusTip(self) | None | str | 获取将要在状态栏显示的提示信息 |
setToolTip(self, arg__1) | arg_1:str | None | 设置气泡提示 |
toolTip(self) | None | str | 获取气泡提示 |
setWhatsThis(self, arg__1) | arg_1:str | None | 设置帮助信息 |
whatsThis(self) | None | str | 获取帮助信息 |
trigger(self) | None | None | 发送triggered()信号或者发送triggered(bool)信号 |
hover(self) | None | None | 发送hovered()信号 |
toggle(self) | None | None | 发送toggleed(bool)信号 |
setActionGroup(self, group) | group:QActionGroup | None | 将动作添加到动作组 |
actionGroup(self) | None | QActionGroup | 获取动作所在的动作组 |
associatedObjects(self) | None | list[QObject] | 返回已添加此动作的对象的列表 |
在通常的情况下(action添加至工具栏或者添加至菜单中),action表现出如按钮类似的状态,在菜单中,不仅可以被点击,还拥有图标及选中标记;在菜单栏中表现的如工具按钮,可以点击也可以拥有图标(见后面的工具按钮)。另外,其还可以拥有气泡提示和“what‘s this”。
QAction基本的方法,如标题、图标、可用性、显示隐藏、设置快捷键等与QPushButton的设置及用法一致。
对于是否可以勾选有一点外观上的区别,当Style为“windows”和“windows11”时,在加入或者不加入动作组中,勾选状态均为“√”;而当Styel为“windowsbvista”和“Fusion”时,不加入动作组勾选状态为“√”,加入动作后勾选状态为“.”。但是对于拥有icon的action,不会显示,由于位置被icon占据,不会显示“√”和“.”,图标会显示选中效果。
关于动作组QActionGroup在后面的小节学习。
可用性上,设置不可用时,是无法选中和点击触发的。
setPriority( )方法的参数是一个枚举类:
枚举类 | 枚举常量 | 枚举值 | 功能描述 |
QAction.Priority | LowPriority | 该动作不应该在用户界面中优先显示。 | |
NormalPriority | |||
HighPriority | 该动作应该在用户界面中优先显示。 |
目前所知的是,当设置action为LowPriority时,在工具栏设置Qt.ToolButtonTextBesideIcon模式时,具有低优先级的动作将不会显示文本,只显示图标。
setMenu()方法在上一节菜单中已经学习过,会再当前action生成一个子菜单;这里要说的是在工具栏的action中添加子菜单,也会生成一个带向下箭头的action,点击箭头会弹出子菜单。同时,这个action也可以用,点击它部位与箭头的区域会激活action的信号。
而关于快捷的上下文环境的设置setShortcutContext( ),其参数是个枚举类:
枚举类 | 枚举常量 | 枚举值 | 功能描述 |
Qt.ShortcutContext | WidgetShortcut | 当其父部件获得焦点时,该快捷方式是活动的。 | |
WidgetWithChildrenShortcut | 当它的父部件或任何子部件获得焦点时,该快捷方式是活动的。除了弹出窗口之外,顶级部件的子部件不受这个快捷方式上下文的影响。 | ||
WindowShortcut | 当其父部件是活动顶级窗口的逻辑子部件时,该快捷方式是活动的 | ||
ApplicationShortcut | 当一个应用程序窗口处于活动状态时,快捷方式是活动的。 |
可以看到,当我们的应用程序比较简单,ApplicationShortcut和WindowShortcut可用,当我们的应用程序复杂,属于大型项目,包含多个子窗口、跳转窗口,控件较多快捷键可能重复或者与标准快捷键冲突时,WidgetShortcut才是合适的。
3.QAction的信号
信号 | 参数说明 | 返回值 | 功能作用 |
hovered() | None | None | 光标划过动作时发送信号 |
tiggered() | None | None | 单击动作或按快捷键时发送信号 |
tiggered(bool) | bool | None | 单击动作或按快捷键时发送信号 |
toggled(bool) | bool | None | 动作的切换状态发生改变时发送信号 |
changed() | None | None | 动作属性(文本、图标、可见性、优先级、快捷键、提示信息)发生改变时发送信号 |
checkableChanged(bool) | bool | None | 动作勾选启用/禁用发生改变时发送信号 |
enableChange(bool) | bool | None | 动作启用状态发生改变时发送信号 |
visableChange(bool) | bool | None | 动作的可见性发生改变时发送信号 |
相关方法及信号的具体用法,将在下面的子中展现:
example:例子中有工具栏QToolBar的部分内容,不用过分纠结,后面会学习,只看注释了解作用即可。
from PySide6.QtWidgets import QMenu,QApplication,QWidget,QMenuBar,QToolBar,QStyleFactory
from PySide6.QtGui import QAction,QIcon,QKeySequence
from PySide6.QtCore import Qt
import sys
class myToolBar(QToolBar):
"""
自定义一个工具栏,主要是实现工具栏上右键的时候可以弹出菜单
"""
def __init__(self,parent):
super(myToolBar, self).__init__(parent)
self.menu = None
def setMouseRightMenu(self,menu):
self.menu = menu
def mousePressEvent(self, a0):
super(myToolBar, self).mousePressEvent(a0)
if a0.button() == Qt.MouseButton.RightButton and self.menu != None:
self.menu.popup(self.cursor().pos())
class testWindow(QWidget):
def __init__(self):
super(testWindow, self).__init__()
self.resize(800,600)
self.setWindowTitle("Qmenu和QAction测试")
# -----------创建一个菜单栏和三个菜单并添加action--------------------------
menubar = QMenuBar(self)
menu_file = QMenu(menubar)
menu_file.setTitle("文件")
menubar.addMenu(menu_file)
action_new = QAction(QIcon("C:/Users/Administrator/Pictures/新建文件.jpg"),"新建",self)
action_new.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_N))
# 新建的action优先级低
action_new.setPriority(QAction.Priority.LowPriority)
action_save = QAction(QIcon("C:/Users/Administrator/Pictures/保存文件.jpg"),"保存",self)
action_save.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_S))
action_save.setPriority(QAction.Priority.NormalPriority)
action_saveAs = QAction(QIcon("C:/Users/Administrator/Pictures/另存为.png"), "另存为..", self)
action_saveAs.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_A))
action_saveAs.setPriority(QAction.Priority.HighPriority)
action_open = QAction(QIcon("C:/Users/Administrator/Pictures/打开文件.jpg"), "打开", self)
action_open.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_O))
action_open.setDisabled(False)
action_colse = QAction(QIcon("C:/Users/Administrator/Pictures/test/581237.png"), "退出", self)
action_colse.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_E))
# 设置action_close不可用
action_colse.setEnabled(False)
# action_line1作为分隔线使用
action_line1 = QAction(self)
action_line1.setSeparator(True)
action_pdf = QAction(QIcon("C:/Users/Administrator/Pictures/导出pdf.jpg"), "导出PDF", self)
action_setting = QAction(QIcon("C:/Users/Administrator/Pictures/设置.png"), "设置", self)
action_setting.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier | Qt.Key.Key_S))
# 添加相关动作
menu_file.addActions([action_new,action_save,action_saveAs,action_open,action_colse,
action_line1,action_pdf])
# 添加一个分割线
menu_file.addSeparator()
menu_file.addAction(action_setting)
# 设置action_open和action_save分别为默认按钮和活动按钮
menu_file.setDefaultAction(action_open)
menu_file.setActiveAction(action_save)
# action被点击时打印
menu_file.triggered.connect(lambda action:print("{}动作被点击了".format(action.text())))
menu_edit = QMenu("编辑",menubar)
menubar.addMenu(menu_edit)
action_undo = QAction(QIcon("C:/Users/Administrator/Pictures/撤销.png"), "撤销", self)
action_undo.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_Z))
action_redo = QAction(QIcon("C:/Users/Administrator/Pictures/重做.png"), "重做", self)
action_redo.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier | Qt.Key.Key_Z))
action_copy = QAction(QIcon("C:/Users/Administrator/Pictures/复制.png"), "复制", self)
action_copy.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_C))
action_paste = QAction(QIcon("C:/Users/Administrator/Pictures/粘贴.png"), "粘贴", self)
action_paste.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_V))
menu_edit.addActions([action_undo,action_redo,action_copy,action_paste])
menu_tool = QMenu("工具",self)
menu_tool.addActions([QAction(QIcon("man.jpeg"),"计算器",self),QAction("放大",self),QAction("缩小",self)])
# 设置带图片的action可被勾选,这个主要是图片小一点,能够显示勾选时的icon的效果
menu_tool.actions()[0].setCheckable(True)
menubar.addMenu(menu_tool)
#---------------------------创建一个工具栏并将三个菜单的action都添加进去-------------------------------
self.actionList = menu_file.actions() + menu_edit.actions() + menu_tool.actions()
toolBar = myToolBar(self)
toolBar.move(0,100)
# 设置工具栏中action的文本显示在icon的右侧,设置这个,低优先的action将不会显示文本
toolBar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
# 创建工具栏的右键菜单
toolBar_customMenu = QMenu("自定义工具栏",menubar)
# 循环项工具栏和工具栏的右键菜单中添加action
for action in self.actionList:
# 剔除分割线
if not action.isSeparator():
icon = action.icon()
text = action.text()
# 为工具栏的右键菜单复制一组新的icon,如果还是用菜单栏菜单的,有图标,如果禁用图标,菜单栏中菜单和
# 工具栏的右键菜单都会禁用,所以,最好是复制一组。
newAction = QAction(icon,text,action)
# 设置可选中
newAction.setCheckable(True)
# 设置在右键菜单中不显示图标
newAction.setIconVisibleInMenu(False)
toolBar_customMenu.addAction(newAction)
toolBar.addAction(action)
# 设置action在右键菜单中显示快捷键
action.setShortcutVisibleInContextMenu(False)
# 设置action的快捷键环境为全局
action.setShortcutContext(Qt.ShortcutContext.ApplicationShortcut)
# 隐藏工具栏的action
if action not in menu_tool.actions():
newAction.setChecked(True)
else:
action.setVisible(False)
# 工具栏的右键菜单的action链接到槽函数,槽函数的作用是勾选显示,取消勾选隐藏
newAction.toggled.connect(lambda tog: self.toolbarSet(tog))
# 设置自定义右键菜单
toolBar.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
toolBar.setMouseRightMenu(toolBar_customMenu)
#------------创建一个新的工具栏,主要看action中添加子菜单在工具栏中的效果------------
toolBarNew = QToolBar(self)
toolBarNew.move(0,300)
action_test = QAction("test",self)
action_test.triggered.connect(lambda :print("hehe"))
menu_test = QMenu("测试",self)
menu_test.triggered.connect(lambda :print("haha"))
menu_test.addActions([QAction("action1",self),QAction("action2",self),QAction("action3",self)])
action_test.setMenu(menu_test)
toolBarNew.addAction(action_test)
#--------------设置主面板的右键菜单--------------
self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
self.addActions(toolBar.actions())
# 设置应用程序的样式,Fusion的样式对于活动action、默认action、勾选状态显示的比较清晰
app.setStyle(QStyleFactory.create("Fusion"))
# 这是个坏习惯,不要学
self.toolbar = toolBar
def toolbarSet(self,toggle):
"""
这个槽函数主要是为工具栏右键菜单服务,当勾选相应的action,在工具栏中显示action
勾选去掉,隐藏action
"""
# 前面已经将newAction通过父子关系进行关联,好找。
# 这里的self.sender()就是被操作的action,这就是官方文档里说的通常将action的父对象设置为
# 顶层父窗口的好处
senderFather = self.sender().parent()
index = self.actionList.index(senderFather)
senderFatherNextAction = None
if index < len(self.actionList) - 1:
senderFatherNextAction = self.actionList[index+1]
else:
senderFatherNextAction = senderFather
if senderFather.isVisible() and not toggle:
senderFather.setVisible(False)
else:
senderFather.setVisible(True)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = testWindow()
win.show()
sys.exit(app.exec())
运行结果:
20241002-233343
三、动作组QActionGroup
QActionGroup是一个基类,用于将继承QAction对象的类分组在一起。它类似于QButtonGroup的用法。
在某些情况下,将QAction对象分组在一起是有用的。例如,如果有一个左对齐操作、右对齐操作、对齐操作和居中操作,那么在任何时候都应该只有其中一个操作是活动的。实现这一点的一个简单方法是将操作分组在一个操作组中,继承QActionGroup。
1.QActionGroup的相关方法
API函数 | 参数说明 | 返回值 | 功能作用 |
QActionGroup(parent) | parent: QWidget | None | 创建一个动作组 |
addAction(self,action) | action: QAction | None | 在动作组中添加新动作 |
addAction(self,text) | text:str | None | 在动作组中添加新动作 |
addAction(self,icon,text) | icon: QIcon text:str | None | 在动作组中添加新动作 |
actions(self) | None | list[QAction] | 获取添加至动作组的动作列表 |
checkedAction(self) | None | QAction | 返回组中当前选中的动作,如未选中,则返回None |
setEnabled(self, arg__1) | arg_1: bool | None | 设置动作组中所有动作是否启用 |
isEnabled(self) | None | bool | 获取动作组中所有动作是否启用 |
setVisible(self, arg__1) | arg_1: bool | None | 设置整组动作是否可见 |
isVisible(self) | None | bool | 获取整组动作是否可见 |
setDisabled(self, b) | b:bool | None | 设置整组动作是否失效 |
setExclusive(self, arg__1) | b:bool | None | 设置动作组是否启用动作间互斥 |
isExclusive(self) | None | b:bool | 获取动作组是否启用动作间互斥 |
setExclusionPolicy(self, policy) | policy: QActionGroup. ExclusionPolicy | None | 设置动作组的动作之间的互斥勾选策略 |
exclusionPolicy(self) | None | QActionGroup. ExclusionPolicy | 获取动作组的动作之间的互斥勾选策略 |
removeAction(self, a) | a:QAction | None | 从动作组中移除一个指定动作 |
动作组的方法基本都比较简单,这里主要说一下互斥勾选策略setExclusionPolicy(self, policy),它的参数是一个枚举类。默认情况下是Exclusive。
枚举类 | 枚举常量 | 枚举值 | 功能描述 |
QActionGroup | None | 组中的操作可以彼此独立地进行检查。 | |
Exclusive | 每次只能检查一个操作。这是默认策略。 | ||
ExclusiveOptional | 每次最多只能检查一个动作。这些操作也可以是未检查的。 |
setExclusive()方法是一个方便函数,当其参数为True时,相当于setExclusionPolicy(QActionGroup.Exclusive),参数为False时相当于setExclusionPolicy(QActionGroup.None)。
2.QActionGroup的信号
信号 | 参数说明 | 返回值 | 功能作用 |
hovered(arg_1) | arg_1:QAction | None | 光标划过动作组时发送信号 |
tiggered(bool) | bool | None | 单击动作或按快捷键时发送信号 |
四、动作面板QWidgetAction
1.QWidgetAction的介绍(官翻)
QWidgetAction是QAction的一个子类。
应用程序中的大多数action都表现为菜单中的项或工具栏中的按钮。然而,有时需要更复杂的部件。例如,文字处理器中的缩放操作可以使用QToolBar中的QComboBox实现,表示不同的缩放级别范围。QToolBar提供了insertWidget()函数,方便插入单个小部件。但是,如果您想实现一个在多个容器中使用自定义窗口组件进行可视化的操作,则必须子类化QWidgetAction。
如果一个QWidgetAction被添加到一个QToolBar,那么createWidget()就会被调用。该函数的重新实现应该创建一个具有指定父组件的新自定义部件。
如果QWidgetAction从容器小部件中移除,则使用之前创建的自定义小部件作为参数调用deleteWidget()。默认实现隐藏小部件,并使用QObject::deleteLater()删除它。
如果只有一个自定义部件,那么可以使用setDefaultWidget()将其设置为默认部件。如果将操作添加到QToolBar,或者通常添加到支持QWidgetAction的操作容器,则将使用该小部件。如果只带有默认小部件的QWidgetAction同时添加到两个工具栏中,则默认小部件只显示在添加该操作的第一个工具栏中。QWidgetAction接管默认小部件的所有权。
请注意,激活操作是由部件来完成的,例如通过重新实现鼠标事件处理程序并调用QAction::trigger()。
macOS:如果你在macOS上向应用程序菜单栏的菜单中添加一个widget, widget将被添加并正常工作,但有一些限制:
- 小部件从QMenu重新父化到本机菜单视图。如果你在其他地方显示菜单(例如,作为一个弹出菜单),小部件将不存在。
- 无法处理widget的焦点/键盘。
- 由于苹果的设计,widget上的鼠标跟踪目前无法工作。
- 将triggered()信号连接到打开模态对话框的插槽将导致macOS 10.4中的崩溃(Apple承认的已知bug),解决方法是使用QueuedConnection而不是DirectConnection。
官方翻译的内容多是针对QToolBar(工具栏)举例,工具栏将在下一章学习,会在例子中具体展现QWidgetAction的使用。放在这里的原因,看下图:
上图是office的Word中设置表格的边框样式,想要实现这样的面板。就必须借助QWidgetAction来完成。前面说了QWidgetAction继承自QAction,那么他实质上也就是QAction,当然就可以放在菜单中使用。具体用法,我们从其相关方法中进行学习。
2.QWigetAction的相关方法
API函数 | 参数说明 | 返回值 | 功能作用 |
QWidgetAction(parent) | parent:(QObject) | None | 创建一个QWidgetAction对象 |
createdWidgets(self) | None | List[QWidget] | 返回一个QWidget的列表。这个列表是通过creatWidget()添加到Widget的并且正在使用的Action的列表。 |
createWidget(self, parent) | parent: Optional[QWidget] | Optional[QWidget] | 每当将Wigetaction被添加到支持自定义部件的容器部件时,都会调用此函数。如果不希望自定义小部件被用作指定父小部件中的操作的表示,则应该返回0。 |
defaultWidget(self) | None | Optional[QWidget] | 返回默认设置的wiget |
deleteWidget(self, widget) | widget: Optional[QWidget] | None | 每当从一个容器小部件中删除widgetAction时,就会调用这个函数,该容器小部件是之前使用createWidget()创建的自定义的QWidgetAction。默认实现隐藏小部件,并使用QObject.deleteLater()将其安排为删除。 |
releaseWidget(self, widget) | widget: Optional[QWidget] | None | 释放指定的widget。 支持操作的容器部件在移除小部件操作时调用此函数。 |
requestWidget(self, parent) | widget: Optional[QWidget] | Optional[QWidget] | 返回一个指定父对象的代表一个QwigetAction的widget。支持操作的容器小部件可以调用这个函数来请求一个小部件作为操作的可视化表示。 |
setDefaultWidget(self, w) | w: Optional[QWidget] | None | 设置默认的widget。 |
创建一个QWidgetAction对象,通过setDefaultWidget()方法添加需要添加的控件,再将QWidgetAction对象添加到菜单或者工具栏中。从菜单或工具栏中移除QWidgetAction对象,只需要同action一样,调用remove(action)即可。而如果想从QWidgetAction中删除控件就需要使用deleteWidget()和releaseWidget()这两个方法,这两个方法的区别在于deleteWidget()会彻底删除控件,而releaseWidget()只是从QWidgetAction中移出。
createWidget( )是菜单栏或者工具栏装载QWidgetAction对象时调用,而非QWidgetAction对象装载默认控件,这个要区别开。
requestWidget( )方法看说明有点绕,实际他是针对自定义QWidgetAction来说的。比如,当我们自定义一个QWidgetAction,这个QWidgetAction并不想装载进Pyside已经提供的能投将action可视化容器(action放入菜单、菜单栏、工具栏能投看到如item和button的外观),而是放入看不见外观的容器,如QWidget、QDialog、QFrame等。此时,我们又想将其展现出来供用户操作,我们就需要在装载进QWidgetAction中的widget指定一个来代表QWidgetAction。好在奥运会中某比赛项目,我们需要选出一个人来代表中国一样的道理。
通常情况下,我们只需要使用setDefaultWidget()和releaseWidget()就能够满足需要。
为了实现开始图中word线型面板的效果,我们使用QWidgetAction来完成一个例子:
examp:这个例子是在上一个例子的基础上添加的代码,自定义了一个lab用来绘制各种线型和颜色的直线。然后,创建一个QWigetAction,设置其默认的控件为一个QWidget。然后,我们将自定义的lab放进QWidget中。这个例子中有没学过的事件和布局,了解一下就好,主要是看QWidgeActiont的setDefaultWidget()方法用法。
自定义lab:
class mylab(QLabel):
# 初始化方法,需要传入几个参数来为每个lab设置线宽和颜色
def __init__(self,i,j,parent):
super(mylab, self).__init__(parent)
self.color = j
self.lineWidth = i
self.colorList = ["#EEE8AA","#DA70D6","#00FF00","#8FBC8F","#ADFF2F","#00BFFF","#DC143C"]
# 临时变量,用于鼠标进入和离开绘制事件的激活
self.Enter = False
# 重写绘制事件
def paintEvent(self, a0):
super(mylab, self).paintEvent(a0)
# 在lab上绘制不同颜色和不同线宽的直线
painter = QPainter(self)
pen = QPen()
pen.setColor(QColor(self.colorList[self.color]))
p1 = self.mapFromParent(QPoint(self.x() + 5, int(self.y() + self.height() / 2)))
p2 = self.mapFromParent(QPoint(self.x() + self.width() - 5, int(self.y() + self.height() / 2)))
p3 = self.mapFromParent(QPoint(self.x() + 5, self.y() + int(self.height() / 2 + 3)))
p4 = self.mapFromParent(QPoint(self.x() + self.width() - 5, self.y() + int(self.height() / 2 + 3)))
if self.lineWidth == 0:
pen.setWidth(1)
painter.setPen(pen)
painter.begin(self)
painter.drawLine(p1,p2)
painter.end()
elif self.lineWidth == 1:
pen.setWidth(3)
painter.setPen(pen)
painter.begin(self)
painter.drawLine(p1,p2)
painter.end()
else:
pen.setWidth(1)
painter.setPen(pen)
painter.begin(self)
painter.drawLine(p1, p2)
painter.drawLine(p3, p4)
painter.end()
# 鼠标进入时,填充颜色和绘制边框,以显示进入效果
if self.Enter:
rect = self.rect()
drawRect = QRect(rect.x()+1,rect.y()+1,rect.width()-2,rect.height()-2)
painter1 = QPainter(self)
pen1 = QPen()
pen1.setColor(QColor("#FF69B4"))
pen1.setStyle(Qt.PenStyle.DashLine)
painter1.setPen(pen1)
color = QColor("#FFF0F5")
color.setAlpha(100)
brush = QBrush(color,Qt.BrushStyle.SolidPattern)
painter1.setBrush(brush)
painter1.begin(self)
painter1.fillRect(self.rect(),brush)
painter1.drawRect(drawRect)
painter1.end()
def enterEvent(self, event):
super(mylab, self).enterEvent(event)
self.Enter = True
# 调用绘制事件
self.update()
def leaveEvent(self, a0):
super(mylab, self).leaveEvent(a0)
self.Enter = False
self.update()
下面的代码添加到上一个例子的menu_edit菜单中:
# 创建一个边框动作
action_frame = QAction("边框",self)
# 创建一个主体边框子菜单
menu_actionwidget = QMenu("主体边框",self)
# 将子菜单设置给边框动作
action_frame.setMenu(menu_actionwidget)
# -------- QWidgetAction对象的创建与设置-------
# 创建一个QWidgetAction对象
actionWidget = QWidgetAction(self)
# 创建一个默认控件
defaultWidget = QWidget()
# 创建一个默认控件的主布局为垂直布局并设置间距和内容编剧
mainLayout = QVBoxLayout()
mainLayout.setSpacing(0)
mainLayout.setContentsMargins(0,0,0,0)
# 创建一个顶部的“主体边框”lab并设置文字对齐和北京色
headLabel = QLabel("主题边框",defaultWidget)
headLabel.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
headLabel.setStyleSheet("background:#EE82EE")
headLabel.setFixedHeight(30)
# 创建一个底部的按钮并设置
colorBtn = QPushButton(defaultWidget)
colorBtn.setIcon(QIcon("边框.png"))
colorBtn.setText("边框取色器(&S)")
colorBtn.setStyleSheet("QPushButton{text-align: left;padding-left: 0px;}")
colorBtn.setFlat(True)
colorBtn.setFixedHeight(30)
# 创建中间部分放置显示线型和颜色的lab的子布局,并将lab进行创建且添加到布局中
layout = QGridLayout()
for i in range(3):
for j in range(7):
lab = mylab(i,j,defaultWidget)
lab.setFixedWidth(100)
lab.setFixedHeight(50)
layout.addWidget(lab,i,j)
# 利用QFrame作为分割线
line = QFrame()
line.setFrameStyle(QFrame.Shape.HLine)
line.setStyleSheet("background:white")
# 按顺序将控件和子布局添加到主布局
mainLayout.addWidget(headLabel)
mainLayout.addLayout(layout)
mainLayout.addWidget(line)
mainLayout.addWidget(colorBtn)
# 将主布局设置给默认控件
defaultWidget.setLayout(mainLayout)
# 设置actionWidget的默认控件为defaultWidget
actionWidget.setDefaultWidget(defaultWidget)
# 将actionWidget添加到子菜单
menu_actionwidget.addAction(actionWidget)
# 最后要将action_frame添加到menu_edit菜单中
menu_edit.addAction(action_frame)
运行结果:
20241003-190016
五、菜单栏QMenuBar
菜单栏前面已经用过很多,方法都很简单,看说明就能知道用途,这里不再解释。只讲方法和信号罗列供查阅。需要说明的是菜单栏的转角控件就是个鸡肋,用不到。
1.QMenuBar的相关方法
API函数 | 参数说明 | 返回值 | 功能作用 |
QMenuBar(parent) | parent:QWidget | None | 创建一个菜单栏 |
addMenu(self, icon, title) | icon: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], title: str | QAction | 添加一个菜单 |
addMenu(self,menu) | menu:QMenu | QMenu | 添加一子菜单 |
addMenu(self,title) | title: str | QMenu | 添加一子菜单 |
addAction(self,action) | action:QAction | None | 在菜单栏中添加新动作 |
addAction(self,text) | text:str | QAction | 在菜单栏中添加新动作 |
addAction(self,icon,text) | icon:QIcon text:str | QAction | 在菜单栏中添加新动作 |
insertMenu(self, before, menu) | before:QAction menu:QMenu | QAction | 在指定动作前插入子菜单 |
addSeparator(self) | None | QAction | 添加一个分割线 |
insertSeparator(self, before) | before: QAction, | QAction | 在指定动作前插入分隔条 |
clear() | None | None | 清空菜单栏中的所有菜单和动作 |
setVisible(self, visible) | visible:bool | None | 设置菜单是否可见 |
setActiveAction(self, act) | act:QAction | None | 设置指定动作为活跃动作,高亮显示 |
activeAction(self) | None | QAction | 获取活跃的动作 |
actionAt(self, arg__1) | arg_1:QPoint | QAction | 获取指定坐标处的动作 |
actionGeometry(self, arg__1) | arg_1: QAction | QRect | 返回动作的几何矩形 |
setCornerWidget(self, w, corner) | w:QWidget corner:QtCore.Qt.Corner = Instance(Qt.TopRightCorner | None | 在菜单栏的角落添加控件 |
cornerWidget(self, corner) | corner:QtCore.Qt.Corner = Instance(Qt.TopRightCorner | QWidget | 获取角落位置控件 |
setDefaultUp(self, arg__1) | arg_1:bool | None | 设置菜单是否向上弹出。默认情况下是向下弹出。 |
isDefaultUp(self) | None | bool | 获取菜单是否向上弹出。默认返回值为False |
2.QMenuBar的信号
信号 | 参数说明 | 返回值 | 功能作用 |
hovered(QAction) | QAction | None | 光标划过菜单栏动作时发射此信号 |
triggered(QAction) | QAction | None | 菜单栏上的菜单或动作被点击是发送此信号,参数为引起信号发出的动。 |