Python PyQt5 教程

 

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++ 示例 步骤)

  1. 第一步:创建一个QWidget实例,并将这个实例设置为 centralWidget:
        QWidget *widget = new QWidget();
        this->setCentralWidget(widget);
  2. 第二部:创建一个主布局mainLayout,并把所需要的所有控件都往里面放(工具栏、菜单栏、状态栏除外):
        QHBoxLayout *mainLayout = new QHBoxLayout;
        mainLayout->addWidget(...);
        mainLayout->addLayout(...);
        ...
  3. 第三步:将 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)。
  • 表单布局:控件以两列的形式布局在窗口中,左边为标签,右边为输入控件。

 

使用布局管理器

 

  1. 绝对布局 这个就不详细说明了,使用 QWidget 的 move、setGeometry 等方法,直接设置其在窗口中的位置。
  2. 盒子布局(QHBoxLayout 水平布局、QVBoxLayout 垂直布局) 方法:
    • stretch(伸缩量),只适用于QBoxLayout布局方式,控件和窗口会随着伸缩量的变大而增加
    • alignment,指定对齐方式
    • addLayout(self, QLayout, stretch=0) 在窗口的右边添加布局,使用stretch(伸缩量)进行伸缩,默认为0
    • addWidget(self, QWidget, stretch, Qt.Alignment) 在布局中添加控件。
  3. 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)处添加伸缩量
  4. 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按钮2stretch。此时 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按钮2stretch,表示左右两边充满,控件占据中间位置

 

注意:当一个布局中出现多个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和正方形。这些形状的形成有四个方格。形状是跌倒。俄罗斯方块游戏的对象是移动和旋转的形状使他们适合尽可能多。如果我们设法形成一个行,该行摧毁我们得分。我们直到我们玩俄罗斯方块游戏。

tetrominoes

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 元组包含所有可能的俄罗斯方块的坐标值。这是一个模板的所有块坐标值。

我们创建一个空的列表保存俄罗斯方块的坐标

coordinates

