PyQt5是一个python库,用来调用Qt。Qt本身是一个C++的GUI framework。使用PyQt5,可以使得我们在不怎么损失C++的速度优势的情况下,利用Python创建一个gui应用。
基础
创建一个application
# py example1.py
import sys
from PyQt5.QtWidgets import QApplication
# 每个app都需要且只需要一个QApplication实例,可以将命令行参数传入其中。
# 如果确定不需要命令行参数,则传入[]即可
app = QApplication(sys.argv)
# 开启主事件循环
app.exec_()
# application不会运行到这个地方,直到你退出,并且主事件循环结束
以上是一个最简单的PyQt5应用,当然,运行它不会出现任何东西(不止如此,还无法正常关闭它),但它实际上确实work了,通过查看任务管理器可以知道。
每个application都需要且只需要一个QApplication示例,其管理着event loop。
event loop是GUI界面运行的标准模式。每次我们和图形界面的交互(按下按钮、点击鼠标)都会产生一个event对象,放入event queue中进行等待。event loop是一个无限循环的loop,其在每次迭代的时候都会进行检查,并找到在event queue等待的evnet,并将event和控制权移交给其对应的event handler,当event handler处理完事件后,将控制权返回给event loop,然后再去寻找下一个等待中的event。每个application中只会有一个event loop。
时刻注意查看任务管理器,很可能有许多程序我们没有正常关闭。
创建一个窗口
# py example2.py
import sys
from PyQt5.QtWidgets import QApplication, QWidget
app = QApplication(sys.argv)
window = QWidget()
window.show() # important! 窗口默认是隐藏的
# sys.exit(app.exec_())
app.exec_()
运行结果:

进入event loop后,Ctrl-C不会使其退出
如果怕无法有效的退出,可以使用下面的语句:
# py example2-2.py
import sys
from PyQt5.QtWidgets import QApplication, QWidget
try:
app = QApplication(sys.argv)
window = QWidget()
window.show() # important! 窗口默认是隐藏的
sys.exit(app.exec_())
finally:
pass
实际上,我们在PyQt5中看到的所有组件,都是一个QWidget,如果其没有parent,则其成为一个main window。但我们有一个更加好的创建主窗口的方式,即一个子类QMainWidget,其可以轻松的添加toolbars、statusbars和dockable等组件,所以我们还是用QMainWidget吧:
# py example2-3.py
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
app = QApplication(sys.argv)
window = QMainWindow()
window.show()
app.exec_()

这个相比上面的那个比较小。。。
实际上我们可以创建多个QMainWidget实例,程序会在最后一个实例关闭后退出。
添加一个组件
这里我们只添加一个widget QLabel。如果我们想要添加更多的组件的话,需要使用Qt layouts,但只添加一个暂时不需要。
# py example3.py
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow
from PyQt5.QtCore import Qt
# 这里使用面向对象的方法
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
label = QLabel("This is a PyQt5 window!")
# Qt namespace中有大量的特征用于自定义widget的行为,类似常量
label.setAlignment(Qt.AlignCenter)
# 将widget放入window的中央,默认widget将填满整个window
self.setCentralWidget(label)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

