3.7 QMainWindow与QAction
3.7.1 功能简介
QMainWindow是主窗体类,可以作为一个应用程序的主窗体,具有主菜单栏、工具栏、状态栏等主窗体常见的界面元素。
QAction是直接从QObject继承而来的一个类,不是一个可视组件。QAction就是一个实现某些功能的“动作”,可以为其编写槽函数,使用一个QAction对象可以创建菜单项、工具栏按钮,点击菜单项或工具栏按钮就执行了关联的Action的槽函数。
本节的示例Demo3_7主要演示QMainWindow和QAction的使用,程序运行时界面如图3-10所示。
图3-10 示例Demo3_7运行时界面
示例Demo3_7是一个简单的文本编辑器,窗体中间是一个QPlainTextEdit组件。本示例的设计实现过程涉及较多的技术点,具体如下。
· 可视化设计Action,通过Action可视化设计主菜单、工具栏。可复选的Action,如设置字体的粗体、斜体、下划线的3个Action;分组互斥型可复选的Action,如选择界面语言的两个Action。
· 可视化地将Action与QPlainTextEdit组件的槽函数关联,实现剪切、复制、粘贴、撤销等常见的编辑操作。
· 根据文本框里当前选择内容的变化,更新相关Action的状态,例如,更新“剪切”“复制”“粘贴”的Enabled属性,更新“粗体”“斜体”“下划线”的Checked属性。
· 在窗体的构造函数里,通过编写代码在工具栏上创建用于字体大小设置的QSpinBox组件和用于字体选择的QFontComboBox组件,因为这两个组件在窗体可视化设计时无法放置到工具栏上。
· 在窗体的构造函数里,编写代码在窗体的状态栏上添加QLabel和QProgressBar组件,编写代码创建QActionGroup分组对象,将选择界面语言的两个Action添加到分组,实现互斥选择。
· 为QPlainTextEdit组件提供标准右键快捷菜单响应。
本示例从mainWindowApp项目模板创建,窗体UI文件是MainWindow.ui,窗体业务逻辑类QmyMainWindow所在的文件是myMainWindow.py。下面是myMainWindow.py文件的import部分、QmyMainWindow类的构造函数和窗体测试部分的代码。import部分显示了本示例用到的一些类及其所在的模块,构造函数完成窗体构建时的初始化工作,其他功能实现的代码在讲解过程中再列出。
import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QActionGroup, QLabel, QProgressBar, QSpinBox, QFontComboBox) from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtGui import QTextCharFormat, QFont 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.__buildUI() #动态创建组件,添加到工具栏和状态栏 self.__spinFontSize.valueChanged[int].connect( self.do_fontSize_Changed) #字体大小设置 self.__comboFontName.currentIndexChanged[str].connect( self.do_fontName_Changed) #字体选择 self.setCentralWidget(self.ui.textEdit) ## ===========窗体测试程序================================= if __name__ == "__main__": ##用于当前窗体测试 app = QApplication(sys.argv) #创建GUI应用程序 form=QmyMainWindow() #创建窗体 form.show() sys.exit(app.exec_())
在构造函数里完成基本窗体的创建后,调用自定义函数__buildUI()创建几个无法在窗体可视化设计时放置到窗体上的界面组件,并添加到工具栏和状态栏,还为在函数__buildUI()里创建的两个组件self.__spinFontSize和self.__comboFontName的信号关联自定义槽函数。
3.7.2 窗体可视化设计
1.设计Action
在UI Designer里对窗体MainWindow.ui进行可视化设计,在UI Designer里有一个Action编辑器用于Action的设计。本示例设计好的Action如图3-11所示,根据图标和文字就基本能知道每个Action的作用。
图3-11 Action编辑器里已经设计好的Action
在Action编辑器的上方有一个工具栏,可以新建、复制、粘贴、删除Action,还可以设置Action列表的显示方式。若要编辑某个Action,在列表里双击该Action即可,单击工具栏上的“New”按钮可以新建一个Action。新建或编辑Action的对话框如图3-12所示。
图3-12 新建或编辑一个Action
在图3-12所示的对话框里有以下的一些设置。
· Text:Action的显示文字,该文字会作为菜单标题或工具栏按钮标题显示。若该标题后面有省略号(一般用于打开对话框的操作),如“打开…”,则在工具栏按钮上显示时会自动忽略省略号,只显示“打开”。
· Object name:该Action的objectName。应该遵循自己的命名规则,例如以“act”开头表示这是一个Action,在Action较多时还应该采用分组。如图3-12所示的是打开文件的Action,命名为actFile_Open,表示它属于“文件”分组。
· ToolTip:这个文字内容是当鼠标在一个菜单项或工具栏按钮上短暂停留时出现的提示文字。
· Icon:设置Action的图标,单击其右边的按钮可以从资源文件里选择图标,或直接选择图片文件作为图标。
· Checkable:设置Action是否可以被复选,如果选中此选项,那么该Action就类似于QCheckBox,可以改变其复选状态。
· Shortcut:设置快捷键,将输入光标移动到Shortcut旁边的编辑框里,然后按下想要设置的快捷键即可,如Ctrl+O。
做好这些设置后,单击“OK”按钮就可以新建或修改Action了。所有用于菜单和工具栏设计的功能都需要用Action来实现。
2.设计菜单和工具栏
建立Action之后,就可以在主窗体上设计菜单和工具栏了。本示例的窗体类是从QMainWindow继承的,具有菜单栏、工具栏和状态栏。在可视化设计窗体时,使用主窗体的右键快捷菜单可以添加或删除工具栏、菜单栏和状态栏。
已完成设计的窗体设计时的效果如图3-13所示。在窗体最上方是菜单栏,菜单栏下方是工具栏,最下方是状态栏。中间工作区放置了一个QPlainTextEdit组件,其objectName设置为textEdit。
图3-13 设计时的窗体(已完成界面可视化设计)
要设计菜单栏,在菜单栏显示“Type Here”的地方双击,出现一个编辑框,在编辑框里输入所要设计菜单的分组名称,如“文件(&F)”,然后回车,这样就创建了一个“文件(F)”菜单分组,在程序运行时通过快捷键Alt+F可以快捷打开“文件”菜单。同样可以创建“编辑(E)”“格式(M)”分组。
创建主菜单的分组后,从Action编辑器的列表里将一个Action拖放到菜单某个分组下,就可以创建一个菜单项,如同在界面上放置一个组件一样。如果需要在菜单里增加一个分隔条,双击“Add Separator”就可以创建一个分隔条,然后拖动到需要的位置即可。如果需要删除某个菜单项或分隔条,单击右键,选择“Remove”菜单项。如果要为一个菜单项创建下级菜单,则点击菜单项右边的图标,拖放Action到下级菜单上就可以创建菜单项。菜单设计的结果如图3-14所示。
图3-14 完成后的菜单栏各分组下的菜单项
工具栏上的按钮也通过Action创建。将一个Action拖放到窗体的工具栏上,就会新建一个工具栏按钮。同样可以在工具栏上添加分隔条,可以移除工具栏按钮。主窗体上初始只有一个工具栏,如果需要设计多个工具栏,在主窗体上单击右键,在快捷菜单中单击“Add Tool Bar”即可新建一个工具栏。
工具栏上的按钮的显示方式有很多种,只需设置工具栏的toolButtonStyle属性,它是Qt.ToolButtonStyle枚举类型,默认是Qt.ToolButtonIconOnly,即只显示按钮的图标。还可以设置为:
· Qt.ToolButtonTextBesideIcon(文字显示在按钮旁边);
· Qt.ToolButtonTextOnly(只显示文字);
· Qt.ToolButtonTextUnderIcon(文字显示在按钮下方)。
在可视化设计窗体时,只能将Action拖放到工具栏上生成按钮,不能将其他组件拖放到工具栏上,如图3-10工具栏上的SpinBox和FontComboBox组件是在业务逻辑类QmyMainWindow的构造函数里用代码创建的。
3.编辑类Action的功能实现
“编辑”分组的Action实现对窗口上的QPlainTextEdit组件textEdit的一些编辑操作,如剪切、复制、撤销、全选等。这些功能无须自己编写代码来实现,QPlainTextEdit提供了实现这些编辑功能的槽函数,如cut()、copy()、paste()、undo()等,只需将这些Action和相应的槽函数关联即可。
图3-15是本示例在信号与槽编辑器里设置的关联,可以看见所有的编辑操作的Action的triggered()信号都与textEdit的相应的槽函数建立了关联。
图3-15 信号与槽编辑器里设置信号与槽的关联
另外,textEdit的信号undoAvailable(bool)与actEdit_Undo的槽函数setEnabled(bool)关联,可以自动设置actEdit_Undo的使能状态;redoAvailable(bool)信号与actEdit_Redo的槽函数setEnabled(bool)关联,可以自动设置actEdit_Redo的使能状态。
3.7.3 界面操作功能的代码实现
1.动态创建界面组件
可视化设计时无法在工具栏上添加除Action之外的其他组件,状态栏也无法可视化添加组件,但是可以使用代码在窗体创建后再动态添加组件。另外,设置界面语言的两个互斥型Action需要为其创建QActionGroup分组才能实现互斥选择。
QmyMainWindow类的构造函数里调用的函数__buildUI()就实现这些功能,下面是__buildUI()函数的代码:
def __buildUI(self): ##窗体上动态添加组件 ##创建状态栏上的组件 self.__LabFile=QLabel(self) #QLabel组件显示信息 self.__LabFile.setMinimumWidth(150) self.__LabFile.setText("文件名: ") self.ui.statusBar.addWidget(self.__LabFile) #添加到状态栏 self.__progressBar1=QProgressBar(self) #progressBar1 self.__progressBar1.setMaximumWidth(200) self.__progressBar1.setMinimum(5) self.__progressBar1.setMaximum(50) sz=self.ui.textEdit.font().pointSize() #字体大小 self.__progressBar1.setValue(sz) self.ui.statusBar.addWidget(self.__progressBar1) #添加到状态栏 self.__LabInfo=QLabel(self) #QLabel组件显示字体名称 self.__LabInfo.setText("选择字体名称: ") self.ui.statusBar.addPermanentWidget(self.__LabInfo) #添加到状态栏 ##为actLang_CN和actLang_EN创建QActionGroup,互斥型选择 actionGroup= QActionGroup(self) actionGroup.addAction(self.ui.actLang_CN) actionGroup.addAction(self.ui.actLang_EN) actionGroup.setExclusive(True) #互斥型分组 self.ui.actLang_CN.setChecked(True) ##创建工具栏上的组件 self.__spinFontSize=QSpinBox(self) #字体大小spinbox self.__spinFontSize.setMinimum(5) self.__spinFontSize.setMaximum(50) sz=self.ui.textEdit.font().pointSize() self.__spinFontSize.setValue(sz) self.__spinFontSize.setMinimumWidth(50) self.ui.mainToolBar.addWidget(self.__spinFontSize) #添加到工具栏 self.__comboFontName=QFontComboBox(self) #字体combobox self.__comboFontName.setMinimumWidth(100) self.ui.mainToolBar.addWidget(self.__comboFontName) #添加到工具栏 self.ui.mainToolBar.addSeparator() #添加一个分隔条 self.ui.mainToolBar.addAction(self.ui.actClose) #添加"关闭"按钮
对于这段代码,需要注意以下几点。
(1)动态创建的组件定义为QmyMainWindow类的私有变量,例如self.__LabFile, self.__progressBar1,它们不是可视化设计的窗体的元素。可视化设计的窗体的元素用self.ui访问,如self.ui.statusBar, self.ui.mainToolBar,这样可以很容易地将可视化设计的窗体的界面组件与动态创建的界面组件区分开来,这也是采用单继承方式设计QmyMainWindow类的一个特点。
(2)状态栏QStatusBar的addWidget()函数将一个组件添加到状态栏,按添加的顺序从左到右排列,addPermanentWidget()添加的组件则位于状态栏的最右边。
(3)工具栏QToolBar添加组件涉及以下3个函数。
· addWidget():添加一个界面组件到工具栏,如QSpinBox组件、QLabel组件等。
· addAction():添加一个QAction对象并创建工具栏按钮。
· addSeparator():添加一个分隔条。
对于工具栏上动态创建的两个组件,必须手动设置其信号与自定义槽函数的关联,这在QmyMainWindow的构造函数里实现。这两个自定义槽函数分别设置选择的文本的字体大小和字体名称,代码如下:
@pyqtSlot(int) ##设置字体大小,关联self.__spinFontSize def do_fontSize_Changed(self, fontSize): fmt=self.ui.textEdit.currentCharFormat() fmt.setFontPointSize(fontSize) self.ui.textEdit.mergeCurrentCharFormat(fmt) self.__progressBar1.setValue(fontSize) @pyqtSlot(str) ##选择字体名称,关联self.__comboFontName def do_fontName_Changed(self, fontName): fmt=self.ui.textEdit.currentCharFormat() fmt.setFontFamily(fontName) self.ui.textEdit.mergeCurrentCharFormat(fmt) self.__LabInfo.setText("字体名称:%s "%fontName)
2.设置字体的Action的代码
QAction常用的信号是triggered()和triggered(bool),它们是overload型信号。在关联的菜单项或按钮被点击时会发射此信号,triggered()是默认的信号,triggered(bool)是带有复选状态参数的信号。
“粗体”“斜体”“下划线”3个用于设置字体的Action使用triggered(bool)信号生成槽函数,需要用@pyqtSlot()进行参数说明。这3个槽函数代码如下:
@pyqtSlot(bool) ##设置粗体 def on_actFont_Bold_triggered(self, checked): fmt=self.ui.textEdit.currentCharFormat() if (checked == True): fmt.setFontWeight(QFont.Bold) else: fmt.setFontWeight(QFont.Normal) self.ui.textEdit.mergeCurrentCharFormat(fmt) @pyqtSlot(bool) ##设置斜体 def on_actFont_Italic_triggered(self, checked): fmt=self.ui.textEdit.currentCharFormat() fmt.setFontItalic(checked) self.ui.textEdit.mergeCurrentCharFormat(fmt) @pyqtSlot(bool) ##设置下划线 def on_actFont_UnderLine_triggered(self, checked): fmt=self.ui.textEdit.currentCharFormat() fmt.setFontUnderline(checked) self.ui.textEdit.mergeCurrentCharFormat(fmt)
这里用QPlainTextEdit类的currentCharFormat()来获取当前选择的文本的格式,它是一个QTextCharFormat类的实例,修改其相应属性后再设置为选中文本的格式。
3.QPlainTextEdit其他信号的使用
QPlainTextEdit还有3个可以利用的信号,其槽函数代码如下:
def on_textEdit_copyAvailable(self, avi): ##文本框内容可复制 self.ui.actEdit_Cut.setEnabled(avi) self.ui.actEdit_Copy.setEnabled(avi) self.ui.actEdit_Paste.setEnabled(self.ui.textEdit.canPaste()) def on_textEdit_selectionChanged(self): ##文本选择内容发生变化 fmt=self.ui.textEdit.currentCharFormat() self.ui.actFont_Bold.setChecked(fmt.font().bold()) self.ui.actFont_Italic.setChecked(fmt.fontItalic()) self.ui.actFont_UnderLine.setChecked(fmt.fontUnderline()) def on_textEdit_customContextMenuRequested(self, pos): ##标准右键菜单 popMenu=self.ui.textEdit.createStandardContextMenu() popMenu.exec(pos) #显示快捷菜单
copyAvailable(bool)信号在文本选择变化时发射,表示是否可以剪切和复制,所以其响应代码里设置actEdit_Cut和actEdit_Copy的使能状态。同时还利用QPlainTextEdit的canPaste()函数返回的值设置actEdit_Paste的使能状态。
selectionChanged()信号在选择的文本变化时发射,在此信号的响应槽函数里可以通过判断选择文本的格式,设置“粗体”“斜体”“下划线”等Action的Checked状态。
customContextMenuRequested(pos)信号在单击鼠标右键时发射,它是在父类QWidget里定义的一个信号,也就是任何界面组件都有这个信号。参数pos是QPoint类型,表示鼠标右键单击点的屏幕坐标。这个信号的响应代码一般用于创建右键快捷菜单,QPlainTextEdit的createStandardContextMenu()函数可以创建一个内建的标准的编辑功能快捷菜单。
4.其他功能代码的实现
还有其他4个Action的槽函数代码如下:
@pyqtSlot(bool) ##设置工具栏按钮样式 def on_actSys_ToggleText_triggered(self, checked): if(checked): st=Qt.ToolButtonTextUnderIcon else: st=Qt.ToolButtonIconOnly self.ui.mainToolBar.setToolButtonStyle(st) def on_actFile_New_triggered(self): ##新建文件,不实现具体功能 self.__LabFile.setText(" 新建文件 ") def on_actFile_Open_triggered(self): ##打开文件,不实现具体功能 self.__LabFile.setText(" 打开的文件 ") def on_actFile_Save_triggered(self): ##保存文件,不实现具体功能 self.__LabFile.setText(" 文件已保存 ")
actSys_ToggleText用于设置工具栏按钮样式,在有文字标签和无文字标签之间切换。
“新建”“打开”“保存”3个Action的具体功能并未实现,因为文件读写不是本示例的目的,只是在状态栏上的标签里显示信息。
设置界面语言的两个Action不需要编写任何代码,在运行时就可以实现互斥选择。这两个Action的功能实现在11.1节介绍多语言界面的实现时再具体介绍。