PyQt5教程 :http://code.py40.com/face
教程翻译自:http://zetcode.com/gui/pyqt5/
PyQt5 的 核心API 以及 扩展应用(CSDN 学院收费视频):https://edu.csdn.net/course/play/9870/222942
pyqt5 - 对文本样式进行操作:https://www.cnblogs.com/XJT2018/p/9835262.html
setStyleSheet 用法:https://www.cnblogs.com/aheng123/p/5630761.html
Pyqt5 —— setStyleSheet 用法:https://blog.csdn.net/weixin_42066185/article/details/82225197
颜色代码查询,RGB 查询:http://tool.chinaz.com/tools/selectcolor.aspx
RGB颜色值转换成十六进制颜色码:https://www.sioe.cn/yingyong/yanse-rgb-16/
【第一节】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 : XXXimport sys# QMainWindow 类提供了一个主要的应用程序窗口。
# 你用它可以让应用程序添加状态栏,工具栏和菜单栏。
from PyQt5.QtWidgets import QWidget, QMainWindowfrom 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()passdef 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 == '':continuebutton = 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()passclass 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, QPushButtonclass 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入门(五)— 布局:https://blog.csdn.net/qq_34710142/article/details/80875625
from PyQt5 import QtWidgetsclass 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 sysapp = 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, QMainWindowfrom 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)###################################passif __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 sysclass 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) # 设置窗口默认部件为主widgetif __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 QWidgetfrom 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_boxself.setLayout(v_box)passif __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 - 布局控件 :https://www.cnblogs.com/liming19680104/p/10356142.html
Python 图形开发:PyQt 三种界面布局解析:http://www.codebelief.com/article/2017/04/python-gui-development-pyqt-three-kinds-of-layout/
Qt 之水平/垂直布局(QBoxLayout、QHBoxLayout、QVBoxLayout):https://blog.csdn.net/liang19890820/article/details/51537246
PyQt5 布局大全:https://blog.csdn.net/jeekmary/article/details/80208601
PyQt5之布局管理:https://www.cnblogs.com/laizhenghong2012/p/10040798.html
窗体的四个角布局:http://www.bubuko.com/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, QHBoxLayoutclass 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) # 注意1layout.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 Qtclass 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 : XXXimport sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QWidget, QMainWindow, QLCDNumber, QSlider, QVBoxLayout, QApplication, QPushButton
)from PyQt5.QtCore import pyqtSignal, QObjectclass 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,否则就是falsefont, 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 QColorclass 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 = 255else:val = 0if 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 = 0self.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')returnself.step = self.step + 1self.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 QDragclass Button(QPushButton):def __init__(self, title, parent):super().__init__(title, parent)def mouseMoveEvent(self, e):if e.buttons() != Qt.RightButton:returnmimeData = 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 Qtclass 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 Qtclass 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, QBrushclass 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 Qtclass 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 Qtclass 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, QPenclass Communicate(QObject):updateBW = pyqtSignal(int)class BurningWidget(QWidget):def __init__(self):super().__init__()self.initUI()def initUI(self):self.setMinimumSize(1, 30)self.value = 75self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]def setValue(self, value):self.value = valuedef 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 = 0for 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 + 1class 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 tutorialThis 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, QColorclass 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 = 10BoardHeight = 22Speed = 300def __init__(self, parent):super().__init__(parent)self.initBoard()def initBoard(self):self.timer = QBasicTimer()self.isWaitingAfterLine = Falseself.curX = 0self.curY = 0self.numLinesRemoved = 0self.board = []self.setFocusPolicy(Qt.StrongFocus)self.isStarted = Falseself.isPaused = Falseself.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] = shapedef squareWidth(self):return self.contentsRect().width() // Board.BoardWidthdef squareHeight(self):return self.contentsRect().height() // Board.BoardHeightdef start(self):if self.isPaused:returnself.isStarted = Trueself.isWaitingAfterLine = Falseself.numLinesRemoved = 0self.clearBoard()self.msg2Statusbar.emit(str(self.numLinesRemoved))self.newPiece()self.timer.start(Board.Speed, self)def pause(self):if not self.isStarted:returnself.isPaused = not self.isPausedif 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)returnkey = event.key()if key == Qt.Key_P:self.pause()returnif self.isPaused:returnelif 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 = Falseself.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.curYwhile newY > 0:if not self.tryMove(self.curPiece, self.curX, newY - 1):breaknewY -= 1self.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 = 0rowsToRemove = []for i in range(Board.BoardHeight):n = 0for j in range(Board.BoardWidth):if not self.shapeAt(j, i) == Tetrominoe.NoShape:n = n + 1if 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 + numFullLinesself.msg2Statusbar.emit(str(self.numLinesRemoved))self.isWaitingAfterLine = Trueself.curPiece.setShape(Tetrominoe.NoShape)self.update()def newPiece(self):self.curPiece = Shape()self.curPiece.setRandomShape()self.curX = Board.BoardWidth // 2 + 1self.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 = Falseself.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 Falseif self.shapeAt(x, y) != Tetrominoe.NoShape:return Falseself.curPiece = newPieceself.curX = newXself.curY = newYself.update()return Truedef 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 = 0ZShape = 1SShape = 2LineShape = 3TShape = 4SquareShape = 5LShape = 6MirroredLShape = 7class 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.NoShapeself.setShape(Tetrominoe.NoShape)def shape(self):return self.pieceShapedef 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 = shapedef 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] = xdef setY(self, index, y):self.coords[index][1] = ydef minX(self):m = self.coords[0][0]for i in range(4):m = min(m, self.coords[i][0])return mdef maxX(self):m = self.coords[0][0]for i in range(4):m = max(m, self.coords[i][0])return mdef minY(self):m = self.coords[0][1]for i in range(4):m = min(m, self.coords[i][1])return mdef maxY(self):m = self.coords[0][1]for i in range(4):m = max(m, self.coords[i][1])return mdef rotateLeft(self):if self.pieceShape == Tetrominoe.SquareShape:return selfresult = Shape()result.pieceShape = self.pieceShapefor i in range(4):result.setX(i, self.y(i))result.setY(i, -self.x(i))return resultdef rotateRight(self):if self.pieceShape == Tetrominoe.SquareShape:return selfresult = Shape()result.pieceShape = self.pieceShapefor i in range(4):result.setX(i, -self.y(i))result.setY(i, self.x(i))return resultif __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 Falseif self.shapeAt(x, y) != Tetrominoe.NoShape:return Falseself.curPiece = newPieceself.curX = newXself.curY = newYself.update()return True
使用tryMove()方法尝试移动方块。如果方块的边缘已经接触到面板边缘或者不能移动,我们返回False。否则我们当前块下降到一个新的位置。
def timerEvent(self, event):if event.timerId() == self.timer.timerId():if self.isWaitingAfterLine:self.isWaitingAfterLine = Falseself.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 = 0rowsToRemove = []for i in range(Board.BoardHeight):n = 0for j in range(Board.BoardWidth):if not self.shapeAt(j, i) == Tetrominoe.NoShape:n = n + 1if 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 + 1self.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 = Falseself.msg2Statusbar.emit("Game over")
通过newPiece()方法创建一个新的方块,如果不能进入它的初始位置,游戏就结束了。
class Tetrominoe(object):NoShape = 0ZShape = 1SShape = 2LineShape = 3TShape = 4SquareShape = 5LShape = 6MirroredLShape = 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 selfresult = Shape()result.pieceShape = self.pieceShapefor i in range(4):result.setX(i, self.y(i))result.setY(i, -self.x(i))return result
rotateLeft() 向左旋转方块。如果方块本身不能被旋转,我们就返回当前对象的应用。否则就创建一个新的块及其坐标设置为的旋转。