可能是因为字体的问题,还有下面的错误信息:
qt.qpa.fonts: Unable to open default EUDC font: "C:\\Users\\hasee\\AppData\\LocalLow\\SogouPY\\EUDC\\SGPYEUDC_2.TTE" qt.qpa.fonts: Unable to enumerate family ' "Droid Sans Mono Dotted for Powerline" ' qt.qpa.fonts: Unable to enumerate family ' "Droid Sans Mono Slashed for Powerline" ' qt.qpa.fonts: Unable to enumerate family ' "Roboto Mono Medium for Powerline" ' qt.qpa.fonts: Unable to enumerate family ' "Ubuntu Mono derivative Powerline" '
Signals, Slots and Events
signal的必要性
就像前面所讲的,整个application是事件驱动的。event有多种类型,比如mouse events或者keyboard events。
点击一个widget会触发QMouseEvent,并送入widget中的.mousePressEvent event handler(这实际上是这个widget的方法),这个handler会处理这个event并得到information(什么触发了这个事件、在哪个位置触发的)。
所以,如果我们想要对某个操作进行自定义,就可以通过继承widget并重写其对应的event handler方法即可。比如我们可以用一下代码来更改Qbutton的.KeyPressEvent handler:
class CustomButton(Qbutton):
def keyPressEvent(self, e):
super(CustomButton, self).keyPressEvent(e)
...
以上的方法是好的,但如果我们现在需要捕获作用在20个buttons上的一个event,则我们需要重新继承20个buttons。。。显然,我们不会这个做。
什么是signals和slots
Qt提供了一个更加整洁的方式,即Signals。
- Signals类似event,当application发生变化时其会发送一个signal
- 比如所有会引起event的行为
- 或者是box中的文本发生了变化
- signal会携带改变的数据一起发送过去
- 在Qt的术语中,接受Signals的方法称为Slots,Qt类中提供了一些标准的Slots,但我们可以使用python func来自己定义slots
Signals实验
Qt5已经有[Python的文档]
常看文档后,发现QMainWidget有一个windowTitleChanged signal,我们现在使用它来进行下述实验:
# py example4.py
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow
from PyQt5.QtCore import Qt
# 这里使用面向对象的方法
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# 使用connect方法会将signal连接到对应的slot(func)上去,一旦signal
# 触发(window title改变),则会使用这些funcs
# 我们可以一次连接多个这样的func,当signal触发时,这些func都会运行
self.windowTitleChanged.connect(self.onWindowTitleChange)
self.windowTitleChanged.connect(lambda x: self.my_custom_fn())
self.windowTitleChanged.connect(lambda x: self.my_custom_fn(x))
self.windowTitleChanged.connect(lambda x: self.my_custom_fn(x, 25))
# 在这里设置title的时候会触发上面的func一次
self.setWindowTitle("My Awesome App")
label = QLabel("This is a PyQt5 window!")
# Qt namespace中有大量的特征用于自定义widget的行为,类似常量
label.setAlignment(Qt.AlignCenter)
# 将widget放入window的中央,默认widget将填满整个window
self.setCentralWidget(label)
def onWindowTitleChange(self, x):
# 这个x是signal触发是排出的内容,具体要查看文档
print(x)
def my_custom_fn(self, a="Hello", b=5):
print(a, b)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
许多signal会被传入已存在的、标准的一些方法中,自然我们可以通过更改这些方法完成我们的目的。同样我们也可以像上面描述的一样,使用
connect来连接一个新的slot,这样不会影响原来的slot,他们都会被触发。
当我们不得不使用event时
所以,因为signals的存在,大多数时候我们不会用到events。但当signal无法解决我们的问题时,我们就必须和event打交道了。
下面是对右击窗口区域的行为的一次更改:
# py example5.py
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow
from PyQt5.QtCore import Qt
# 这里使用面向对象的方法
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
label = QLabel("This is a PyQt5 window!")
# Qt namespace中有大量的特征用于自定义widget的行为,类似常量
label.setAlignment(Qt.AlignCenter)
# 将widget放入window的中央,默认widget将填满整个window
self.setCentralWidget(label)
def contextMenuEvent(self, event):
# 右键点击会触发一个event,如果此发生在MainWindow中,则其会被送入
# 此方法内,这里我们让它在console中打印一些东西
print("Context menu event!")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
结果:

如果我们希望保留其原来的功能,可以使用下面的写法:
def contextMenuEvent(self, event):
print("Context menu event!")
super(MainWindow, self).contextMenuEvent(event)
其实就是调用了父类的方法。
在一些比较复杂的application上,widget接受了event后,还会将此event发送到其父widget上,如果我们不希望此event发送,可以在widget的handler上添加下面的操作:
class CustomButton(Qbutton):
def event(self, e):
e.accept()
相似的,如果希望它发送,则使用
class CustomButton(Qbutton):
def event(self, e):
e.ignore()
这个命名也有点太别扭了。
基础的组件
Toolbars
整个代码如下:
# py example11.py
import sys
import PyQt5.QtWidgets as qtw
import PyQt5.QtCore as qtc
import PyQt5.QtGui as qtg
# 这里使用面向对象的方法
class MainWindow(qtw.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
label = qtw.QLabel("This is a PyQt5 window!")
# Qt namespace中有大量的特征用于自定义widget的行为,类似常量
label.setAlignment(qtc.Qt.AlignCenter)
# 将widget放入window的中央,默认widget将填满整个window
self.setCentralWidget(label)
# 创建一个toolbar,并将toolbar加入到main window中
toolbar = qtw.QToolBar("My Main toolbar")
# 告诉toolbar,我们使用的icon 图片的大小,这样icon可以填充整个按钮
toolbar.setIconSize(qtc.QSize(16, 16))
self.addToolBar(toolbar)
# 创建一个按钮(这里使用的是action,这是一个更加通用的抽象的按钮类)
# action相对于button的好处在于,button只是一个按钮,所以其唯一接受的交互形式是
# press。但action可以作为任何形式的交互出现,即其既可以是按钮,又可以出现在菜单栏,
# 也可以通过键盘快捷键触发。这在toolbar和menubar的实现中是非常必要的,因为一般来说
# 有很多内容在toolbar和menu中是共享的,如果没有action,我们需要为menu中和toolbar
# 中都有的一样的功能实现两次。。
# action接受的是一个QIcon对象作为其icon、string作为其介绍,还有最后的参数是其parent,
# 这里其signal会传递非其父元素,所以这里设定为main window
button_action = qtw.QAction(
qtg.QIcon("bug.png"), "Your button", self
)
# hover到action上,会在status上打印下面的信息
button_action.setStatusTip("This is you button")
# 为action的triggeredsignal绑定slot
button_action.triggered.connect(self.onMyToolBarButtonClick)
# 该设置使action 按钮有按下的效果
button_action.setCheckable(True)
# 将action加入到toolbar中
toolbar.addAction(button_action)
# 添加分割线
toolbar.addSeparator()
# 添加第二个button
button_action2 = qtw.QAction(
qtg.QIcon("bug.png"), "You button2", self
)
button_action2.triggered.connect(self.onMyToolBarButtonClick)
button_action.setCheckable(True)
toolbar.addAction(button_action)
# toolbar也可以加入其他的widgets
toolbar.addWidget(qtw.QLabel("Hello"))
toolbar.addWidget(qtw.QCheckBox())
self.setStatusBar(qtw.QStatusBar(self))
def onMyToolBarButtonClick(self, s):
# 这里可以知道,传入的信息是个boolean
print("click", s)
app = qtw.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

主要需要注意的内容有:
虽然Qt的toolbars支持icons、text或者其他的Qt标准widgets,但最好的实现方式是使用
QAction,原因如上所述。简单来说,QAction可以看做一个综合版的交互单元,可以放在任何组件内,提高了复用性。使用
mainwin.setStatusBar来创建状态栏,需要接受一个QStatusBar实例。使用
setCheckable来控制按下的效果。实际上有一个signal
.toggled来传递这个按下的消息,但这个消息的功能太单一了,所以用的不多。按钮可以下载fugue icon set中的图片来进行美化。
添加icon,需要使用下列3个步骤:
- 使用
Action.setIconSize方法来接受一个QSize对象,指定icon大小 - 使用
QIcon类来接受图片创建一个Icon对象 - 将Icon对象送入Action的第一个参数
因为用的是相对路径来添加图片,所以运行时路径必须在文件所在的路径
文字和Icon的显示方式,我们可以通过
.setToolButtonStyle方法来调整,其值可以使用以下的flag:- 使用
最后可以添加多个
QAction对象,甚至是其他的widgets。
menu
menu就是下图所示的gui元素:

代码如下:
# py example12.py
import sys
import PyQt5.QtWidgets as qtw
import PyQt5.QtCore as qtc
import PyQt5.QtGui as qtg
# 这里使用面向对象的方法
class MainWindow(qtw.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
label = qtw.QLabel("This is a PyQt5 window!")
# Qt namespace中有大量的特征用于自定义widget的行为,类似常量
label.setAlignment(qtc.Qt.AlignCenter)
# 将widget放入window的中央,默认widget将填满整个window
self.setCentralWidget(label)
toolbar = qtw.QToolBar("My Main toolbar")
toolbar.setIconSize(qtc.QSize(16, 16))
self.addToolBar(toolbar)
button_action = qtw.QAction(
qtg.QIcon("bug.png"), "Your button", self
)
button_action.setStatusTip("This is you button")
button_action.triggered.connect(self.onMyToolBarButtonClick)
button_action.setCheckable(True)
# 为了此action添加快捷键
button_action.setShortcut(qtg.QKeySequence("Ctrl+p"))
toolbar.addAction(button_action)
# 添加分割线
toolbar.addSeparator()
# 添加第二个button
button_action2 = qtw.QAction(
qtg.QIcon("bug.png"), "You button2", self
)
button_action2.triggered.connect(self.onMyToolBarButtonClick)
button_action.setCheckable(True)
toolbar.addAction(button_action)
# toolbar也可以加入其他的widgets
toolbar.addWidget(qtw.QLabel("Hello"))
toolbar.addWidget(qtw.QCheckBox())
# 本来QMainWindow就已经有menu bar了,只是menu bar里是空的
menu = self.menuBar()
self.setStatusBar(qtw.QStatusBar(self))
# 添加一个menu
file_menu = menu.addMenu("&File") # &使得此Menu可以有alt来控制
file_menu.addAction(button_action) # 为file menu添加一个按钮
file_menu.addSeparator() # 添加一个分割线
file_menu.addAction(button_action2) # 添加第二个按钮
# 可以我menu继续添加子menu
file_submenu = file_menu.addMenu("Submenu")
file_submenu.addAction(button_action2)
def onMyToolBarButtonClick(self, s):
print("click", s)
app = qtw.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
QMainWindow中本身自带了menuBar,所以只需要使用self.menuBar得到即可。使用
addMenu方法来添加menu,里面输入menu的名称即可不要把menu理解成一个按钮,其更是一个容器,一个下拉菜单,里面储存这一个一个的acton
menu可以嵌套,只需要使用mean的
addMenu方法即可。使用
setShortCut方法添加快捷键,需要传入QKeySequence对象。
widgets
widgets就是UI的基本组件,是可以和用户产生交互的元素。我们看到的各种toolbar、button等,都是widgets。
Qt有大量的widgets,而且还支持自定义。以下代码将一些常用的widgets放置在一个QWidget基类中,并放置在main windows的中央。
这里
QWidget被用作一个容器
# py example13.py
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
layout = QVBoxLayout()
widgets = [
QCheckBox,
QComboBox,
QDateTimeEdit,
QDial,
QDoubleSpinBox,
QFontComboBox,
QLCDNumber,
QLabel,
QLineEdit,
QProgressBar,
QPushButton,
QRadioButton,
QSlider,
QSpinBox,
QTimeEdit
]
for w in widgets:
layout.addWidget(w())
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()


1. QLabel
这是最简单的widget,不存在交互,只是显示一行字而已。以下代码展示了如何改变其中的文字内容和文字样式:
# py example14.py
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
widget1 = QLabel("Hello") # 可以直接传入文字
widget1.setText("Hello world") # 通过此方法改变文字的内容
# 以下是改变文字样式的方法
font = widget1.font()
font.setPointSize(30)
widget1.setFont(font)
# 改变文字的对齐位置
widget1.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
widget2 = QLabel("")
widget2.setPixmap(QPixmap("space.jpg"))
widget2.setScaledContents(True)
layout = QVBoxLayout()
layout.addWidget(widget1)
layout.addWidget(widget2)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

这里有几点需要注意:
改变文字样式的方式,最好是先拿到其中的font对象,将其更改后,再把其送入widget中,这样能够保证一些默认样式不会丢失。
Qt中的flag可以被用于设置widgets的属性,这里使用了关于文字对齐的部分:
水平对齐的flags:

垂直对齐的flags:

一个特殊的flags:

我们可以使用
|来组合一个水平和一个垂直flags来得到4个角落,至于为什么使用|而不是&,这涉及到bitmasks的问题,反正先记住吧。所有Qt的flags的合并操作都是这样的可以使用
setPixMap(QPixmpa(a.png))来设置图片。使用.setScaledContents(True)来将图片整个布满按钮。
2. QCheckBox
代码如下:
# py example15.py
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
widget1 = QCheckBox()
# 这个使用boolean来设置是否选中
# widget1.setCheckted(True)
# 这个使用flag来设置状态,有3种
widget1.setCheckState(Qt.Checked) # 设置状态是checked
# 可以使用此方法来选择第三种状态
# widget1.setTristate(True)
# 将state改变的flag连接到slot,传递给slot的实际上就是flag
widget1.stateChanged.connect(self.show_state)
self.setCentralWidget(widget1)
def show_state(self, s):
# 打印一下flag
print(s == Qt.Checked)
print(s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
结果:

注意:
有两种方式来改变其checkbox的状态:
使用
setCheckState,需要传入flags
或者使用方法
setChecked(True)和setTristate(True),这两个不需要flags,直接设置boolean就可以
其有一个signal:
stateChanged,其传入slot的参数就是上面的flags:这些flags实际上就是
0, 1, 2,但有了flag,我们没有必要去进行记忆。
3. QComboBox
即下拉列表,代码如下:
# py example16.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
widget = QComboBox()
# 增加可选元素
widget.addItems(["one", "two", "Three"])
# 这个signal默认传给slot的是index
widget.currentIndexChanged.connect(self.index_changed)
# 可以通过此操作,使signal传递给slot的是上面的text
widget.currentIndexChanged[str].connect(self.text_changed)
# 此操作使得combo box可以输入文本
widget.setEditable(True)
# 这些输入的文本怎么处理
# 当我们输入文本并按enter后,下面的设置会使这个文本选项称为新的Items
# 并插入到Item列表的最后
widget.setInsertPolicy(QComboBox.InsertAtBottom)
# Items最多有5个
widget.setMaxCount(5)
self.setCentralWidget(widget)
def index_changed(self, i):
print(i)
def text_changed(self, s):
print(s)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

注意:
设置插入模式,可以有以下的flags(不再
Qt中,而是在QComboBox本身中)
一个应用就是使用combobox来选择字体,但注意,Qt中有一个
QFontComboBox直接实现了这个功能。
4. QListWidget
可以认为是始终展开的QComboBox,和QComboBox基本一致,区别在于可用的signals。代码:
# py example17.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
widget = QListWidget()
# 增加可选元素
widget.addItems(["one", "two", "Three"])
widget.currentItemChanged.connect(self.index_changed)
widget.currentTextChanged.connect(self.text_changed)
self.setCentralWidget(widget)
def index_changed(self, i):
# 这里传入的不是index,而是一个QListItem对象
print(i.text())
def text_changed(self, s):
print(s)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

注意:
- 其将
QComboBox的一个signal拆成了两个。 - 无法更改文本。
5. QLineEdit
单行文本输入框,代码如下:
# py example18.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
widget = QLineEdit()
# 设置可以输入的最大字符数量
widget.setMaxLength(10)
# 设置预先填入的提示字符
widget.setPlaceholderText("Enter your text")
# 此signal在按下enter时触发
widget.returnPressed.connect(self.return_pressed)
# 此signal在选中的文本发生变化的时候触发
widget.selectionChanged.connect(self.selection_changed)
# 此signal在text发生变化时触发,并返回改变的文本
widget.textChanged.connect(self.text_changed)
# 此signal在进行编辑的时候触发,基本上和textChanged同时触发
widget.textEdited.connect(self.text_edited)
self.setCentralWidget(widget)
def return_pressed(self):
print("Return preseed!")
self.centralWidget().setText("BOOM!")
def selection_changed(self):
print("Selection changed")
print(self.centralWidget().selectedText())
def text_changed(self, s):
print("Text changed...")
print(s)
def text_edited(self, s):
print("Text edited...")
print(s)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

注意:
- 可以使用
self.CentralWidget()得到位于main window中央的widget,这里就是QLineEdit。 - 使用
widget.setText和widget.selectionText设置输入框中的文本和返回选中的文本。
布局
在Qt中有4种布局方式:

当然,Qt Designer也是可以的,这是更加方便的布局方式。
1. QVBoxLayout
就是将widgets从上到下堆叠起来

代码:
# py example19.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Color(QWidget):
"""
一个自定义的类,用于展示布局
"""
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
# 开启后,其自动使用window color来填充背景
self.setAutoFillBackground(True)
# 得到调色板,默认是global desktop palette。调色板中记录了
# widgets的各种元素的颜色,可以看做是widgets颜色设置的一组
# 集合
palette = self.palette()
# 将调色板中窗口颜色改变,需要使用QColor类
palette.setColor(QPalette.Window, QColor(color))
# 将这组设置更新到当前的widgets中
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
layout = QVBoxLayout()
layout.addWidget(Color("red"))
layout.addWidget(Color("green"))
layout.addWidget(Color("blue"))
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

2. QHBoxLayout
就是将widgets从左到右堆叠起来

代码:
# py example20.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Color(QWidget):
"""
一个自定义的类,用于展示布局
"""
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
# 开启后,其自动使用window color来填充背景
self.setAutoFillBackground(True)
# 得到调色板,默认是global desktop palette。调色板中记录了
# widgets的各种元素的颜色,可以看做是widgets颜色设置的一组
# 集合
palette = self.palette()
# 将调色板中窗口颜色改变,需要使用QColor类
palette.setColor(QPalette.Window, QColor(color))
# 将这组设置更新到当前的widgets中
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
layout = QHBoxLayout()
layout.addWidget(Color("red"))
layout.addWidget(Color("green"))
layout.addWidget(Color("blue"))
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

3. Nesting layouts
任何一个widget都有.addLayout方法,当使用这个方法,其传入的layout中储存的多个widgets就会以当前widget为容器在其中进行排列。每个widget都可以成为这样一个容器,layout本身也不例外。只是layout除了可以addLayout外,还可以直接addWidget,而其他的widgets只能addLayout。
代码:
# py example21.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Color(QWidget):
"""
一个自定义的类,用于展示布局
"""
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
# 开启后,其自动使用window color来填充背景
self.setAutoFillBackground(True)
# 得到调色板,默认是global desktop palette。调色板中记录了
# widgets的各种元素的颜色,可以看做是widgets颜色设置的一组
# 集合
palette = self.palette()
# 将调色板中窗口颜色改变,需要使用QColor类
palette.setColor(QPalette.Window, QColor(color))
# 将这组设置更新到当前的widgets中
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
layout1 = QHBoxLayout()
layout2 = QVBoxLayout()
layout3 = QVBoxLayout()
layout2.addWidget(Color("red"))
layout2.addWidget(Color("yellow"))
layout2.addWidget(Color("purple"))
layout1.addLayout(layout2)
layout1.addWidget(Color("green"))
layout3.addWidget(Color("red"))
layout3.addWidget(Color("purple"))
layout1.addLayout(layout3)
layout1.setContentsMargins(0, 0, 0, 0)
layout1.setSpacing(20)
widget = QWidget()
widget.setLayout(layout1)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

另外,我们可以通过layout.setContentsMargins来控制layout的外边距的宽度,layout.setSpacing来控制里面元素的间隔。
所以我们在代码中加入:
layout1.setContentsMargins(0, 0, 0, 0)
layout1.setSpacing(20)
得到以下结果:

4. QGridLayout
模式如下:

里面的元素没有必要全都给满,所以下面的模式也是可以实现的:

代码:
# py example22.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Color(QWidget):
"""
一个自定义的类,用于展示布局
"""
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
# 开启后,其自动使用window color来填充背景
self.setAutoFillBackground(True)
# 得到调色板,默认是global desktop palette。调色板中记录了
# widgets的各种元素的颜色,可以看做是widgets颜色设置的一组
# 集合
palette = self.palette()
# 将调色板中窗口颜色改变,需要使用QColor类
palette.setColor(QPalette.Window, QColor(color))
# 将这组设置更新到当前的widgets中
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
layout = QGridLayout()
layout.addWidget(Color("red"), 0, 0)
layout.addWidget(Color("green"), 1, 0)
layout.addWidget(Color("blue"), 1, 1)
layout.addWidget(Color("purple"), 2, 1)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

5. QStackedLayout
这个布局是用来实现widgets的堆叠的。在实现graphics和类似书签式的东西时非常有用。

代码:
# py example23.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Color(QWidget):
"""
一个自定义的类,用于展示布局
"""
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
# 开启后,其自动使用window color来填充背景
self.setAutoFillBackground(True)
# 得到调色板,默认是global desktop palette。调色板中记录了
# widgets的各种元素的颜色,可以看做是widgets颜色设置的一组
# 集合
palette = self.palette()
# 将调色板中窗口颜色改变,需要使用QColor类
palette.setColor(QPalette.Window, QColor(color))
# 将这组设置更新到当前的widgets中
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
layout = QStackedLayout()
layout.addWidget(Color("red"))
layout.addWidget(Color("green"))
layout.addWidget(Color("blue"))
layout.addWidget(Color("yellow"))
layout.setCurrentIndex(3) # 让yellow显示在上面
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

注意,有一个
QStackedWidget能够实现相同的功能,其一般作为放在Main Windows中央的widget。
但上面的结果我们只能看到最上面的widget,所以,我们可以另外设置一些button来实现widgets的切换:
# py example24.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Color(QWidget):
"""
一个自定义的类,用于展示布局
"""
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
# 开启后,其自动使用window color来填充背景
self.setAutoFillBackground(True)
# 得到调色板,默认是global desktop palette。调色板中记录了
# widgets的各种元素的颜色,可以看做是widgets颜色设置的一组
# 集合
palette = self.palette()
# 将调色板中窗口颜色改变,需要使用QColor类
palette.setColor(QPalette.Window, QColor(color))
# 将这组设置更新到当前的widgets中
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
pagelayout = QVBoxLayout()
button_layout = QHBoxLayout()
layout = QStackedLayout()
pagelayout.addLayout(button_layout)
pagelayout.addLayout(layout)
cs = ["red", "green", "blue", "yellow"]
for n, color in enumerate(cs):
btn = QPushButton(color)
btn.pressed.connect(lambda n=n: layout.setCurrentIndex(n))
# 这里使用了python的一个技巧。这里将index设为lambda参数的默认值,则其会被缓存,
# 不然,所有的这四个lambda里的n都只会是黄色
button_layout.addWidget(btn)
layout.addWidget(Color(color))
widget = QWidget()
widget.setLayout(pagelayout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

As a matter of fact,Qt内部实现了一个类似功能的组件,可以直接使用:
# py example25.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Color(QWidget):
"""
一个自定义的类,用于展示布局
"""
def __init__(self, color, *args, **kwargs):
super(Color, self).__init__(*args, **kwargs)
# 开启后,其自动使用window color来填充背景
self.setAutoFillBackground(True)
# 得到调色板,默认是global desktop palette。调色板中记录了
# widgets的各种元素的颜色,可以看做是widgets颜色设置的一组
# 集合
palette = self.palette()
# 将调色板中窗口颜色改变,需要使用QColor类
palette.setColor(QPalette.Window, QColor(color))
# 将这组设置更新到当前的widgets中
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
tabs = QTabWidget()
tabs.setDocumentMode(True)
tabs.setTabPosition(QTabWidget.East) # 控制标签所在的位置,右侧
tabs.setMovable(True)
cs = ["red", "green", "blue", "yellow"]
for n, color in enumerate(cs):
tabs.addTab(Color(color), color)
self.setCentralWidget(tabs)
app = QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
结果:

对话框
用于和用户交流,比如打开保存文件、设置或者其他不会在主窗口实现的内容。Qt实现了一系列常用的对话框类型。
作为对话框的基本class是QDialog,其接受的参数是其父元素(其依附的窗口):
我们可以有以下代码:
# py example26.py
import sys
import PyQt5.QtWidgets as qtw
import PyQt5.QtCore as qtc
import PyQt5.QtGui as qtg
class CustomDialog(qtw.QDialog):
def __init__(self, *args, **kwargs):
super(CustomDialog, self).__init__(*args, **kwargs)
self.setWindowTitle("HELLO!")
QBtn = qtw.QDialogButtonBox.Ok | qtw.QDialogButtonBox.Cancel
self.buttonBox = qtw.QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = qtw.QVBoxLayout()
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
# 这里使用面向对象的方法
class MainWindow(qtw.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
label = qtw.QLabel("This is a PyQt5 window!")
label.setAlignment(qtc.Qt.AlignCenter)
self.setCentralWidget(label)
toolbar = qtw.QToolBar("My Main toolbar")
toolbar.setIconSize(qtc.QSize(16, 16))
self.addToolBar(toolbar)
button_action = qtw.QAction(
qtg.QIcon("bug.png"), "Your button", self
)
button_action.setStatusTip("This is you button")
button_action.triggered.connect(self.onMyToolBarButtonClick)
button_action.setCheckable(True)
button_action.setShortcut(qtg.QKeySequence("Ctrl+p"))
toolbar.addAction(button_action)
toolbar.addSeparator()
button_action2 = qtw.QAction(
qtg.QIcon("bug.png"), "You button2", self
)
button_action2.triggered.connect(self.onMyToolBarButtonClick)
button_action.setCheckable(True)
toolbar.addAction(button_action)
toolbar.addWidget(qtw.QLabel("Hello"))
toolbar.addWidget(qtw.QCheckBox())
menu = self.menuBar()
self.setStatusBar(qtw.QStatusBar(self))
file_menu = menu.addMenu("&File") # &使得此Menu可以有alt来控制
file_menu.addAction(button_action) # 为file menu添加一个按钮
file_menu.addSeparator() # 添加一个分割线
file_menu.addAction(button_action2) # 添加第二个按钮
file_submenu = file_menu.addMenu("Submenu")
file_submenu.addAction(button_action2)
def onMyToolBarButtonClick(self, s):
print("click", s)
# 主要改动在这里
dlg = CustomDialog(self)
if dlg.exec_():
print("Success!")
else:
print("Cancel!")
app = qtw.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
结果:

注意:
QDialog的启动形式和我们的application很像,因为它毕竟是一个新的窗口,也需要一个event loop。
开启QDialog的event loop后,其会阻塞主窗口的event loop,这个问题需要使用多线程来解决。
这里创建dialog按钮的方式有点异类,这里使用Qt准备好的一些flags来创建的,这些flags有以下:


这种做法的好处在于可以将多个button的行为捏合到一个button对象上。之后再去将其不同的signals映射到不同的slots即可。