上面的图像将有助于理解坐标值。例如,元组(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() 向左旋转方块。如果方块本身不能被旋转,我们就返回当前对象的应用。否则就创建一个新的块及其坐标设置为的旋转。

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/495805.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Silverlight中全屏处理

Silverlight插件支持全屏模式&#xff0c;这个没什么好说的&#xff0c;只需要用设置IsFullScreen属性即可&#xff0c;问题在于全屏模式中&#xff0c;尽管屏幕变大了&#xff0c;但是页面中的控件并未相应的变大&#xff0c;下面是我在网上找到的解决这个问题的两种方式。 第…

人工智能的过去、现在和未来

来源&#xff1a; 人工智能和大数据 作者&#xff1a; 腾讯 AI Lab 主任 张潼在回答人工智能达到了什么程度这个问题之前&#xff0c;需先了解人工智能的概念是什么&#xff1f;人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;是指计算机像人一样拥有智…

Dom4j完整教程~DOM4J简介

DOM4J是 dom4j.org 出品的一个开源 XML 解析包。DOM4J应用于 Java 平台&#xff0c;采用了 Java 集合框架并完全支持 DOM&#xff0c;SAX 和 JAXP。 DOM4J 使用起来非常简单。只要你了解基本的 XML-DOM 模型&#xff0c;就能使用。 Dom&#xff1a;把整个文档作为一个对象。 DO…

除了芯片 我们还应关注哪些核心技术

来源&#xff1a;传感器技术&#xff08;公众号&#xff09;最近的“中兴封杀”事件&#xff0c;让国人深深领略了缺少核心技术给企业乃至行业和国家带来的巨痛。除了我们知道的芯片技术之外&#xff0c;还有哪些核心技术需要我们重点关注呢&#xff1f;超级计算终端 超级计算终…

Python PyInstaller 安装 和 使用教程( 打包生成 exe )

From&#xff1a;http://c.biancheng.net/view/2690.html Python zipapp打包教程&#xff1a;http://c.biancheng.net/view/2687.html 在创建了独立应用&#xff08;自包含该应用的依赖包&#xff09;之后&#xff0c;还可以使用 PyInstaller 将 Python 程序生成可直接运行的程…

Yann LeCun:距离“真正的” AI,我们还缺什么?

来源&#xff1a; AI科技大本营&#xff08;ID&#xff1b;rgznai100&#xff09;GMIC Beijing 2018 大会第一天&#xff0c;首个演讲者是 Facebook 首席 AI 科学家 Yann LeCun。他讲述了关于深度学习的最新研究成果&#xff0c;同时也描述了深度学习的未来&#xff0c;以及机…

Python 使用 smtp ssl 模式 发送邮件与附件

参考 &#xff1a; 发送邮件简单入门 ( 以qq邮箱,163邮箱为例 ) &#xff1a;https://blog.csdn.net/qq_38661599/article/details/81013834 smtp ssl 模式 发送邮件 与 附件&#xff1a;https://www.cnblogs.com/SunshineKimi/p/10629342.html Python…

Unity.Interception System (2.0)

转载于:https://www.cnblogs.com/artech/archive/2010/08/27/1810010.html

卷积神经网络为什么能称霸计算机视觉领域?

来源&#xff1a;图灵人工智能摘要&#xff1a;在机器视觉和其他很多问题上&#xff0c;卷积神经网络取得了当前最好的效果&#xff0c;它的成功促使我们思考一个问题&#xff0c;卷积神经网络为什么会这么有效&#xff1f;在本文中&#xff0c;将为大家分析卷积神经网络背后的…

Python3.2+ 的 concurrent.futures 模块

concurrent.futures 官方文档&#xff1a;https://docs.python.org/3/library/concurrent.futures.html concurrent.futures: 线程池, 并发的处理任务&#xff1a;https://www.h3399.cn/201906/703751.html IO 密集型 vs 计算密集型&#xff1a; IO密集型&#xff1a;读取文件…

C# 判断一字符串是否为合法数字(正则表达式)

代码 ///<summary>///判断一个字符串是否为合法整数(不限制长度) ///</summary>///<param name"s">字符串</param>///<returns></returns>publicstaticboolIsInteger(strings) { stringpattern "…

关于现在人工智能预测的一些冷水

来源&#xff1a;人工智能和大数据 作者&#xff1a;田渊栋最近有关人工智能和深度学习的各种预测满天飞&#xff0c;作为圈内人&#xff0c;我来泼点冷水。我们常说科技树科技树&#xff0c;这个比喻是非常贴切的&#xff0c;现代科技的基础理论并不复杂&#xff0c;做个摘要…

Windows10 下搭建汇编语言开发环境( 利用 DOSBOX 和 MASM32 )

Windows10下搭建汇编语言开发环境&#xff08;利用DOSBOX和MASM32&#xff09;&#xff1a;https://www.cnblogs.com/zyever/p/7919007.html 如何在win10 64位下搭载汇编环境&#xff08;包含汇编dosbox和masm文件&#xff09;&#xff1a;https://blog.csdn.net/xyisv/articl…

一文解读“边缘计算” 和物联网的亲密关系!

来源&#xff1a;IT技术之家摘要&#xff1a;物联网的最终目标是万物互联&#xff0c;而边缘计算是解决当前云计算所面对的数据传输问题所应用的主要方式&#xff0c;相对于云计算&#xff0c;有更大的优势。物联网的最终目标是万物互联&#xff0c;而当前对于宽带水平来说&…

寄存器 和 存储器

From&#xff1a;https://blog.csdn.net/menshu1892/article/details/79912090 一、计算机硬件 先说内核&#xff1a;内核是操作系统最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件&#xff0c;这种访问是有限的&#xff0c;并且内核决定一个程序在什…

博文视点大讲堂36期——让Oracle跑得更快 成功举办

博文视点大讲堂36期——让Oracle跑得更快 成功举办 电子工业出版社博文视点公司于2010年9月日在西单图书大厦成功举办“博文视点大讲堂36期——让Oracle跑得更快”讲座。8月22日&#xff0c;博文视点大讲堂为配合《让Oracle跑得更快—Oracle 10g性能分析与优化思路》的全国热销…

NLP 解决方案是如何被深度学习改写的?

作者&#xff1a;杨晓凡摘要&#xff1a;英特尔人工智能产品事业部&#xff0c;数据科学主任 Yinyin Liu 近日撰写了一篇文章&#xff0c;介绍了深度学习为自然语言处理带来的种种变化。有趣的大趋势是首先产生在 CV 领域的技术也不断用于 NLP&#xff0c;而深度学习解决方案的…

CompletableFuture详解~思维导图

#原图 System.out.println("https://www.processon.com/view/621a1b361e08533fc3afaa44?fromnew1");

如何在SharePoint 2010项目中引用UserProfiles.dll

如果需要进行SharePoint的UserProfile开发的话&#xff0c;我们需要引用以下程序集&#xff1a; C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.Office.Server.UserProfiles.dll在添加引用的时候&#xff0c;Visual Studio 2010会…

汇编中各寄存器的作用(16位CPU14个,32位CPU16个)和 x86汇编指令集大全(带注释)

From&#xff1a;https://www.cnblogs.com/zimmerk/articles/2520011.html From&#xff1a;https://blog.csdn.net/bjbz_cxy/article/details/79467688 汇编寄存器功能详解&#xff1a;https://wenku.baidu.com/view/14ef15857cd184254a353586.html 寄存器、汇编命令详解&am…