布局管理
绝对定位
每个程序都是以像素为单位区分元素的位置,衡量元素的⼤⼩。所以我们完全可以使⽤绝对定位搞定每个元素和窗⼜的位置。
局限性:
- 元素不会随着我们更改窗⼜的位置和⼤⼩⽽变化
- 不能适⽤于不同的平台和不同分辨率的显⽰器
- 更改应⽤字体⼤⼩会破坏布局
- 如果我们决定重构这个应⽤,需要全部计算⼀下每个元素的位置和⼤⼩
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 使⽤move()⽅法定位了每⼀个元素,使⽤x、y坐标。x、y坐标的原点是程序的左上⾓
lbl1 = QLabel('Zetcode', self)
lbl1.move(15, 10)
lbl2 = QLabel('tutorials', self)
lbl2.move(35, 40)
lbl3 = QLabel('for programmers', self)
lbl3.move(55, 70)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Absolute')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
盒布局
使⽤盒布局能让程序具有更强的适应性。这个才是布局⼀个应⽤的更合适的⽅式。 QHBoxLayout 和QVBoxLayout 是基本的布局类,分别是⽔平布局和垂直布局。
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 创建按钮
okButton = QPushButton("OK")
cancelButton = QPushButton("Cancel")
# 创建⼀个⽔平布局,增加两个按钮和弹性空间
hbox = QHBoxLayout()
# stretch函数在两个按钮前⾯增加了⼀些弹性空间
hbox.addStretch(1)
hbox.addWidget(okButton)
hbox.addWidget(cancelButton)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
# 把这个⽔平布局放到⼀个垂直布局盒⾥⾯
# 弹性元素会把所有的元素⼀起都放置在应⽤的右下⾓
self.setLayout(vbox)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Buttons')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
、
栅格布局
最常⽤的还是栅格布局了。这种布局是把窗⼜分为⾏和列。创建和使⽤栅格布局,需要使⽤QGridLayout模块。
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QGridLayout, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 创建栅格化的按钮
grid = QGridLayout()
self.setLayout(grid)
# 创建⼀个QGridLayout实例,并把放到程序窗口⾥
names = ['Cls', 'Bck', '', 'Close',
'7', '8', '9', '/',
'4', '5', '6', '*',
'1', '2', '3', '-',
'0', '.', '=', '+']
# 将要使⽤的按钮的名称
positions = [(i, j) for i in range(5) for j in range(4)]
# 创建按钮位置列表
for position, name in zip(positions, names):
if name == '':
continue
button = QPushButton(name)
# 创建按钮,并使⽤ addWidget() ⽅法把按钮放到布局⾥⾯
grid.addWidget(button, *position)
self.move(300, 150)
self.setWindowTitle('Calculator')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
制作提交反馈信息的布局
组件能跨列和跨⾏展⽰
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QLineEdit, QGridLayout,QTextEdit, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
title = QLabel('Title')
author = QLabel('Author')
review = QLabel('Review')
titleEdit = QLineEdit() # QLineEdit 只有⼀⾏
authorEdit = QLineEdit()
reviewEdit = QTextEdit() # QTextEdit 不⽌⼀⾏
grid = QGridLayout()
grid.setSpacing(10) # 珊格间隙的像素值,单位是像素
grid.addWidget(title, 1, 0)
grid.addWidget(titleEdit, 1, 1)
grid.addWidget(author, 2, 0)
grid.addWidget(authorEdit, 2, 1)
grid.addWidget(review, 3, 0)
# 指定这个元素跨5⾏显⽰
grid.addWidget(reviewEdit, 3, 1, 5, 1)
self.setLayout(grid)
self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('Review')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
事件和信号
事件
所有的应⽤都是事件驱动的
在事件模型中,有三个⾓⾊:
- 事件源 — 是发⽣了状态改变的对象
- 事件 — 是这个对象状态改变的内容
- 事件⽬标 —事件想作⽤的⽬标
事件源绑定事件处理函数,然后作⽤于事件⽬标⾝上。
PyQt5处理事件⽅⾯有个signal and slot机制。Signals and slots⽤于对象间的通讯。事件触发的时候,发⽣⼀个signal,slot是⽤来被Python调⽤的slot只有在事件触发的时候才能调⽤。
Signals & slots
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QLCDNumber, QSlider, QVBoxLayout, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
lcd = QLCDNumber(self)
sld = QSlider(Qt.Horizontal, self)
vbox = QVBoxLayout()
vbox.addWidget(lcd)
vbox.addWidget(sld)
self.setLayout(vbox)
# 把滑块的变化和数字的变化绑定在⼀起
sld.valueChanged.connect(lcd.display)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal and slot')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
重构事件处理器
在PyQt5中,事件处理器经常被重写。
替换事件处理器函数 keyPressEvent() ,实现按下ESC键程序就会退出。
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Event handler')
self.show()
def keyPressEvent(self, e): # 键盘按下事件
if e.key() == Qt.Key_Escape: # 按下ESC键
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
事件对象
事件对象是⽤python来描述⼀系列的事件⾃⾝属性的对象
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QLabel
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
grid = QGridLayout()
grid.setSpacing(10)
x = 0
y = 0
# 显⽰⿏标的X和Y坐标
self.text = "x: {0}, y: {1}".format(x, y)
self.label = QLabel(self.text, self)
grid.addWidget(self.label, 0, 0, Qt.AlignTop) # Qt.AlignTop 居顶
self.setMouseTracking(True)
self.setLayout(grid)
self.setGeometry(300, 300, 350, 200)
self.setWindowTitle('Event object')
self.show()
def mouseMoveEvent(self, e):
# 事件追踪默认没有开启,当开启后才会追踪⿏标的点击事件
# e 代表了事件对象,x() 和 y() ⽅法得到⿏标的x和y
# 坐标点,然后拼成字符串输出到 QLabel 组件⾥
x = e.x()
y = e.y()
text = "x: {0}, y: {1}".format(x, y)
self.label.setText(text)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
X和Y的值代表当前鼠标的位置
事件发送
import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
btn1 = QPushButton("Button 1", self)
btn1.move(30, 50)
btn2 = QPushButton("Button 2", self)
btn2.move(150, 50)
# 两个按钮都和同⼀个slot绑定
btn1.clicked.connect(self.buttonClicked)
btn2.clicked.connect(self.buttonClicked)
self.statusBar()
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Event sender')
self.show()
# buttonClicked() ⽅法决定了是哪个按钮能调⽤ sender() ⽅法
def buttonClicked(self):
sender = self.sender()
self.statusBar().showMessage(sender.text() + ' was pressed')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
⽤调⽤ sender() ⽅法的⽅式决定了事件源。状态栏显⽰了被点击的按钮
信号发送
QObject 实例能发送事件信号
import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QMainWindow, QApplication
class Communicate(QObject):
# 创建⼀个叫closeApp的信号,这个信号会在⿏标按下的时候触发,事件与 QMainWindow 绑定
# 创建⼀个 pyqtSignal() 属性的信号
closeApp = pyqtSignal()
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.c = Communicate()
# closeApp 信号 QMainWindow 的 close() ⽅法绑定
self.c.closeApp.connect(self.close)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Emit signal')
self.show()
def mousePressEvent(self, event):
# 点击窗⼜的时候,发送closeApp信号,程序终⽌
self.c.closeApp.emit()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
点击窗口对话框便会消失,不展示
对话框
输⼊⽂字
QInputDialog 提供了⼀个简单⽅便的对话框,可以输⼊字符串,数字或列表
from PyQt5.QtWidgets import QWidget, QPushButton, QLineEdit, QInputDialog, QApplication
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.btn = QPushButton('Dialog', self)
self.btn.move(20, 20)
self.btn.clicked.connect(self.showDialog)
self.le = QLineEdit(self)
self.le.move(130, 22)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Input dialog')
self.show()
def showDialog(self):
# 第⼀个参数是输⼊框的标题,第⼆个参数是输⼊框的占位符。对话框
# 返回输⼊内容和⼀个布尔值,如果点击的是OK按钮,布尔值就返回True
text, ok = QInputDialog.getText(self, 'Input Dialog', 'Enter your name:')
if ok:
self.le.setText(str(text))
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_()
该⽰例生成⼀个按钮和⼀个输⼊框,可以直接在输入框输入内容
也可以点击按钮(Dialog)弹出对话框,输入。点击ok后,内容会替换输入框的内容