3 基本功能
参考自: pyqt5/firstprograms
在 PyQt5 教程的这一部分中,我们学习一些基本功能。这些示例显示了一个提示窗口(tooltip)和一个图标,关闭了一个窗口,显示了一个消息框,并在桌面上将窗口居中。
3.1 创建一个简单的窗口
每个 PyQt5 应用都必须创建一个应用对象
QApplication
。
app = QApplication(sys.argv)
其中 sys.argv
是一组命令行参数的列表。Python 可以在 shell 里运行,这个参数提供对脚本控制的功能。这是一种通过参数来选择启动脚本的方式。如果不需要传入参数,可以使用 []
代替 sys.argv
。
QApplication
管理 GUI 程序的控制流和主要设置。它包含由窗口系统和其他来源处理过和发送过的主事件循环。它也处理应用程序的初始化和收尾工作,并提供对话管理。它还可以对系统和应用的大部分设置项进行设置。对于用 Qt 写的任何一个 GUI 应用,不管这个应用有没有窗口或多少个窗口,有且只有一个 QApplication
对象。而对于用 Qt 写的非 GUI 应用,则有且只有一个 QCoreApplication
对象,并且这个应用不依赖 QtGui
库。
为了让 GUI 启动,需要使用 app.exec_()
启动事件循环(启动应用,直至用户关闭它)。app.exec_()
的作用是运行主循环,必须调用此函数才能开始事件处理,调用该方法进入程序的主循环直到调用 exit()
结束。主事件循环从窗口系统接收事件,并将其分派给应用程序小部件。如果没有该方法,那么在运行的时候还没有进入程序的主循环就直接结束了,所以运行的时候窗口会闪退。
app.exec_()
在退出时会返回状态代码(如果程序运行成功,则返回 0
,否则为非 0
)。也可以用 sys.exit(app.exec_())
(sys.exit(n)
的作用是退出应用程序并返回 n
到父进程)。我们进入了应用的主循环中,事件处理器这个时候开始工作。主循环从窗口上接收事件,并把事件传入到派发到应用控件里。当调用 exit()
方法或直接销毁主控件时,主循环就会结束。sys.exit()
方法能确保主循环安全退出。外部环境能通知主控件怎么结束。
这样便可以写出最小化的 PyQt5 代码:
import sys
from PyQt5.QtWidgets import QApplication
# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.
# If you know you won't use command line arguments QApplication([]) is fine.
app = QApplication(sys.argv)
# Start the event loop.
sys.exit(app.exec_())
# Your application won't reach here until you exit and the event
# loop has stopped.
此时运行,什么也没有出现?这是因为,我们没有创建一个可视化的窗口。
QWidget
控件(所有的窗口和控件都直接或者间接继承自 QWidget
)是一个用户界面的基础控件,它提供了基本的应用构造器。默认情况下,构造器是没有父级的,没有父级的构造器被称为窗口(window)。
这样,我们可以创建一个简单的窗口:
import sys
from PyQt5.QtWidgets import QApplication, QWidget
app = QApplication(sys.argv)
window = QWidget()
window.show() # IMPORTANT!!!!! Windows are hidden by default.
# Start the event loop.
app.exec_()
展示如下:
PyQt5 创建的窗口默认是隐藏的,需要调用 show()
显示。
QMainWindow
、QWidget
和 QDialog
三个类都是用来创建窗口的,可以直接使用也可以继承后来使用。QMainWindow
窗口可以包含菜单栏、工具栏、状态栏和标题栏等,是最常见的窗口形式,是 GUI 程序的主窗口。QDialog
是对话框的基类,对话框主要用来执行短期任务和与用户进行互动任务,有模态和非模态两种形式。QWidget
可以用作嵌入其他窗口。
本节仅仅介绍 QMainWindow
、QWidget
。
3.2 自定义窗口
窗口是整个程序的整体界面,有边框、标题栏、菜单栏、工具栏、关闭按钮、最小化按钮、最大化按钮等。控件是指按钮、复选框、文本框、表格、进度条等这些组成 GUI 的基本元素。一个程序可以有多个窗口,一个窗口可以有多个控件。
3.2.1 窗口坐标系统
PyQt5 使用统一的坐标系统来定义窗口控件的位置和大小。以屏幕(或窗口)的左上角为坐标原点 ,从左到右为 轴的正方向,从上到下为 轴的正方向。一般称窗口的原点与坐标轴围成的区域为客户区(Client Area)。
QWidget
的成员函数可分为三类:
QWidget
直接提供的成员函数:x()
、y()
获得窗口左上角坐标,width()
、height()
获得客户区的宽度和高度。
QWidget
的 geometry()
提供的成员函数:x()
、y()
获得客户区左上角的坐标,width()
、height()
获得客户区的宽度和高度。
QWidget
的 frameGeometry()
提供的成员函数:x()
、y()
获得窗口左上角,width()
、height()
获得客户区、标题栏和边框的整个窗口的的宽度和高度。
Qt 提供 Window and Dialog Widgets 详细说明了坐标系统,下图是其截图:
3.2.2 常用的几何结构
QWidget
包含两种常见结构:不包含边框的结构与包含边框的结构。
一般情况下,不包含边框的部分是客户区,即用户操作的界面,可以添加子控件。在 Qt 中使用 QRect
类保存了它的位置和大小。您可以对其进行修改:
改变客户区的大小:QWidget.resize(width, height)
。可以通过鼠标的来改变尺寸。
获得客户区的大小,宽度、高度分别使用:QWidget.size()
、QWidget.width()
、QWidget.height()
。
设定不可使用鼠标修改的宽度或者高度:QWidget.setFixedWidth(int width)
、QWidget.setFixedHeight(int height)
。
同时修改客户区的大小和位置,可以使用 QWidget.setGeometry(int x, int y, int width, int height)
。
包含边框的结构是窗口在屏幕上显示的整个区域。它包含如下函数:
获得窗口的大小和位置:QWidget.frameGeometry()
。
设置窗口的位置:QWidget.move(int x, int y)
。
获得窗口左上角坐标:QWidget.pos()
下面是一个简单的例子:
from PyQt5.QtWidgets import QApplication, QWidget
if __name__ == '__main__':
app = QApplication([])
w = QWidget()
#不同操作系统可能对窗口最小宽度有规定,若设置宽度小于规定值,则会以规定值进行显示
w.resize(250, 150)
# 以 QWidget 左上角为(0, 0)点
w.move(300, 300)
w.setWindowTitle('PyQt5 坐标系统例子') # 修改窗口的标题
w.show()
print('控件的左上角坐标,宽度,高度,以及尺寸分别为', (w.x(), w.y()), w.width(), w.height(), w.size())
print('客户区的左上角坐标,宽度,高度,以及尺寸分别为', (w.geometry().x(), w.geometry().y()),
w.geometry().width(), w.geometry().height(), w.geometry().size())
print('窗口的左上角坐标,宽度,高度分别为', w.pos(), w.frameGeometry().width(),
w.frameGeometry().height())
app.exec_()
展示效果如下:
其中 QWidget.setWindowTitle
用于修改窗口的标题。
3.2.3 设置窗口的图标
窗口图标通常是显示在窗口的左上角,标题栏的最左边。QWidget.setWindowIcon(QIcon('cartoon1.ico')
提供了设置图标的方法。在某些环境下,图标显示不出来。如果你遇到了这个问题,看我在Stackoverfolw的回答
import sys
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QApplication
class Icon(QWidget):
def __init__(self, parent = None):
super(Icon,self).__init__(parent)
self.initUI()
def initUI(self):
self.setGeometry(500, 500, 450, 150)
self.setWindowTitle('演示程序图标例子')
self.setWindowIcon(QIcon('7092.gif'))
if __name__ == '__main__':
app = QApplication(sys.argv)
icon = Icon()
icon.show()
sys.exit(app.exec_())
import sys
from PyQt5.QtWidgets import QWidget, QToolTip, QApplication
from PyQt5.QtGui import QFont
class Winform(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
QToolTip.setFont(QFont('SansSerif', 10))
self.setToolTip('这是一个<b>气泡提示</b>')
self.setGeometry(200, 300, 400, 400)
self.setWindowTitle('气泡提示')
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Winform()
win.show()
sys.exit(app.exec_())
3.4 创建主窗口类
QWidget
可以作为嵌套型的窗口存在,结构简单,而 QMainWindow
是一个程序框架,有自己的布局,可以在布局中添加控件,如将工具栏添加到布局管理器中。它常常作为 GUI 的主窗口。
主窗口在 GUI 程序是唯一的,是所有窗口中的最顶层窗口,其他窗口都是它的直接或间接子窗口。PyQt5 中,主窗口中会有一个控件(QWidget
)占位符来占着中心窗口,可以使用 setCentralWidget()
来设置中心窗口。QMainWindow
继承自 QWidget
类,拥有 QWidget
所有的派生方法和属性。QMainWindow
类中比较重要的方法,如下表:
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
class MainWidget(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
# 设置主窗体标签
self.setWindowTitle("QMainWindow 例子")
self.resize(400, 200)
self.status = self.statusBar()
# 5 秒后状态栏消失
self.status.showMessage("这是状态栏提示", 5000)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = MainWidget()
main.show()
sys.exit(app.exec_())
3.4.2 菜单栏
菜单栏是一组命令的集合:
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication
from PyQt5.QtGui import QIcon
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 创建一个图标、一个 exit 的标签和一个快捷键组合,都执行了一个动作
exitAct = QAction(QIcon('7092.gif'), '&Exit', self)
exitAct.setShortcut('Ctrl+Q')
# 创建了一个状态栏,当鼠标悬停在菜单栏的时候,能显示当前状态
exitAct.setStatusTip('Exit application')
# 当执行这个指定的动作时,就触发了一个事件。
## 这个事件跟 QApplication 的 quit() 行为相关联,所以这个动作就能终止这个应用。
exitAct.triggered.connect(qApp.quit)
self.statusBar()
# 创建菜单栏
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAct)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Simple menu')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
我们创建了只有一个命令的菜单栏,这个命令就是终止应用。同时也创建了一个状态栏。而且还能使用快捷键 Ctrl+Q
退出应用。QAction
是菜单栏、工具栏或者快捷键的动作的组合。
3.4.3 子菜单
子菜单是嵌套在菜单里面的二级或者三级等的菜单。
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, QMenu, QApplication
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
menubar = self.menuBar()
fileMenu = menubar.addMenu('File')
# 使用 QMenu 创建一个新菜单
impMenu = QMenu('Import', self)
impAct = QAction('Import mail', self)
impMenu.addAction(impAct)
newAct = QAction('New', self)
fileMenu.addAction(newAct)
fileMenu.addMenu(impMenu)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Submenu')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
from PyQt5.QtWidgets import QMainWindow, QAction, QApplication
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.statusbar = self.statusBar()
self.statusbar.showMessage('Ready')
menubar = self.menuBar()
viewMenu = menubar.addMenu('View')
viewStatAct = QAction('View statusbar', self, checkable=True)
viewStatAct.setStatusTip('View statusbar')
viewStatAct.setChecked(True) # 默认设置为选中状态
viewStatAct.triggered.connect(self.toggleMenu)
viewMenu.addAction(viewStatAct)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Check menu')
self.show()
def toggleMenu(self, state):
if state:
self.statusbar.show()
else:
self.statusbar.hide()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
本例创建了一个行为菜单。这个行为/动作能切换状态栏显示或者隐藏。用 checkable
选项创建一个能选中的菜单。
3.4.5 context 菜单
context menu 也叫弹出框,是在某些场合下显示的一组命令。例如,Opera 浏览器里,网页上的右键菜单里会有刷新,返回或者查看页面源代码。如果在工具栏上右键,会得到一个不同的用来管理工具栏的菜单。
为了使 context menu 生效,需要实现 contextMenuEvent()
方法。
import sys
from PyQt5.QtWidgets import QMainWindow, qApp, QMenu, QApplication
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.show()
def contextMenuEvent(self, event):
cmenu = QMenu(self)
newAct = cmenu.addAction("New")
openAct = cmenu.addAction("Open")
quitAct = cmenu.addAction("Quit")
# 使用 exec_() 方法显示菜单。从鼠标右键事件对象中获得当前坐标。
# mapToGlobal() 方法把当前组件的相对坐标转换为窗口(window)的绝对坐标。
action = cmenu.exec_(self.mapToGlobal(event.pos()))
if action == quitAct:
qApp.quit()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
菜单栏包含了所有的命令,工具栏就是常用的命令的集合。
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QFileInfo
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#root = QFileInfo(__file__).absolutePath() # 避免图标不显示,而使用绝对路径
exitAct = QAction(QIcon('paper.png'), 'Exit', self)
exitAct.setShortcut('Ctrl+Q')
exitAct.triggered.connect(qApp.quit)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAct)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Toolbar')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
3.5 主窗口居中显示
QMainWindow
利用 QDesktopWidget
类实现窗口居中显示,如下示例:
from PyQt5.QtWidgets import QDesktopWidget, QApplication, QMainWindow
import sys
class Winform(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('主窗口放在屏幕中间例子')
self.resize(300, 200)
self.center()
def center(self):
# 获取屏幕信息
screen = QDesktopWidget().screenGeometry()
size = self.geometry()
x = int((screen.width() - size.width()) / 2)
y = int((screen.height() - size.height()) / 2)
self.move(x, y)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Winform()
win.show()
sys.exit(app.exec_())
也可以直接使用 QDesktopWidget().availableGeometry().center()
设置窗口居中:
import sys
from PyQt5.QtWidgets import QWidget, QDesktopWidget, QApplication
class CenterWin(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(250, 150)
self.center()
self.setWindowTitle('Center')
self.show()
def center(self):
qr = self.frameGeometry() # 获得主窗口所在的框架
# 获取显示器的分辨率,然后得到屏幕中间点的位置
cp = QDesktopWidget().availableGeometry().center()
# 然后把主窗口框架的中心点放置到屏幕的中心位置
qr.moveCenter(cp)
# 然后通过 move 函数把主窗口的左上角移动到其框架的左上角,这样就把窗口居中了
self.move(qr.topLeft())
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = CenterWin()
sys.exit(app.exec_())
3.6 关闭窗口
关闭一个窗口最直观的方式就是点击标题栏的那个叉,接下来,我们展示的是如何用程序关闭一个窗口。这里我们将接触到一点 signals
和 slots
的知识。本例使用的是 QPushButton
组件类。
QPushButton(string text, QWidget parent = None)
text
参数是想要显示的按钮名称,parent
参数是放在按钮上的组件,在我们的例子里,这个参数是 QWidget
。应用中的组件都是一层一层的,在这个层里,大部分的组件都有自己的父级,没有父级的组件,是顶级的窗口(即主窗口)。
下面创建一个点击之后就退出窗口的按钮:
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
qbtn = QPushButton('Quit', self)
qbtn.clicked.connect(QApplication.instance().quit)
qbtn.resize(qbtn.sizeHint())
qbtn.move(50, 50)
self.setGeometry(300, 300, 350, 250)
self.setWindowTitle('Quit button')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
事件传递系统在 PyQt5 内建的 signal 和 slot 机制里面。点击按钮之后,信号会被捕捉并给出既定的反应。QCoreApplication
包含了事件的主循环,它能添加和删除所有的事件,instance()
创建了一个它的实例。QCoreApplication
是在 QApplication
里创建的。点击事件和能终止进程并退出应用的 quit
函数绑定在了一起。在发送者和接受者之间建立了通讯,发送者就是按钮,接受者就是应用对象。
3.7 消息盒子
默认情况下,我们点击标题栏的 X
按钮,QWidget
就会关闭。但是有时候,我们修改默认行为。比如,如果我们打开的是一个文本编辑器,并且做了一些修改,我们就会想在关闭按钮的时候让用户进一步确认操作。
import sys
from PyQt5.QtWidgets import QWidget, QMessageBox, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Message box')
self.show()
def closeEvent(self, event):
# 第一个字符串显示在消息框的标题栏,
# 第二个字符串显示在对话框,第三个参数是消息框的俩按钮,
# 最后一个参数是默认按钮,这个按钮是默认选中的。
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
# 这里判断返回值,如果点击的是Yes按钮,我们就关闭组件和应用,否者就忽略关闭事件。
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())