PyQt5教程 :http://code.py40.com/face
教程翻译自:http://zetcode.com/gui/pyqt5/
【第一节】PyQt5简介:http://code.py40.com/1948.html 【第二节】PyQt5基本功能:http://code.py40.com/1961.html 【第三节】PyQt5布局管理:http://code.py40.com/1995.html 【第四节】PyQt5菜单和工具栏:http://code.py40.com/1984.html 【第五节】PyQt5事件和信号:http://code.py40.com/2004.html
【第六节】PyQt5 对话框:http://code.py40.com/2009.html
【第七节】PyQt5控件:http://code.py40.com/2018.html 【第八节】PyQt5控件(II):http://code.py40.com/2026.html 【第九节】PyQt 拖拽:http://code.py40.com/2035.html 【第十节】PyQt5绘图:http://code.py40.com/2042.html 【第十一节】PyQt5自定义控件:http://code.py40.com/2049.html 【第十二节】PyQt5俄罗斯方块:http://code.py40.com/2052.html
PyQt5 说明
pyqt5是一套Python绑定Digia QT5应用的框架。它可用于Python 2和3。本教程使用Python 3。Qt库是最强大的GUI库之一。pyqt5的官方网站http://www.riverbankcomputing.co.uk/news。
pyqt5 做为 Python 的一个模块,它有 620 多个类和 6000 个函数和方法。这是一个跨平台的工具包,它可以运行在所有主要的操作系统,包括 UNIX,Windows,Mac OS。pyqt5 是双重许可。开发者可以在 GPL 和 商业许可 之间进行选择。
pyqt5 的类别分为几个模块,包括以下:
- QtCore
- QtGui
- QtWidgets
- QtMultimedia
- QtBluetooth
- QtNetwork
- QtPositioning
- Enginio
- QtWebSockets
- QtWebKit
- QtWebKitWidgets
- QtXml
- QtSvg
- QtSql
- QtTest
说明:
QtCore | 包含了核心的非 GUI 功能。 此模块用于处理时间、文件和目录、各种数据类型、流、URL、MIME类型、线程或进程。 |
QtGui | 包含类窗口系统集成、事件处理、二维图形、基本成像、字体和文本。 |
QtWidgets | 模块包含创造经典桌面风格的用户界面提供了一套UI元素的类。 |
QtMultimedia | 包含的类来处理多媒体内容和 API 来访问相机和收音机的功能。 |
QtBluetooth | 模块包含类的扫描设备和连接并与他们互动。描述模块包含了网络编程的类。 这些类便于 TCP 和 IP 和 UDP 客户端和服务器的编码,使网络编程更容易和更便携。 |
QtNetwork | 网络模块 |
QtPositioning | 包含类的利用各种可能的来源,确定位置,包括卫星、Wi-Fi、或一个文本文件。 |
Enginio | 模块实现了客户端库访问 Qt 云服务托管的应用程序运行时。 |
QtWebSockets | 模块包含实现 WebSocket 协议类。 |
QtWebKit | 包含一个基于 Webkit2 图书馆 Web 浏览器 实现类。 |
QtWebKitWidgets | 包含的类的基础 webkit1 ,用于 qtwidgets 应用 Web 浏览器的实现。 |
QtXml | 包含与 XML 文件的类。这个模块为 SAX 和 DOM API 提供了实现。 |
QtSvg | 模块提供了显示 SVG 文件内容的类。可伸缩矢量图形(SVG)是一种描述二维图形和图形应用的语言。 |
QtSql | 模块提供操作数据库的类。 |
QtTest | 包含的功能,使 pyqt5 应用程序的单元测试 |
pyqt5 不向后兼容 pyqt4。pyqt5 有几个显著的变化。将旧代码调整到新库并不困难。有几个大的改变如下:
- Python模块已经重组。一些模块已经删除(qtscript),有的被分割成子模块(QtGui,QtWebKit)。
- 新的模块作了详细的介绍,包括qtbluetooth,qtpositioning,或enginio。
- pyqt5只支持新型的信号和槽handlig。电话signal()或slot()不再支持。
- pyqt5不支持Qt的API被标记为过时或陈旧的任何部分在QT V5.0。
【第 1 节】 到 【第 4 节】演示代码
( 可以 把 __init__ 函数中的 注释逐个取消,然后运行程序看执行效果
# -*- coding: utf-8 -*-
# @Author :
# @File : main.py
# @Software: PyCharm
# @description : XXX
import sys
# QMainWindow 类提供了一个主要的应用程序窗口。
# 你用它可以让应用程序添加状态栏,工具栏和菜单栏。
from PyQt5.QtWidgets import QWidget, QMainWindow
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QDesktopWidget, QMessageBox, QLabel, QPushButton, QAction
from PyQt5.QtWidgets import QAction, qApp
from PyQt5.QtWidgets import QLineEdit, QTextEdit
from PyQt5.QtWidgets import QHBoxLayout # horizontal 水平布局
from PyQt5.QtWidgets import QVBoxLayout # vertical 垂直布局
from PyQt5.QtWidgets import QGridLayout # Grid 网格布局
from PyQt5.QtGui import QIcon
# 如果不想一个一个 导入,可以 import *
# from PyQt5.QtWidgets import *
class MyForm1(QWidget):
def __init__(self):
super(MyForm1, self).__init__()
# self._init_ui_1()
# self._init_ui_2()
# self._init_ui_3()
# self._init_ui_4()
self._init_ui_5()
pass
def center(self):
"""
控制窗口显示在屏幕中心的方法
:return:
"""
# 获得窗口
qr = self.frameGeometry()
# 获得屏幕中心点
cp = QDesktopWidget().availableGeometry().center()
# 显示到屏幕中心
qr.moveCenter(cp)
self.move(qr.topLeft())
def _init_ui_1(self):
"""
重置大小,中心显示
:return:
"""
self.resize(1000, 600)
self.center()
self.show()
def _init_ui_2(self):
"""
使用 绝对位置 布局元素位置
:return:
"""
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()
def _init_ui_3(self):
"""
使用布局 来 布局 元素位置
:return:
"""
btn_ok = QPushButton("OK")
btn_cancel = QPushButton("Cancel")
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(btn_ok)
hbox.addWidget(btn_cancel)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Buttons')
self.show()
def _init_ui_4(self):
"""
使用 网格布局 来 布局 元素位置
:return:
"""
grid = QGridLayout()
self.setLayout(grid)
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)
grid.addWidget(button, *position)
self.move(300, 150)
self.setWindowTitle('Calculator')
self.show()
def _init_ui_5(self):
"""
网格布局 跨越 多行 或者 多列
:return:
"""
title = QLabel('Title')
author = QLabel('Author')
review = QLabel('Review')
titleEdit = QLineEdit()
authorEdit = QLineEdit()
reviewEdit = 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)
grid.addWidget(reviewEdit, 3, 1, 5, 1)
self.setLayout(grid)
self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('Review')
self.show()
def closeEvent(self, event):
"""
重写关闭窗口事件
:param event:
:return:
"""
reply = QMessageBox.question(
self, 'Message', 'Are you sure close window ?',
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
pass
class MyForm2(QMainWindow):
"""
QMainWindow 类提供了一个主要的应用程序窗口。
你用它可以让应用程序添加状态栏,工具栏和菜单栏。
"""
def __init__(self):
super(MyForm2, self).__init__()
# self._init_ui_6()
# self._init_ui_7()
# self._init_ui_8()
self._init_ui_9()
def _init_ui_6(self):
"""
状态栏
:return:
"""
self.statusBar().showMessage('Ready')
self.setGeometry(800, 300, 250, 150)
self.setWindowTitle('Statusbar')
self.show()
def _init_ui_7(self):
"""
菜单栏
:return:
"""
# QAction可以操作菜单栏,工具栏,或自定义键盘快捷键。
# 创建一个事件和一个特定的图标和一个“退出”的标签。
exitAction = QAction(QIcon('exit.png'), '&Exit', self)
exitAction.setShortcut('Ctrl+Q') # 定义该操作的快捷键。
# 创建一个鼠标指针悬停在该菜单项上时的提示。
exitAction.setStatusTip('Exit application')
# # 第三行创建一个鼠标指针悬停在该菜单项上时的提示。
exitAction.triggered.connect(qApp.quit)
self.statusBar()
# 创建一个菜单栏
menubar = self.menuBar()
# 添加菜单
fileMenu = menubar.addMenu('&File')
# 添加事件
fileMenu.addAction(exitAction)
self.setGeometry(800, 300, 300, 200)
self.setWindowTitle('Menubar')
self.show()
def _init_ui_8(self):
"""
工具栏
:return:
"""
exitAction = QAction(QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(qApp.quit)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)
self.setGeometry(800, 300, 300, 200)
self.setWindowTitle('Toolbar')
self.show()
def _init_ui_9(self):
# 创建一个菜单条,工具栏和状态栏的小窗口
textEdit = QTextEdit()
self.setCentralWidget(textEdit)
exitAction = QAction(QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
toolbar = self.addToolBar('Exit')
toolbar.addAction(exitAction)
self.setGeometry(800, 300, 350, 250)
self.setWindowTitle('Main window')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
form_1 = MyForm1()
form_2 = MyForm2()
sys.exit(app.exec_())
pass
点击产生一个新窗口:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton
class Form1(QWidget):
def __init__(self):
super(Form1, self).__init__()
self.setWindowTitle('From_1')
self.resize(600, 300)
self._init_ui()
self.show()
def close_window(self):
"""
点击按钮 将 窗体1 关掉
:return:
"""
self.close()
def _init_ui(self):
v_box = QVBoxLayout()
self.btn_1 = QPushButton('btn_1: close Form_2')
self.btn_2 = QPushButton('btn_2: show Form_2')
v_box.addWidget(self.btn_1)
v_box.addWidget(self.btn_2)
self.setLayout(v_box)
class Form2(QWidget):
def __init__(self):
super(Form2, self).__init__()
self.setWindowTitle('From_2')
self.resize(800, 400)
self._init_ui()
def display(self):
"""
显示窗体
:return:
"""
self.show()
def _init_ui(self):
h_box = QHBoxLayout()
btn_3 = QPushButton('btn_3')
btn_4 = QPushButton('btn_4')
h_box.addWidget(btn_3)
h_box.addWidget(btn_4)
self.setLayout(h_box)
if __name__ == '__main__':
app = QApplication(sys.argv)
w1 = Form1()
w2 = Form2()
w1.show()
w1.btn_1.clicked.connect(w1.close_window)
w1.btn_2.clicked.connect(w2.display)
app.exec_()
布局示例:
PyQt入门(五)— 布局:
from PyQt5 import QtWidgets
class MyWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('嵌套布局示例')
# 开始:
wlayout = QtWidgets.QHBoxLayout() # 全局布局(1个):水平
hlayout = QtWidgets.QHBoxLayout() # 局部布局(4个):水平、竖直、网格、表单
vlayout = QtWidgets.QVBoxLayout()
glayout = QtWidgets.QGridLayout()
flayout = QtWidgets.QFormLayout()
hlayout.addWidget(QtWidgets.QPushButton(str(1))) # 局部布局添加部件(例如:按钮)
hlayout.addWidget(QtWidgets.QPushButton(str(2)))
vlayout.addWidget(QtWidgets.QPushButton(str(3)))
vlayout.addWidget(QtWidgets.QPushButton(str(4)))
glayout.addWidget(QtWidgets.QPushButton(str(5)), 0, 0)
glayout.addWidget(QtWidgets.QPushButton(str(6)), 0, 1)
glayout.addWidget(QtWidgets.QPushButton(str(7)), 1, 0)
glayout.addWidget(QtWidgets.QPushButton(str(8)), 1, 1)
flayout.addWidget(QtWidgets.QPushButton(str(9)))
flayout.addWidget(QtWidgets.QPushButton(str(10)))
flayout.addWidget(QtWidgets.QPushButton(str(11)))
flayout.addWidget(QtWidgets.QPushButton(str(12)))
hwg = QtWidgets.QWidget() # 准备四个部件
vwg = QtWidgets.QWidget()
gwg = QtWidgets.QWidget()
fwg = QtWidgets.QWidget()
hwg.setLayout(hlayout) # 四个部件设置局部布局
vwg.setLayout(vlayout)
gwg.setLayout(glayout)
fwg.setLayout(flayout)
wlayout.addWidget(hwg) # 四个部件加至全局布局
wlayout.addWidget(vwg)
wlayout.addWidget(gwg)
wlayout.addWidget(fwg)
self.setLayout(wlayout) # 窗体本尊设置全局布局
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
效果:
布局 时 需要注意点
继承 QMainWindow 时布局界面 和 继承 QWidget 时 布局界面
1. 继承 QMainWindow 时 的界面布局
如何给QMainWindow正确地设置布局( C++ 示例 步骤)
- 第一步:创建一个QWidget实例,并将这个实例设置为 centralWidget:
QWidget *widget = new QWidget();
this->setCentralWidget(widget); - 第二部:创建一个主布局mainLayout,并把所需要的所有控件都往里面放(工具栏、菜单栏、状态栏除外):
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(...);
mainLayout->addLayout(...);
... - 第三步:将 widget 的布局设置为 mainLayout:
centralWidget()->setLayout(mainLayout);
//centralWidget()返回的是第一步创建的那个QWidget实例
继承 QMainWindow 时,需要设置 中心部件(即 主界面),如下面代码需要添加三行代码:
main_widget = QWidget() # 界面 实例
self.setCentralWidget(main_widget) # 设置 为 中心界面
......
......
self.centralWidget().setLayout(v_box) # 设置中心界面的布局
根据 C++ 示例 步骤改成 python ,完整示例代码如下:
import sys
# QMainWindow 类提供了一个主要的应用程序窗口。
# 你用它可以让应用程序添加状态栏,工具栏和菜单栏。
from PyQt5.QtWidgets import QWidget, QMainWindow
from PyQt5.QtWidgets import (
QApplication, QDesktopWidget, QHBoxLayout, QVBoxLayout,
QPushButton, QListView,
)
class LayoutDemoByQMainWindow(QMainWindow):
def __init__(self):
super(LayoutDemoByQMainWindow, self).__init__()
self.resize(1000, 600) # 重置大小
self._center_display() # 中心显示
self.vertical_layout()
self.show()
def _center_display(self):
"""
控制窗口显示在屏幕中心的方法
:return:
"""
# 获得窗口
qr = self.frameGeometry()
# 获得屏幕中心点
cp = QDesktopWidget().availableGeometry().center()
# 显示到屏幕中心
qr.moveCenter(cp)
self.move(qr.topLeft())
def vertical_layout(self):
"""
垂直布局
:return:
"""
###################################
main_widget = QWidget()
self.setCentralWidget(main_widget)
###################################
h_box_1 = QHBoxLayout()
h_box_2 = QHBoxLayout()
h_box_3 = QHBoxLayout()
btn_ok = QPushButton('Ok', self)
btn_cancel = QPushButton('Cancel', self)
btn_exit = QPushButton('Exit', self)
btn_ok.resize(10, 10)
btn_cancel.resize(10, 10)
btn_exit.resize(40, 10)
# h_box_1.addStretch(1)
h_box_1.addWidget(btn_ok)
h_box_1.addWidget(btn_cancel)
h_box_1.addWidget(btn_exit)
list_view = QListView(self)
h_box_2.addWidget(list_view)
btn_1 = QPushButton('btn_1', self)
btn_2 = QPushButton('btn_2', self)
btn_3 = QPushButton('btn_3', self)
h_box_3.addStretch(1)
h_box_3.addWidget(btn_1)
h_box_3.addWidget(btn_2)
h_box_3.addWidget(btn_3)
v_box = QVBoxLayout()
v_box.addLayout(h_box_1)
v_box.addLayout(h_box_2)
v_box.addLayout(h_box_3)
###################################
self.centralWidget().setLayout(v_box)
###################################
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
# form_1 = LayoutDemoByQWidget()
form = LayoutDemoByQMainWindow()
sys.exit(app.exec_())
pass
示例代码 2:
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QWidget, QVBoxLayout, QFrame
from PyQt5.Qt import QSize
import sys
class Example(QMainWindow):
def __init__(self):
super(Example, self).__init__()
self._init_ui()
def _init_ui(self):
# 控件随窗口改变而改变
# 可以通过继承 QMainWindow 来实现
self.resize(400, 400)
# 建立顶层控件
self.centeralwidget = QWidget()
self.v_box = QVBoxLayout(self.centeralwidget)
edit = QTextEdit()
self.v_box.addWidget(edit)
# 通过设置中心控件,将子控件填充布局
# 如果有多个控件最好在加一层widget这样最好布局,控制
self.setCentralWidget(self.centeralwidget)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
示例代码 3:
from PyQt5 import QtWidgets
import sys
# 主窗口类
class MainWidget(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.resize(500, 300)
self.setWindowTitle("") # 设置窗口标题
main_widget = QtWidgets.QWidget() # 实例化一个widget部件
main_layout = QtWidgets.QGridLayout() # 实例化一个网格布局层
main_widget.setLayout(main_layout) # 设置主widget部件的布局为网格布局
self.setCentralWidget(main_widget) # 设置窗口默认部件为主widget
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
gui = MainWidget()
gui.show()
sys.exit(app.exec_())
2. 继承 QWidget 时
继承 Qwidget 时,不需要设置中心部件,直接布局就行。最后把 Qwidget 的布局设置成 你自己的布局即可。
self.setLayout(v_box)
完成示例代码:
import sys
# QMainWindow 类提供了一个主要的应用程序窗口。
# 你用它可以让应用程序添加状态栏,工具栏和菜单栏。
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import (
QApplication, QDesktopWidget, QHBoxLayout, QVBoxLayout,
QPushButton, QListView,
)
class LayoutDemoByQWidget(QWidget):
def __init__(self):
super(LayoutDemoByQWidget, self).__init__()
self.resize(1000, 600) # 重置大小
self._center_display() # 中心显示
self.vertical_layout()
self.show()
def _center_display(self):
"""
控制窗口显示在屏幕中心的方法
:return:
"""
# 获得窗口
qr = self.frameGeometry()
# 获得屏幕中心点
cp = QDesktopWidget().availableGeometry().center()
# 显示到屏幕中心
qr.moveCenter(cp)
self.move(qr.topLeft())
def vertical_layout(self):
"""
垂直布局
:return:
"""
h_box_1 = QHBoxLayout()
h_box_2 = QHBoxLayout()
h_box_3 = QHBoxLayout()
btn_ok = QPushButton('Ok', self)
btn_cancel = QPushButton('Cancel', self)
btn_exit = QPushButton('Exit', self)
btn_ok.resize(10, 10)
btn_cancel.resize(10, 10)
btn_exit.resize(40, 10)
# h_box_1.addStretch(1)
h_box_1.addWidget(btn_ok)
h_box_1.addWidget(btn_cancel)
h_box_1.addWidget(btn_exit)
list_view = QListView(self)
h_box_2.addWidget(list_view)
btn_1 = QPushButton('btn_1', self)
btn_2 = QPushButton('btn_2', self)
btn_3 = QPushButton('btn_3', self)
h_box_3.addStretch(1)
h_box_3.addWidget(btn_1)
h_box_3.addWidget(btn_2)
h_box_3.addWidget(btn_3)
v_box = QVBoxLayout()
# v_box.addStretch(1)
v_box.addLayout(h_box_1)
v_box.addLayout(h_box_2)
v_box.addLayout(h_box_3)
# 把 默认布局 设置成 v_box
self.setLayout(v_box)
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
form = LayoutDemoByQWidget()
sys.exit(app.exec_())
pass
一篇文章让你读懂PyQt5布局管理,绝对干货
From:https://cloud.tencent.com/developer/article/1437295
python爬虫实战之路:https://cloud.tencent.com/developer/column/4821
pyqt5 - 布局
Python 图形开发:PyQt 三种界面布局解析:http://www.codebelief.com/article/2017/04/python-gui-development-pyqt-three-kinds-of-layout/
Qt 之水平/垂直布局(QBoxLayout、QHBoxLayout、QVBox
PyQt5 布局大全infodetail-2838999.html
继承图:
在布局中添加控件用addWidght(),添加布局用addLayout()
PyQt5 的界面布局主要有两种方法:绝对定位和局部类。在PyQt5中有四种布局方式:水平布局、垂直布局、网格布局、表单布局。还有两种布局方法:addLayout和addWidget,其中addLayout用于在布局中插入子布局,addWidget用于在布局中插入控件。
- 垂直布局:控件默认按照从上到下的顺序进行纵向添加。
- 水平布局:控件默认按照从左到右的顺序进行横向添加。
- 栅格布局:将窗口分为若干行(row)和列(column)。
- 表单布局:控件以两列的形式布局在窗口中,左边为标签,右边为输入控件。
使用布局管理器
- 绝对布局 这个就不详细说明了,使用 QWidget 的 move、setGeometry 等方法,直接设置其在窗口中的位置。
- 盒子布局(QHBoxLayout 水平布局、QVBoxLayout 垂直布局) 方法:
- stretch(伸缩量),只适用于QBoxLayout布局方式,控件和窗口会随着伸缩量的变大而增加
- alignment,指定对齐方式
- addLayout(self, QLayout, stretch=0) 在窗口的右边添加布局,使用stretch(伸缩量)进行伸缩,默认为0
- addWidget(self, QWidget, stretch, Qt.Alignment) 在布局中添加控件。
- QGridLayout 栅格布局(网格布局) 方法:
- addLayout(QLayout, row, column, Qt.Alignment) 在栅格布局的行(row)、列(column)位置添加新的布局,并设置对齐方式
- addLayout(QLayout, row, column, rowSpan, columnSpan, Qt.Alignment) 在栅格布局中新的布局,从行(row)、列(column)开始,占据rowSpan行、columnSpan列
- addWidget(QWidget, row, column, Qt.Alignment) 在栅格布局的行(row)、列(column)中添加窗口控件,
- addWidget(QWidget, fromRow, fromColumn, rowSpan, columnSpan, Qt.Alignment) 在栅格布局中添加窗口控件,从行(row)、列(column)开始,占据rowSpan行、columnSpan列
- setRowStretch(row, stretch) 在行(row)处添加伸缩量
- setColumnStretch(column, stretch) 在列(column)处添加伸缩量
- QFormLayout 表单布局 方法:
- addRow(QWidget, QWidget)
- addRow(QWidget, QLayout)
- addRow(str, QWidget)
- addRow(str, QLayout) 以上在表单布局最后添加一行数据,设置表单的标签和控件
- addRow(QWidget)
- addRow(QLayout) QWidget和QLayout添加在最后一行,并占据两列宽度
- insertRow(row, QWidget, QWidget)
- insertRow(row, QWidget, QLayout)
- insertRow(row, str, QWidget)
- insertRow(row, str, QLayout) 在指定行添加标签和控件
- insertRow(row, QWidget)
- insertRow(row, QLayout) 在指定行添加控件,并占据两列宽度
盒布局名字虽然听起来怪怪的,但是却非常形象,主要借助两个函数QHBoxLayout(水平方向)和QVBoxLayout(竖直方向),建立一个个水平方向或者是竖直方向的“盒子”,盒子本身整齐排列,同时把盒子内部的组件也整齐排列,这样整体来看就显得会很整齐。就像小时候写作业一样,放置内容之前之间先给你把区域划好了。
示例代码:
import sys
from PyQt5.QtWidgets import (QWidget, QPushButton,
QHBoxLayout, QVBoxLayout, QApplication,QMainWindow)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 添加5个按钮
Button1 = QPushButton("按钮1")
Button2 = QPushButton("按钮2")
Button3 = QPushButton("按钮3")
Button4 = QPushButton("按钮4")
Button5 = QPushButton("按钮5")
#添加一个竖直盒子,两个水平盒子
hbox1 = QHBoxLayout()
hbox2 = QHBoxLayout()
# hbox = QHBoxLayout()
vbox = QVBoxLayout()
#把按钮放在盒子里
vbox.addWidget(Button1)
vbox.addWidget(Button2)
vbox.addWidget(Button3)
hbox2.addWidget(Button4)
hbox2.addWidget(Button5)
#把竖直盒子和水平盒子嵌套在水平盒子中
hbox1.addLayout(vbox)
hbox1.addLayout(hbox2)
self.setLayout(hbox1)
self.setGeometry(400, 400, 600, 300)
self.setWindowTitle('盒布局示例')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
效果如下:
在盒布局里,还有一个特别有用的函数:布局.addStretch(int)
。这个函数很形象,就是在布局之内添加一个弹簧,使组件之间有空隙。int值代表“弹簧”的长度。
在QT Designer中,你可以看到这个组件就是一个弹簧样子的。
需要注意的是:弹簧控件是按照这个弹簧的比例分的,比如:
hbox.addStretch(1)#hbox是一个水平“盒子”
hbox.addWidget(Button1)
hbox.addStretch(1)
hbox.addWidget(Button2)
hbox.addStretch(1)
就是把hbox
这个盒子剩余空间等分成三份,如果把中间的弹簧改成2,那么就是把这个盒子剩余空间等分成4分,中间的空间占两份。对比如下:
布局对齐方式:
参数 | 描述 |
QtCore.Qt.AlignLeft | 水平方向居左对齐 |
QtCore.Qt.AlignRight | 水平方向居右对齐 |
QtCore.Qt.AlignCenter | 水平方向居中对齐 |
QtCore.Qt.AlignJustify | 水平方向两端对齐 |
QtCore.Qt.AlignTop | 垂直方向靠上对齐 |
QtCore.Qt.AlignBottom | 垂直方向靠下对齐 |
QtCore.Qt.AlignVCenter | 垂直方向居中对齐 |
间距
addSpacing(self, int) # 设置各控件的上下间距,通过该方法可以增加额外的控件
addStretch(self, int) # 分配布局大小比例
insertStretch(index, stretch = 0) # 在指定控件间隔处添加布局比例
insertSpacing(index, size) #在指定控件间隔处设置间隔大小
addStretch 是按照比例来调整界面布局,在页面布局中使用广泛,所以我们要使用一定的篇幅来进行代码测试。
使用 addStretch,我们可以实现各种对齐方式,而且更加灵活。
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
layout = QHBoxLayout()
layout.addWidget(QPushButton(str(1)))
layout.addWidget(QPushButton(str(2)))
self.setLayout(layout)
self.setGeometry(300, 300, 400, 100)
self.setWindowTitle("Buttons")
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
默认是所有控件撑满整个布局文件。运行结果截图:
按比例布局:
layout.addWidget(btn)
layout.addWidget(tableWidget)
layout.addLayout(h_layout)
layout.setStretchFactor(btn, 1)
layout.setStretchFactor(tableWidget, 2)
layout.setStretchFactor(h_layout, 2)
调用 setStretchFactor 函数后,三个控件的比例分别为:1:2:2
- 水平居左对齐 ~ QtCore.Qt.AlignLeft
def initUI(self):
layout = QHBoxLayout()
layout.addWidget(QPushButton(str(1))
layout.addWidget(QPushButton(str(2))
layout.addStretch(1) # 新增这一行。把 剩余空间分成一份,最后添加这一份的间隔
......
在两个控件后增加这一行,相当于水平布局中存在:按钮1 ,按钮2, stretch。此时 addStretch 的参数只要大于0,则表示占满整个布局最后一部分,前面的控件显示为正常大小,不要拉伸。
- 水平居右对齐 ~ QtCore.Qt.AlignRight
def initUI(self):
layout = QHBoxLayout()
layout.addStretch(1) # 新增这一行在控件前面
layout.addWidget(QPushButton(str(1))
layout.addWidget(QPushButton(str(2))
......
这一行加入到布局中所有控件之前,相当于水平布局中存在:stretch-按钮1-按钮2,表示占满整个布局的最开始部分,后面的控件显示为正常大小,不要拉伸。
- 水平居中对齐 ~ QtCore.Qt.AlignCenter
def initUI(self):
layout = QHBoxLayout()
layout.addStretch(1) # 前面增加一行
layout.addWidget(QPushButton(str(1))
layout.addWidget(QPushButton(str(2))
layout.addStretch(1) #后面增加一行
......
stretch,按钮1,按钮2,stretch,表示左右两边充满,控件占据中间位置
注意:当一个布局中出现多个addStretch时,后面的参数就有意义了,其表示整个布局的大小减去控件总大小进行n等份分配。
例如:
def initUI(self):
layout = QHBoxLayout()
layout.addStretch(1) # 注意1
layout.addWidget(QPushButton(str(1))
layout.addWidget(QPushButton(str(2))
layout.addStretch(3) # 注意2
表示除去控件1、控件2 的宽度,剩余部分四等份,前面占据一份,最后面占据三份
- 水平两端对齐 ~ QtCore.Qt.AlignJustify
def initUI(self):
layout = QHBoxLayout()
layout.addWidget(QPushButton(str(1))
layout.addStretch(1) # 添加行
layout.addWidget(QPushButton(str(2))
- 垂直顶部对齐 ~ QtCore.Qt.AlignTop
def initUI(self):
layout = QVBoxLayout()
layout.addWidget(QPushButton(str(1))
layout.addWidget(QPushButton(str(2))
layout.addStretch(1) # 添加行
- 垂直底部对齐 ~ QtCore.Qt.AlignBottom
def initUI(self):
layout = QVBoxLayout()
layout.addStretch(1) # 添加行
layout.addWidget(QPushButton(str(1))
layout.addWidget(QPushButton(str(2))
- 垂直居中对齐 ~ QtCore.Qt.AlignVCenter
def initUI(self):
layout = QVBoxLayout()
layout.addStretch(1) # 添加行
layout.addWidget(QPushButton(str(1))
layout.addWidget(QPushButton(str(2))
layout.addStretch(1) # 添加行
- 垂直两端对齐
def initUI(self):
layout = QVBoxLayout()
layout.addWidget(QPushButton(str(1))
layout.addStretch(1) # 添加行
layout.addWidget(QPushButton(str(2))
如果要进行组合布局,例如左上角、右下角等,如下:
def initUI(self):
layout1 = QHBoxLayout()
layout1.addWidget(QPushButton(str(1))
layout1.addWidget(QPushButton(str(2))
layout1.addStretch(1) # 水平居左
layout2 = QVBoxLayout()
layout2.addLayout(layout1)
layout2.addStretch(1) # 垂直顶部对齐
self.setLayout(layout2)
......
addSpacing
addSpacing是设置控件之间的间距。就按照图1的布局及代码进行演示。
def initUI(self):
layout = QHBoxLayout()
layout.addWidget(QPushButton(str(1))
layout.addSpacing(100)
layout.addWidget(QPushButton(str(2))
间距设置可以放置在任何地方,对于调整控件位置十分有效。相当于在控件之间添加了一个空的控件。详细的用法与addStrech类似,参考以上使用即可。
QSplitter
控件大小占比不固定,可以通过拖拽两个控件的边界改变控件占比大小
import sys
from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QFrame,
QSplitter, QTextEdit, QApplication)
from PyQt5.QtCore import Qt
class SplitterExample(QWidget):
def __init__(self):
super(SplitterExample, self).__init__()
self.initUI()
def initUI(self):
# 初始化控件
topleft = QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
textedit = QTextEdit()
# 设置第一个Splitter的布局方向
splitter1 = QSplitter(Qt.Horizontal)
# 为第一个Splitter添加控件,并设置两个控件所占空间大小
splitter1.addWidget(topleft)
splitter1.addWidget(textedit)
splitter1.setSizes([100, 200])
# 设置第二个Splitter的布局方向,将第一个Splitter嵌套在第二个里
splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
# 设置全局布局
hbox = QHBoxLayout(self)
hbox.addWidget(splitter2)
self.setLayout(hbox)
self.setWindowTitle('QSplitter 例子')
self.setGeometry(300, 300, 300, 200)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = SplitterExample()
demo.show()
sys.exit(app.exec_())
【第 5 节】PyQt5 事件 和 信号 演示代码
( 可以 把 __init__ 函数中的 注释逐个取消,然后运行程序看执行效果
# -*- coding: utf-8 -*-
# @Author :
# @File : main.py
# @Software: PyCharm
# @description : XXX
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QWidget, QMainWindow, QLCDNumber, QSlider, QVBoxLayout, QApplication, QPushButton
)
from PyQt5.QtCore import pyqtSignal, QObject
class Communicate(QObject):
"""
创建了一个名为closeApp的信号。
这个信号会在按下鼠标时触发,它连接着QMainWindow的close()插槽。
"""
closeApp = pyqtSignal()
# class MyForm(QWidget):
class MyForm(QMainWindow):
def __init__(self):
super(MyForm, self).__init__()
# self._init_ui_1()
# self._init_ui_2()
# self._init_ui_3()
self._init_ui_4()
def _init_ui_1(self):
"""
展示了一个QtGui.QLCDNumber和QtGui.QSlider。lcd的值会随着滑块的拖动而改变。
:return:
"""
lcd = QLCDNumber(self)
sld = QSlider(Qt.Horizontal, self)
vbox = QVBoxLayout()
vbox.addWidget(lcd)
vbox.addWidget(sld)
self.setLayout(vbox)
##########################################################
# 这里我们将滚动条的 valueChanged信号连接到lcd的display插槽。
# sender是发出信号的对象。
# receiver是接收信号的对象。
# slot(插槽)是对信号做出反应的方法。
sld.valueChanged.connect(lcd.display)
##########################################################
self.setGeometry(300, 300, 500, 300)
self.setWindowTitle('Signal & slot')
self.show()
def _init_ui_2(self):
self.setGeometry(300, 300, 500, 300)
self.setWindowTitle('Event handler')
self.show()
def keyPressEvent(self, e):
"""
重写 keyPressEvent 事件
:param e:
:return:
"""
if e.key() == Qt.Key_Escape:
self.close()
def _init_ui_3(self):
btn1 = QPushButton("Button 1", self)
btn1.move(30, 50)
btn2 = QPushButton("Button 2", self)
btn2.move(150, 50)
# 按钮的点击信号 连接到 槽(槽就是一个函数)buttonClicked
# 两个按钮连接到了同一个插槽。
btn1.clicked.connect(self.buttonClicked)
btn2.clicked.connect(self.buttonClicked)
self.statusBar()
self.setGeometry(300, 300, 500, 300)
self.setWindowTitle('Event sender')
self.show()
def buttonClicked(self):
# 通过调用sender()方法来判断当前按下的是哪个按钮
sender = self.sender()
# 通过调用sender()方法来判断信号源, 并将其名称显示在窗体的状态栏中。
self.statusBar().showMessage(sender.text() + ' was pressed')
def _init_ui_4(self):
"""
通过QObject创建的对象可以发出信号。
这个示例演示了如何发出自定义信号
:return:
"""
# 信号closeApp 是 Communicate的类属性,它由 pyqtSignal()创建。
self.c = Communicate()
# 自定义closeApp信号连接到QMainWindow的close槽
# 当在窗体上点击鼠标时会触发closeApp信号,使程序退出。
self.c.closeApp.connect(self.close)
self.setGeometry(1000, 300, 500, 300)
self.setWindowTitle('Emit signal')
self.show()
def mousePressEvent(self, event):
self.c.closeApp.emit()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MyForm()
sys.exit(app.exec_())
pass
【第 6 节】对话框 演示代码
演示代码:
import sys
from PyQt5.QtWidgets import (
QWidget, QMainWindow, QPushButton, QLineEdit, QInputDialog, QApplication, QFrame, QColorDialog,
QVBoxLayout, QSizePolicy, QLabel, QFontDialog, QTextEdit, QAction, QFileDialog
)
from PyQt5.QtGui import QColor
from PyQt5.QtGui import QIcon
# class MyForm(QWidget):
class MyForm(QMainWindow):
def __init__(self):
super(MyForm, self).__init__()
# self._init_ui_1()
# self._init_ui_2()
# self._init_ui_3()
self._init_ui_4()
def _init_ui_1(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(800, 300, 500, 300)
self.setWindowTitle('Input dialog')
self.show()
def showDialog_1(self):
text, ok = QInputDialog.getText(self, 'Input Dialog', 'Enter your name:')
if ok:
self.le.setText(str(text))
else:
self.le.setText(str(ok))
def _init_ui_2(self):
"""
显示一个按钮和一个QFrame。
QFrame的背景为黑色。通过QColorDialog,我们可以改变它的背景。
:return:
"""
col = QColor(0, 0, 0)
self.btn = QPushButton('Dialog', self)
self.btn.move(20, 20)
self.btn.clicked.connect(self.showDialog_2)
self.frm = QFrame(self)
self.frm.setStyleSheet("QWidget { background-color: %s }" % col.name())
self.frm.setGeometry(130, 22, 100, 100)
self.setGeometry(800, 300, 500, 300)
self.setWindowTitle('Color dialog')
self.show()
def showDialog_2(self):
# 初始化QFrame的颜色为黑色
col = QColorDialog.getColor()
# 先检查col的值。如果点击的是Cancel按钮,返回的颜色值是无效的。
# 当颜色值有效时,我们通过样式表(style sheet)来改变背景颜色。
if col.isValid():
self.frm.setStyleSheet("QWidget { background-color: %s }" % col.name())
def _init_ui_3(self):
"""
创建了一个按钮和一个标签,通过QFontDialog来改变标签的字体
:return:
"""
vbox = QVBoxLayout()
btn = QPushButton('Dialog', self)
btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
btn.move(20, 20)
vbox.addWidget(btn)
btn.clicked.connect(self.showDialog_3)
self.lbl = QLabel('Knowledge only matters', self)
self.lbl.move(130, 20)
vbox.addWidget(self.lbl)
self.setLayout(vbox)
self.setGeometry(800, 300, 500, 300)
self.setWindowTitle('Font dialog')
self.show()
def showDialog_3(self):
# 这一行代码弹出字体选择对话框
# getFont()方法返回字体名称和ok参数,如果用户点击了ok他就是True,否则就是false
font, ok = QFontDialog.getFont()
if ok:
# 点击了ok,标签的字体就会被改变
self.lbl.setFont(font)
def _init_ui_4(self):
"""
这个例子展示了一个菜单栏,中部TextEdit控件和一个状态栏。
菜单项 Open会显示用于选择文件的QtGui.QFileDialog对话框。
选定文件的内容会加载到TextEdit控件中。
:return:
"""
self.textEdit = QTextEdit()
self.setCentralWidget(self.textEdit)
self.statusBar()
openFile = QAction(QIcon('open.png'), 'Open', self)
openFile.setShortcut('Ctrl+O')
openFile.setStatusTip('Open new File')
openFile.triggered.connect(self.showDialog_4)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(openFile)
self.setGeometry(300, 300, 350, 300)
self.setWindowTitle('File dialog')
self.show()
def showDialog_4(self):
# 弹出QFileDialog对话框,
# 第一个字符串参数是对话框的标题,
# 第二个指定对话框的工作目录,默认情况下文件筛选器会匹配所有类型的文件(*)
fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')
if fname[0]:
f = open(fname[0], 'r')
with f:
data = f.read()
self.textEdit.setText(data)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MyForm()
sys.exit(app.exec_())
pass
【第 7 节】和 【第 8 节】 控件 演示代码
示例代码:
import sys
from PyQt5.QtWidgets import (
QWidget, QCheckBox, QApplication, QPushButton, QLineEdit, QSplitter,
QFrame, QSlider, QLabel, QProgressBar, QCalendarWidget, QHBoxLayout, QComboBox
)
from PyQt5.QtCore import Qt, QBasicTimer, QDate
from PyQt5.QtGui import QPixmap
from PyQt5.QtGui import QColor
class MyForm(QWidget):
def __init__(self):
super(MyForm, self).__init__()
# self._init_ui_1()
# self._init_ui_2()
# self._init_ui_3()
# self._init_ui_4()
# self._init_ui_5()
# self._init_ui_6()
# self._init_ui_7()
# self._init_ui_8()
self._init_ui_9()
def _init_ui_1(self):
"""
QCheckBox复选框控件,它有两个状态:打开和关闭,
他是一个带有文本标签(Label)的控件。
复选框常用于表示程序中可以启用或禁用的功能。
:return:
"""
cb = QCheckBox('Show title', self)
cb.move(20, 20)
cb.toggle()
cb.stateChanged.connect(self.changeTitle)
self.setGeometry(800, 300, 500, 300)
self.setWindowTitle('QCheckBox')
self.show()
def changeTitle(self, state):
if state == Qt.Checked:
self.setWindowTitle('QCheckBox')
else:
self.setWindowTitle('')
def _init_ui_2(self):
"""
开关按钮 Toggle button是 QPushButton的一种特殊模式。它是一个有两种状态的按钮:按下与未按下。
通过点击在这两种状态间来回切换。这种功能在某些场景会很实用。
代码中我们创建了三个ToggleButton与一个QWidget。将QWidget的背景色设为黑色。
ToggleButton会切换颜色值中的红色、绿色与蓝色部分。QWidget的背景颜色依赖于按下的按钮。
:return:
"""
# 初始黑颜色的值。
self.col = QColor(0, 0, 0)
# 创建一个 QPushButton 并通过其 setCheckable() 方法来得到一个ToggleButton。
redb = QPushButton('Red', self)
redb.setCheckable(True)
redb.move(10, 10)
# 将clicked信号连接到用户自定义的方法。我们通过clicked信号操作一个布尔值。
redb.clicked[bool].connect(self.setColor)
greenb = QPushButton('Green', self)
greenb.setCheckable(True)
greenb.move(10, 60)
greenb.clicked[bool].connect(self.setColor)
blueb = QPushButton('Blue', self)
blueb.setCheckable(True)
blueb.move(10, 110)
blueb.clicked[bool].connect(self.setColor)
self.square = QFrame(self)
self.square.setGeometry(150, 20, 100, 100)
self.square.setStyleSheet("QWidget { background-color: %s }" % self.col.name())
self.setGeometry(800, 300, 600, 300)
self.setWindowTitle('Toggle button')
self.show()
def setColor(self, pressed):
source = self.sender()
# 得到按下的按钮。
if pressed:
val = 255
else:
val = 0
if source.text() == "Red":
self.col.setRed(val)
elif source.text() == "Green":
self.col.setGreen(val)
else:
self.col.setBlue(val)
self.square.setStyleSheet("QFrame { background-color: %s }" % self.col.name())
def _init_ui_3(self):
"""
QSlider是一个带有简单滑块的控件。滑块可以前后拖动。可以通过拖动选择一个特定的值。
有时使用滑动条比直接输入数字或使用旋转框更加自然。
:return:
"""
sld = QSlider(Qt.Horizontal, self)
sld.setFocusPolicy(Qt.NoFocus)
sld.setGeometry(30, 40, 200, 50)
sld.valueChanged[int].connect(self.changeValue)
self.label = QLabel(self)
self.label.setPixmap(QPixmap('audio.ico'))
self.label.setText(str(0))
self.label.setGeometry(400, 40, 80, 30)
self.setGeometry(800, 300, 600, 300)
self.setWindowTitle('QSlider')
self.show()
def changeValue(self, value: int):
# if value == 0:
# self.label.setPixmap(QPixmap('audio.ico'))
# elif value > 0 and value <= 30:
# self.label.setPixmap(QPixmap('min.ico'))
# elif value > 30 and value < 80:
# self.label.setPixmap(QPixmap('med.ico'))
# else:
# self.label.setPixmap(QPixmap('max.ico'))
self.label.setText(str(value))
def _init_ui_4(self):
self.pbar = QProgressBar(self)
self.pbar.setGeometry(30, 40, 200, 25)
self.btn = QPushButton('Start', self)
self.btn.move(40, 80)
self.btn.clicked.connect(self.doAction)
self.timer = QBasicTimer()
self.step = 0
self.setGeometry(800, 300, 600, 300)
self.setWindowTitle('QProgressBar')
self.show()
def timerEvent(self, e):
if self.step >= 100:
self.timer.stop()
self.btn.setText('Finished')
return
self.step = self.step + 1
self.pbar.setValue(self.step)
def doAction(self):
if self.timer.isActive():
self.timer.stop()
self.btn.setText('Start')
else:
self.timer.start(100, self)
self.btn.setText('Stop')
def _init_ui_5(self):
cal = QCalendarWidget(self)
cal.setGridVisible(True)
cal.move(20, 20)
# 从部件选择一个日期,点击[QDate]发出信号。将这个信号连接到用户定义的showDate()方法。
cal.clicked[QDate].connect(self.showDate)
self.lbl = QLabel(self)
date = cal.selectedDate()
self.lbl.setText(date.toString())
self.lbl.move(130, 260)
self.setGeometry(800, 300, 700, 600)
self.setWindowTitle('Calendar')
self.show()
def showDate(self, date):
self.lbl.setText(date.toString())
def _init_ui_6(self):
"""
像素图
:return:
"""
hbox = QHBoxLayout(self)
pixmap = QPixmap("icon.png")
lbl = QLabel(self)
lbl.setPixmap(pixmap)
hbox.addWidget(lbl)
self.setLayout(hbox)
self.move(300, 200)
self.setWindowTitle('Red Rock')
self.show()
def _init_ui_7(self):
"""
文本框
:return:
"""
self.lbl = QLabel(self)
qle = QLineEdit(self)
qle.move(60, 100)
self.lbl.move(60, 40)
# 文本框的内容发生改变的时候,会调用onChanged方法
qle.textChanged[str].connect(self.onChanged)
self.setGeometry(800, 300, 500, 300)
self.setWindowTitle('QLineEdit')
self.show()
def onChanged(self, text):
self.lbl.setText(text)
# 通过调用adjustSize()方法将QLabel控件的尺寸调整为文本的长度。
self.lbl.adjustSize()
def _init_ui_8(self):
"""
通过QSplitter,用户可以拖动子控件边界来调整子控件的尺寸
:return:
"""
hbox = QHBoxLayout(self)
topleft = QFrame(self)
topleft.setFrameShape(QFrame.StyledPanel)
topright = QFrame(self)
topright.setFrameShape(QFrame.StyledPanel)
bottom = QFrame(self)
bottom.setFrameShape(QFrame.StyledPanel)
splitter1 = QSplitter(Qt.Horizontal)
splitter1.addWidget(topleft)
splitter1.addWidget(topright)
splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
hbox.addWidget(splitter2)
self.setLayout(hbox)
self.setGeometry(800, 300, 600, 300)
self.setWindowTitle('QSplitter')
self.show()
def _init_ui_9(self):
self.lbl = QLabel("Ubuntu", self)
combo = QComboBox(self)
combo.addItem("Ubuntu")
combo.addItem("Mandriva")
combo.addItem("Fedora")
combo.addItem("Arch")
combo.addItem("Gentoo")
combo.move(50, 50)
self.lbl.move(50, 150)
combo.activated[str].connect(self.onActivated)
self.setGeometry(800, 300, 600, 300)
self.setWindowTitle('QComboBox')
self.show()
def onActivated(self, text):
self.lbl.setText(text)
self.lbl.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MyForm()
sys.exit(app.exec_())
pass
【第九节】PyQt 拖拽 演示代码
示例代码:
import sys
from PyQt5.QtWidgets import QPushButton, QWidget, QApplication
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag
class Button(QPushButton):
def __init__(self, title, parent):
super().__init__(title, parent)
def mouseMoveEvent(self, e):
if e.buttons() != Qt.RightButton:
return
mimeData = QMimeData()
drag = QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(e.pos() - self.rect().topLeft())
dropAction = drag.exec_(Qt.MoveAction)
def mousePressEvent(self, e):
QPushButton.mousePressEvent(self, e)
if e.button() == Qt.LeftButton:
print('press')
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setAcceptDrops(True)
self.button = Button('Button', self)
self.button.move(100, 65)
self.setWindowTitle('Click or Move')
self.setGeometry(300, 300, 280, 150)
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
position = e.pos()
self.button.move(position)
e.setDropAction(Qt.MoveAction)
e.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()
在这个例子中,在窗口显示一个QPushButton 。如果用鼠标左键点击这个按钮会在控制台中输出’press’消息。鼠标右击进行拖动。
我们从 QPushButton 派生了一个 Button 类,并重新实现了 mouseMoveEvent() 与 mousePressEvent() 方法。mouseMoveEvent() 方法是拖放操作产生的地方。
if e.buttons() != Qt.RightButton:
return
上面代码设置只在鼠标右击时才执行拖放操作。鼠标左击用于按钮的点击事件。
mimeData = QMimeData()
drag = QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(e.pos() - self.rect().topLeft())
QDrag 提供了对基于 MIME 的拖放的数据传输的支持。
dropAction = drag.exec_(Qt.MoveAction)
Drag对象的exec_()方法用于启动拖放操作。
def mousePressEvent(self, e):
QPushButton.mousePressEvent(self, e)
if e.button() == Qt.LeftButton:
print('press')
鼠标左击按钮时我们会在控制台打印‘press’。注意我们也调用了父按钮的mousePressEvent()方法。否则会看不到按钮的按下效果。
position = e.pos()
self.button.move(position)
释放右键后调用dropEvent()方法中,即找出鼠标指针的当前位置,并将按钮移动过去。
e.setDropAction(Qt.MoveAction)
e.accept()
我们可以对指定的类型放弃行动。在我们的例子中它是一个移动动作。
【第十节】PyQt5 绘图 演示代码
PyQt5 绘画系统能够呈现矢量图形,图像,和大纲 font-based文本。我们也可以在程序中调用系统 api 自定义绘图控件。
绘图要在 paintEvent() 方法中实现。在 QPainter 对象的 begin() 与 end() 方法间编写绘图代码。它会在控件或其他图形设备上进行低级的图形绘制。
绘制文本
以窗体内Unicode文本的绘制为例。示例代码:
import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.text = u'\u041b\u0435\u0432 \u041d\u0438\u043a\u043e\u043b\u0430\
\u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441\u0442\u043e\u0439: \n\
\u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430'
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Draw text')
self.show()
def paintEvent(self, event):
qp = QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
def drawText(self, event, qp):
qp.setPen(QColor(168, 34, 3))
qp.setFont(QFont('Decorative', 10))
qp.drawText(event.rect(), Qt.AlignCenter, self.text)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
在我们的示例中,我们绘制一些Cylliric文本。文本垂直和水平对齐。
def paintEvent(self, event):
...
绘制工作在paintEvent的方法内部完成。
qp = QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
QPainter类负责所有的初级绘制。之间的所有绘画方法去start()和end()方法。实际的绘画被委托给drawText()方法。
qp.setPen(QColor(168, 34, 3))
qp.setFont(QFont('Decorative', 10))
在这里,我们定义一个画笔和一个字体用于绘制文本。
qp.drawText(event.rect(), Qt.AlignCenter, self.text)
drawText()方法将文本绘制在窗体,显示在中心
画点
点是可以绘制的最简单的图形对象。
import sys, random
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Points')
self.show()
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
self.drawPoints(qp)
qp.end()
def drawPoints(self, qp):
qp.setPen(Qt.red)
size = self.size()
for i in range(1000):
x = random.randint(1, size.width() - 1)
y = random.randint(1, size.height() - 1)
qp.drawPoint(x, y)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
在这例子中,我们在窗口上随机绘制了1000个红点
设置画笔为红色,我们使用了预定义的Qt.red常量
qp.setPen(Qt.red)
每次我们改变窗口的大小,生成一个 paint event 事件。我们得到的当前窗口的大小size。我们使用窗口的大小来分配点在窗口的客户区。
size = self.size()
通过drawpoint绘制圆点
qp.drawPoint(x, y)
qp.drawPoint(x, y)
颜色
颜色是一个对象代表红、绿、蓝(RGB)强度值。有效的RGB值的范围从0到255。我们可以用不同的方法定义了一个颜色。最常见的是RGB十进制或十六进制值的值。我们也可以使用一个RGBA值代表红色,绿色,蓝色,透明度。我们添加一些额外的信息透明度。透明度值255定义了完全不透明,0是完全透明的,例如颜色是无形的。
示例代码:
import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QBrush
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 350, 100)
self.setWindowTitle('Colours')
self.show()
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
self.drawRectangles(qp)
qp.end()
def drawRectangles(self, qp):
col = QColor(0, 0, 0)
col.setNamedColor('#d4d4d4')
qp.setPen(col)
qp.setBrush(QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)
qp.setBrush(QColor(255, 80, 0, 160))
qp.drawRect(130, 15, 90, 60)
qp.setBrush(QColor(25, 0, 90, 200))
qp.drawRect(250, 15, 90, 60)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
实例中我们绘制了3个不同颜色的矩形
在这里,我们定义一个使用十六进制符号颜色。
color = QColor(0, 0, 0)
color.setNamedColor('#d4d4d4')
我们为QPainter设置了一个笔刷(Bursh)对象并用它绘制了一个矩形。笔刷是用于绘制形状背景的基本图形对象。drawRect()方法接受四个参数,前两个是起点的x,y坐标,后两个是矩形的宽和高。这个方法使用当前的画笔与笔刷对象进行绘制。
qp.setBrush(QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)
QPen(画笔)
QPen是一个基本的图形对象。用于绘制线条、曲线和轮廓的矩形、椭圆、多边形或其他形状。
import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 280, 270)
self.setWindowTitle('Pen styles')
self.show()
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
self.drawLines(qp)
qp.end()
def drawLines(self, qp):
pen = QPen(Qt.black, 2, Qt.SolidLine)
qp.setPen(pen)
qp.drawLine(20, 40, 250, 40)
pen.setStyle(Qt.DashLine)
qp.setPen(pen)
qp.drawLine(20, 80, 250, 80)
pen.setStyle(Qt.DashDotLine)
qp.setPen(pen)
qp.drawLine(20, 120, 250, 120)
pen.setStyle(Qt.DotLine)
qp.setPen(pen)
qp.drawLine(20, 160, 250, 160)
pen.setStyle(Qt.DashDotDotLine)
qp.setPen(pen)
qp.drawLine(20, 200, 250, 200)
pen.setStyle(Qt.CustomDashLine)
pen.setDashPattern([1, 4, 5, 4])
qp.setPen(pen)
qp.drawLine(20, 240, 250, 240)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
示例中我们画六行。线条勾勒出了六个不同的笔风格。有五个预定义的钢笔样式。我们也可以创建自定义的钢笔样式。最后一行使用一个定制的钢笔绘制风格。
我们创建一个QPen对象。颜色是黑色的。宽度设置为2像素,这样我们可以看到笔风格之间的差异。Qt.SolidLine是预定义的钢笔样式。
pen = QPen(Qt.black, 2, Qt.SolidLine)
这里我们定义了一个画笔风格。我们设置了Qt.CustomDashLine并调用了setDashPattern()方法,它的参数(一个数字列表)定义了一种风格,必须有偶数个数字;其中奇数表示绘制实线,偶数表示留空。数值越大,直线或空白就越大。这里我们定义了1像素的实线,4像素的空白,5像素实线,4像素空白。。。
pen.setStyle(Qt.CustomDashLine)
pen.setDashPattern([1, 4, 5, 4])
qp.setPen(pen)
QBrush(笔刷)
QBrush是一个基本的图形对象。它用于油漆的背景图形形状,如矩形、椭圆形或多边形。三种不同类型的刷可以:一个预定义的刷,一个梯度,或纹理模式。
import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QBrush
from PyQt5.QtCore import Qt
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 355, 280)
self.setWindowTitle('Brushes')
self.show()
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
self.drawBrushes(qp)
qp.end()
def drawBrushes(self, qp):
brush = QBrush(Qt.SolidPattern)
qp.setBrush(brush)
qp.drawRect(10, 15, 90, 60)
brush.setStyle(Qt.Dense1Pattern)
qp.setBrush(brush)
qp.drawRect(130, 15, 90, 60)
brush.setStyle(Qt.Dense2Pattern)
qp.setBrush(brush)
qp.drawRect(250, 15, 90, 60)
brush.setStyle(Qt.DiagCrossPattern)
qp.setBrush(brush)
qp.drawRect(10, 105, 90, 60)
brush.setStyle(Qt.Dense5Pattern)
qp.setBrush(brush)
qp.drawRect(130, 105, 90, 60)
brush.setStyle(Qt.Dense6Pattern)
qp.setBrush(brush)
qp.drawRect(250, 105, 90, 60)
brush.setStyle(Qt.HorPattern)
qp.setBrush(brush)
qp.drawRect(10, 195, 90, 60)
brush.setStyle(Qt.VerPattern)
qp.setBrush(brush)
qp.drawRect(130, 195, 90, 60)
brush.setStyle(Qt.BDiagPattern)
qp.setBrush(brush)
qp.drawRect(250, 195, 90, 60)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
示例中绘制九个不同的矩形
我们定义了一个笔刷对象,然后将它设置给QPainter对象,并调用painter的drawRect()方法绘制矩形。
brush = QBrush(Qt.SolidPattern)
qp.setBrush(brush)
qp.drawRect(10, 15, 90, 60)
【第十一节】PyQt5 自定义控件 演示代码
PyQt5包含种类丰富的控件。但能满足所有需求的控件库是不存在的。通常控件库只提供了像按钮、文本控件、滑块等最常用的控件。但如果需要某种特殊的控件,我们只能自己动手来实现。 自定义控件需要使用工具库提供的绘图工具,可能有两种方式:在已有的控件上进行拓展或从头开始创建自定义控件。
Burning widget(烧录控件)
这个控件可能会在Nero,K3B或其他CD/DVD烧录软件中见到。
import sys
from PyQt5.QtWidgets import (QWidget, QSlider, QApplication,
QHBoxLayout, QVBoxLayout)
from PyQt5.QtCore import QObject, Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QColor, QPen
class Communicate(QObject):
updateBW = pyqtSignal(int)
class BurningWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setMinimumSize(1, 30)
self.value = 75
self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]
def setValue(self, value):
self.value = value
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
self.drawWidget(qp)
qp.end()
def drawWidget(self, qp):
font = QFont('Serif', 7, QFont.Light)
qp.setFont(font)
size = self.size()
w = size.width()
h = size.height()
step = int(round(w / 10.0))
till = int(((w / 750.0) * self.value))
full = int(((w / 750.0) * 700))
if self.value >= 700:
qp.setPen(QColor(255, 255, 255))
qp.setBrush(QColor(255, 255, 184))
qp.drawRect(0, 0, full, h)
qp.setPen(QColor(255, 175, 175))
qp.setBrush(QColor(255, 175, 175))
qp.drawRect(full, 0, till - full, h)
else:
qp.setPen(QColor(255, 255, 255))
qp.setBrush(QColor(255, 255, 184))
qp.drawRect(0, 0, till, h)
pen = QPen(QColor(20, 20, 20), 1,
Qt.SolidLine)
qp.setPen(pen)
qp.setBrush(Qt.NoBrush)
qp.drawRect(0, 0, w - 1, h - 1)
j = 0
for i in range(step, 10 * step, step):
qp.drawLine(i, 0, i, 5)
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i - fw / 2, h / 2, str(self.num[j]))
j = j + 1
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
sld = QSlider(Qt.Horizontal, self)
sld.setFocusPolicy(Qt.NoFocus)
sld.setRange(1, 750)
sld.setValue(75)
sld.setGeometry(30, 40, 150, 30)
self.c = Communicate()
self.wid = BurningWidget()
self.c.updateBW[int].connect(self.wid.setValue)
sld.valueChanged[int].connect(self.changeValue)
hbox = QHBoxLayout()
hbox.addWidget(self.wid)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.setGeometry(300, 300, 390, 210)
self.setWindowTitle('Burning widget')
self.show()
def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
在示例中我们使用了滑块与一个自定义控件。自定义控件受滑块控制。控件显示了媒体介质的容量和剩余空间。该控件的最小值为1,最大值为750。在值超过700时颜色变为红色。这通常意味着超刻(即实际写入光盘的容量超过刻录盘片官方标称容量的一种操作)。
BurningWidget控件通过QHBoxLayout与QVBoxLayout置于窗体的底部。
class BurningWidget(QWidget):
def __init__(self):
super().__init__()
烧录的控件,它基于QWidget
self.setMinimumSize(1, 30)
我们改变了控件的最小大小(高度),默认值为有点小。
font = QFont('Serif', 7, QFont.Light)
qp.setFont(font)
我们使用一个比默认要小的字体。
size = self.size()
w = size.width()
h = size.height()
step = int(round(w / 10.0))
till = int(((w / 750.0) * self.value))
full = int(((w / 750.0) * 700))
控件采用了动态绘制技术。窗体越大,控件也随之变大;反之亦然。这也是我们需要计算自定义控件的载体控件(即窗体)尺寸的原因。till参数定义了需要绘制的总尺寸,它根据slider控件计算得出,是整体区域的比例值。full参数定义了红色区域的绘制起点。注意在绘制时为取得较大精度而使用的浮点数运算。
实际的绘制分三个步骤。黄色或红黄矩形的绘制,然后是刻度线的绘制,最后是刻度值的绘制。
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))
我们使用字体度量来绘制文本。我们必须知道文本的宽度,以中心垂直线。
def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()
当滑块发生移动时,changeValue()方法会被调用。在方法内我们触发了一个自定义的updateBW信号,其参数是当前滚动条的值。该值被用于计算Burning widget的容量值。然后对控件进行重绘。
【第十二节】PyQt5 俄罗斯方块
俄罗斯方块
俄罗斯方块游戏是有史以来最受欢迎的电脑游戏。最初的游戏是由俄罗斯设计和编程的程序员阿列克谢帕基特诺夫于1985年。此后,俄罗斯方块是几乎所有的计算机平台上可用在很多变化。
俄罗斯方块称为积木拼图游戏。在这个游戏中,我们有七种不同形状叫tetrominoes:“s”形,Z-shape,t形,一个l型的空间,一个线,MirroredL-shape和正方形。这些形状的形成有四个方格。形状是跌倒。俄罗斯方块游戏的对象是移动和旋转的形状使他们适合尽可能多。如果我们设法形成一个行,该行摧毁我们得分。我们直到我们玩俄罗斯方块游戏。
PyQt5 是一种用于创建应用程序的工具。还有其他的库是针对创建电脑游戏。然而,PyQt5和其他应用程序工具包可以用来创建简单的游戏。
创建一个电脑游戏是一个提高编程技能的很好的方发。
开发
在俄罗斯方块中没有图片,我们使用PyQt5编程工具包绘图API中绘制图形。每一个电脑游戏的背后,都有一个数学模型。在俄罗斯方块中更是如此。
游戏的一些思想:
我们使用QtCore.QBasicTimer()来创建一个游戏循环。
俄罗斯方块是绘制的。
图形是一个方块一个方块移动的(不是像素)
图形其实是一个简单的数字列表。
代码包括四类:Tetris, Board, Tetrominoe 和Shape。Tetris 类用来存放游戏。Board是编写游戏逻辑的地方。Tetrominoe类包含所有俄罗斯方块的名称,Shape类包含一个俄罗斯方块的代码。
# -*- coding: utf-8 -*-
"""
PyQt5 tutorial
This is a Tetris game clone..
author: py40.com
last edited: 2017年3月
"""
import sys, random
from PyQt5.QtWidgets import QMainWindow, QFrame, QDesktopWidget, QApplication
from PyQt5.QtCore import Qt, QBasicTimer, pyqtSignal
from PyQt5.QtGui import QPainter, QColor
class Tetris(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.tboard = Board(self)
self.setCentralWidget(self.tboard)
self.statusbar = self.statusBar()
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
self.tboard.start()
self.resize(180, 380)
self.center()
self.setWindowTitle('Tetris')
self.show()
def center(self):
screen = QDesktopWidget().screenGeometry()
size = self.geometry()
self.move((screen.width() - size.width()) / 2,
(screen.height() - size.height()) / 2)
class Board(QFrame):
msg2Statusbar = pyqtSignal(str)
BoardWidth = 10
BoardHeight = 22
Speed = 300
def __init__(self, parent):
super().__init__(parent)
self.initBoard()
def initBoard(self):
self.timer = QBasicTimer()
self.isWaitingAfterLine = False
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
self.setFocusPolicy(Qt.StrongFocus)
self.isStarted = False
self.isPaused = False
self.clearBoard()
def shapeAt(self, x, y):
return self.board[(y * Board.BoardWidth) + x]
def setShapeAt(self, x, y, shape):
self.board[(y * Board.BoardWidth) + x] = shape
def squareWidth(self):
return self.contentsRect().width() // Board.BoardWidth
def squareHeight(self):
return self.contentsRect().height() // Board.BoardHeight
def start(self):
if self.isPaused:
return
self.isStarted = True
self.isWaitingAfterLine = False
self.numLinesRemoved = 0
self.clearBoard()
self.msg2Statusbar.emit(str(self.numLinesRemoved))
self.newPiece()
self.timer.start(Board.Speed, self)
def pause(self):
if not self.isStarted:
return
self.isPaused = not self.isPaused
if self.isPaused:
self.timer.stop()
self.msg2Statusbar.emit("paused")
else:
self.timer.start(Board.Speed, self)
self.msg2Statusbar.emit(str(self.numLinesRemoved))
self.update()
def paintEvent(self, event):
painter = QPainter(self)
rect = self.contentsRect()
boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()
for i in range(Board.BoardHeight):
for j in range(Board.BoardWidth):
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
if shape != Tetrominoe.NoShape:
self.drawSquare(painter,
rect.left() + j * self.squareWidth(),
boardTop + i * self.squareHeight(), shape)
if self.curPiece.shape() != Tetrominoe.NoShape:
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.drawSquare(painter, rect.left() + x * self.squareWidth(),
boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
self.curPiece.shape())
def keyPressEvent(self, event):
if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
super(Board, self).keyPressEvent(event)
return
key = event.key()
if key == Qt.Key_P:
self.pause()
return
if self.isPaused:
return
elif key == Qt.Key_Left:
self.tryMove(self.curPiece, self.curX - 1, self.curY)
elif key == Qt.Key_Right:
self.tryMove(self.curPiece, self.curX + 1, self.curY)
elif key == Qt.Key_Down:
self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)
elif key == Qt.Key_Up:
self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
elif key == Qt.Key_Space:
self.dropDown()
elif key == Qt.Key_D:
self.oneLineDown()
else:
super(Board, self).keyPressEvent(event)
def timerEvent(self, event):
if event.timerId() == self.timer.timerId():
if self.isWaitingAfterLine:
self.isWaitingAfterLine = False
self.newPiece()
else:
self.oneLineDown()
else:
super(Board, self).timerEvent(event)
def clearBoard(self):
for i in range(Board.BoardHeight * Board.BoardWidth):
self.board.append(Tetrominoe.NoShape)
def dropDown(self):
newY = self.curY
while newY > 0:
if not self.tryMove(self.curPiece, self.curX, newY - 1):
break
newY -= 1
self.pieceDropped()
def oneLineDown(self):
if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
self.pieceDropped()
def pieceDropped(self):
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.setShapeAt(x, y, self.curPiece.shape())
self.removeFullLines()
if not self.isWaitingAfterLine:
self.newPiece()
def removeFullLines(self):
numFullLines = 0
rowsToRemove = []
for i in range(Board.BoardHeight):
n = 0
for j in range(Board.BoardWidth):
if not self.shapeAt(j, i) == Tetrominoe.NoShape:
n = n + 1
if n == 10:
rowsToRemove.append(i)
rowsToRemove.reverse()
for m in rowsToRemove:
for k in range(m, Board.BoardHeight):
for l in range(Board.BoardWidth):
self.setShapeAt(l, k, self.shapeAt(l, k + 1))
numFullLines = numFullLines + len(rowsToRemove)
if numFullLines > 0:
self.numLinesRemoved = self.numLinesRemoved + numFullLines
self.msg2Statusbar.emit(str(self.numLinesRemoved))
self.isWaitingAfterLine = True
self.curPiece.setShape(Tetrominoe.NoShape)
self.update()
def newPiece(self):
self.curPiece = Shape()
self.curPiece.setRandomShape()
self.curX = Board.BoardWidth // 2 + 1
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
if not self.tryMove(self.curPiece, self.curX, self.curY):
self.curPiece.setShape(Tetrominoe.NoShape)
self.timer.stop()
self.isStarted = False
self.msg2Statusbar.emit("Game over")
def tryMove(self, newPiece, newX, newY):
for i in range(4):
x = newX + newPiece.x(i)
y = newY - newPiece.y(i)
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
return False
if self.shapeAt(x, y) != Tetrominoe.NoShape:
return False
self.curPiece = newPiece
self.curX = newX
self.curY = newY
self.update()
return True
def drawSquare(self, painter, x, y, shape):
colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
color = QColor(colorTable[shape])
painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,
self.squareHeight() - 2, color)
painter.setPen(color.lighter())
painter.drawLine(x, y + self.squareHeight() - 1, x, y)
painter.drawLine(x, y, x + self.squareWidth() - 1, y)
painter.setPen(color.darker())
painter.drawLine(x + 1, y + self.squareHeight() - 1,
x + self.squareWidth() - 1, y + self.squareHeight() - 1)
painter.drawLine(x + self.squareWidth() - 1,
y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)
class Tetrominoe(object):
NoShape = 0
ZShape = 1
SShape = 2
LineShape = 3
TShape = 4
SquareShape = 5
LShape = 6
MirroredLShape = 7
class Shape(object):
coordsTable = (
((0, 0), (0, 0), (0, 0), (0, 0)),
((0, -1), (0, 0), (-1, 0), (-1, 1)),
((0, -1), (0, 0), (1, 0), (1, 1)),
((0, -1), (0, 0), (0, 1), (0, 2)),
((-1, 0), (0, 0), (1, 0), (0, 1)),
((0, 0), (1, 0), (0, 1), (1, 1)),
((-1, -1), (0, -1), (0, 0), (0, 1)),
((1, -1), (0, -1), (0, 0), (0, 1))
)
def __init__(self):
self.coords = [[0, 0] for i in range(4)]
self.pieceShape = Tetrominoe.NoShape
self.setShape(Tetrominoe.NoShape)
def shape(self):
return self.pieceShape
def setShape(self, shape):
table = Shape.coordsTable[shape]
for i in range(4):
for j in range(2):
self.coords[i][j] = table[i][j]
self.pieceShape = shape
def setRandomShape(self):
self.setShape(random.randint(1, 7))
def x(self, index):
return self.coords[index][0]
def y(self, index):
return self.coords[index][1]
def setX(self, index, x):
self.coords[index][0] = x
def setY(self, index, y):
self.coords[index][1] = y
def minX(self):
m = self.coords[0][0]
for i in range(4):
m = min(m, self.coords[i][0])
return m
def maxX(self):
m = self.coords[0][0]
for i in range(4):
m = max(m, self.coords[i][0])
return m
def minY(self):
m = self.coords[0][1]
for i in range(4):
m = min(m, self.coords[i][1])
return m
def maxY(self):
m = self.coords[0][1]
for i in range(4):
m = max(m, self.coords[i][1])
return m
def rotateLeft(self):
if self.pieceShape == Tetrominoe.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, self.y(i))
result.setY(i, -self.x(i))
return result
def rotateRight(self):
if self.pieceShape == Tetrominoe.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, -self.y(i))
result.setY(i, self.x(i))
return result
if __name__ == '__main__':
app = QApplication([])
tetris = Tetris()
sys.exit(app.exec_())
游戏简化一点,让它更容易理解。在比赛开始后立即启动。我们可以通过按p键暂停游戏。空格键将立即把俄罗斯方块块底部。游戏是在恒定速度,实现没有加速度。分数是我们已经删除的行数。
self.tboard = Board(self)
self.setCentralWidget(self.tboard)
Board创建一个面板类的实例,并设置应用程序的核心部件。
self.statusbar = self.statusBar()
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
我们创建一个状态栏将显示消息。我们将显示三种可能的消息:已删除的行数,停顿了一下消息,或游戏结束的消息。msg2Statusbar是一个自定义的信号,在Board 中实现类。showMessage()是一个内置的方法,在状态栏显示一条消息。
self.tboard.start()
这一行代码启动游戏
class Board(QFrame):
msg2Statusbar = pyqtSignal(str)
...
创建一个自定义的信号。当我们想写一个信息或状态栏的分数的时候,msg2Statusbar发出一个信号
BoardWidth = 10
BoardHeight = 22
Speed = 300
这些都是Board的类变量。BoardWidth和BoardHeight定义的块的大小。Speed定义了游戏的速度。每个300 ms将开始一个新游戏循环。
...
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
...
我们在initBoard()方法初始化一些重要的变量。board变量是一个从0到7的数字列表。它代表了面板上各种形状和位置。
def shapeAt(self, x, y):
return self.board[(y * Board.BoardWidth) + x]
shapeAt()方法确定在给定形状块的类型。
def squareWidth(self):
return self.contentsRect().width() // Board.BoardWidth
Board可以动态地调整大小。因此,块的大小可能会有所改变。squareWidth()计算单一方块像素的宽度并返回它。Board.BoardWidth方块板的大小。
for i in range(Board.BoardHeight):
for j in range(Board.BoardWidth):
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
if shape != Tetrominoe.NoShape:
self.drawSquare(painter,
rect.left() + j * self.squareWidth(),
boardTop + i * self.squareHeight(), shape)
游戏的绘制分为两个步骤,第一步,绘制所有方块,这些方块都要保存在底部列表中。列表通过shapeAt() 方法来添加方块。
if self.curPiece.shape() != Tetrominoe.NoShape:
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
self.drawSquare(painter, rect.left() + x * self.squareWidth(),
boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
self.curPiece.shape())
第二步绘制下降中的方块
elif key == Qt.Key_Right:
self.tryMove(self.curPiece, self.curX + 1, self.curY)
keyPressEvent()方法检查按下键。当按右箭头键,我们试图向右移动一块。我们使用tyrMove,因为可能无法移动。
elif key == Qt.Key_Up:
self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
向上箭头键将旋转方块。
elif key == Qt.Key_Space:
self.dropDown()
空格键立即下降到底部
elif key == Qt.Key_D:
self.oneLineDown()
按下D键,可以加速下降。
def tryMove(self, newPiece, newX, newY):
for i in range(4):
x = newX + newPiece.x(i)
y = newY - newPiece.y(i)
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
return False
if self.shapeAt(x, y) != Tetrominoe.NoShape:
return False
self.curPiece = newPiece
self.curX = newX
self.curY = newY
self.update()
return True
使用tryMove()方法尝试移动方块。如果方块的边缘已经接触到面板边缘或者不能移动,我们返回False。否则我们当前块下降到一个新的位置。
def timerEvent(self, event):
if event.timerId() == self.timer.timerId():
if self.isWaitingAfterLine:
self.isWaitingAfterLine = False
self.newPiece()
else:
self.oneLineDown()
else:
super(Board, self).timerEvent(event)
计时器事件,当我们前一个方块降到底部后,创建一个新的方块。
def clearBoard(self):
for i in range(Board.BoardHeight * Board.BoardWidth):
self.board.append(Tetrominoe.NoShape)
clearBoard()方法通过设置Tetrominoe.NoShape清除面板
def removeFullLines(self):
numFullLines = 0
rowsToRemove = []
for i in range(Board.BoardHeight):
n = 0
for j in range(Board.BoardWidth):
if not self.shapeAt(j, i) == Tetrominoe.NoShape:
n = n + 1
if n == 10:
rowsToRemove.append(i)
rowsToRemove.reverse()
for m in rowsToRemove:
for k in range(m, Board.BoardHeight):
for l in range(Board.BoardWidth):
self.setShapeAt(l, k, self.shapeAt(l, k + 1))
numFullLines = numFullLines + len(rowsToRemove)
...
如果到达底部,会调用removeFullLines()方法。我们会检查所有完整的线条然后删除它们。然后移动所有行高于当前删除整行一行。请注意,我们反的顺序行被删除。否则,就会出错。
def newPiece(self):
self.curPiece = Shape()
self.curPiece.setRandomShape()
self.curX = Board.BoardWidth // 2 + 1
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
if not self.tryMove(self.curPiece, self.curX, self.curY):
self.curPiece.setShape(Tetrominoe.NoShape)
self.timer.stop()
self.isStarted = False
self.msg2Statusbar.emit("Game over")
通过newPiece()方法创建一个新的方块,如果不能进入它的初始位置,游戏就结束了。
class Tetrominoe(object):
NoShape = 0
ZShape = 1
SShape = 2
LineShape = 3
TShape = 4
SquareShape = 5
LShape = 6
MirroredLShape = 7
Tetrominoe类包含所有可能的形状。NoShape空形状。
Shape 类保存方块信息
class Shape(object):
coordsTable = (
((0, 0), (0, 0), (0, 0), (0, 0)),
((0, -1), (0, 0), (-1, 0), (-1, 1)),
...
)
...
coordsTable 元组包含所有可能的俄罗斯方块的坐标值。这是一个模板的所有块坐标值。
我们创建一个空的列表保存俄罗斯方块的坐标
上面的图像将有助于理解坐标值。例如,元组(0,1),(0,0)、(1,0)、(1,1)代表Z-shape。图表说明了形状
def rotateLeft(self):
if self.pieceShape == Tetrominoe.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, self.y(i))
result.setY(i, -self.x(i))
return result
rotateLeft() 向左旋转方块。如果方块本身不能被旋转,我们就返回当前对象的应用。否则就创建一个新的块及其坐标设置为的旋转。
标签:__,qp,教程,Python,self,PyQt5,init,addWidget,def From: https://blog.51cto.com/csnd/5956114