python PyQt5学习笔记 事件和信号 有注释 p2

事件和信号

事件

所有的应用都是事件驱动的。事件大部分都是由用户的行为产生的,当然也有其他的事件产生方式,比如网络的连接,窗口管理器或者定时器等。调用应用的exec_()方法时,应用会进入主循环,主循环会监听和分发事件。

在事件模型中,有三个角色:

  • 事件源

  • 事件

  • 事件目标

事件源就是发生了状态改变的对象。事件是这个对象状态改变的内容。事件目标是事件想作用的目标。事件源绑定事件处理函数,然后作用于事件目标身上。

PyQt5处理事件方面有个signal and slot机制。Signals and slots用于对象间的通讯。事件触发的时候,发生一个signal,slot是用来被Python调用的

Signals & slots

信号与触发

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):

        lcd = QLCDNumber(self)
        sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(lcd)
        vbox.addWidget(sld)

        self.setLayout(vbox)
        sld.valueChanged.connect(lcd.display)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal and slot')
        self.show()
if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

python PyQt5学习笔记 事件和信号 有注释 p2_第1张图片

显示了QtGui.QLCDNumber和QtGui.QSlider模块,我们能拖动滑块让数字跟着发生改变。
sld.valueChanged.connect(lcd.display)
这里是把滑块的变化和数字的变化绑定在一起。

sender是信号的发送者,receiver是信号的接收者,slot是对这个信号应该做出的反应。

重构事件处理器

在PyQt5中,事件处理器经常被重写(也就是用自己的覆盖库自带的)。

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Event handler')
        self.show()
    def keyPressEvent(self, e):
        print(e.key(),chr(e.key()))
        if e.key() == Qt.Key_Escape:
            self.close()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

87 W
68 D
83 S
74 J
74 J
74 J

事件对象

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QLabel

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        # grid = QGridLayout()
        # grid.setSpacing(10)
        x = 0
        y = 0
        self.text = "x: {0},  y: {1}".format(x, y)

        self.label = QLabel(self.text, self) #self.label 全局
        # grid.addWidget(self.label, 0, 0, Qt.AlignTop)
        self.setMouseTracking(True)
        self.label.resize(200,60)#宽 高
        # self.setLayout(grid)

        self.setGeometry(300, 300, 350, 200)
        self.setWindowTitle('Event object')
        self.show()
    def mouseMoveEvent(self, e):
        x = e.x()
        y = e.y()
        text = "x: {0},  y: {1}".format(x, y)
        self.label.setText(text)
        # print(text)
        self.label.move(x,y)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

事件对象是用python来描述一系列的事件自身属性的对象。

这个示例中,我们在一个组件里显示鼠标的X和Y坐标。
self.text = "x: {0},  y: {1}".format(x, y)

self.label = QLabel(self.text, self)
X Y坐标显示在QLabel组件里
self.setMouseTracking(True)
事件追踪默认没有开启,当开启后才会追踪鼠标的点击事件。
def mouseMoveEvent(self, e):

    x = e.x()
    y = e.y()

    text = "x: {0},  y: {1}".format(x, y)
    self.label.setText(text)
e代表了事件对象。里面有我们触发事件(鼠标移动)的事件对象。x()和y()方法得到鼠标的x和y坐标点,然后拼成字符串输出到QLabel组件里。

python PyQt5学习笔记 事件和信号 有注释 p2_第2张图片

事件发送

import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication
class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        btn1 = QPushButton("Button 1", self)
        btn1.move(30, 50)

        btn2 = QPushButton("Button 2", self)
        btn2.move(150, 50)

        btn1.clicked.connect(self.buttonClicked)
        btn2.clicked.connect(self.buttonClicked2)

        self.statusBar()
        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('Event sender')
        self.show()
    def buttonClicked(self):
        sender = self.sender()
        print(sender.text())
        self.statusBar().showMessage(sender.text() + ' was pressed')
    def buttonClicked2(self):
        print(2)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

这个例子里有俩按钮,buttonClicked()方法决定了是哪个按钮能调用sender()方法。
btn1.clicked.connect(self.buttonClicked)            
btn2.clicked.connect(self.buttonClicked)
两个按钮都和同一个slot绑定。
def buttonClicked(self):

    sender = self.sender()
    self.statusBar().showMessage(sender.text() + ' was pressed')
我们用调用sender()方法的方式决定了事件源。状态栏显示了被点击的按钮。

 信号发送

import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QMainWindow, QApplication

class Communicate(QObject):
    closeApp = pyqtSignal()
class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):

        self.c = Communicate()
        #self.c.closeApp.connect(self.close)
        self.c.closeApp.connect(self.print6) #自定义函数
        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('Emit signal')
        self.show()
    def mousePressEvent(self, event):
        self.c.closeApp.emit()#点击时运行
    def print6(self):
        print(666)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

我们创建了一个叫closeApp的信号,这个信号会在鼠标按下的时候触发,事件与QMainWindow绑定。


class Communicate(QObject):

    closeApp = pyqtSignal()
Communicate类创建了一个pyqtSignal()属性的信号


self.c = Communicate()
self.c.closeApp.connect(self.close)
closeApp信号QMainWindow的close()方法绑定。


def mousePressEvent(self, event):

    self.c.closeApp.emit()
点击窗口的时候,发送closeApp信号,程序终止

python PyQt5学习笔记 事件和信号 有注释 p2_第3张图片

对话框

对话框用来输入数据,修改数据,修改应用设置等等

输入文字

QInputDialog提供了一个简单方便的对话框,可以输入字符串,数字或列表

from PyQt5.QtWidgets import (QWidget, QPushButton, QLineEdit,
    QInputDialog, QApplication)
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.btn = QPushButton('Dialog', self)
        self.btn.move(20, 20)
        self.btn.clicked.connect(self.showDialog)

        self.le = QLineEdit(self)
        self.le.move(130, 22)

        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('Input dialog')
        self.show()
    def showDialog(self):
        text, ok = QInputDialog.getText(self, 'Input Dialog',
            'Enter your name:')
        if ok:
            self.le.setText(str(text))
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

这个示例有一个按钮和一个输入框,点击按钮显示对话框,输入的文本会显示在输入框里。
text, ok = QInputDialog.getText(self, 'Input Dialog',
    'Enter your name:')

这是显示一个输入框的代码。第一个参数是输入框的标题,第二个参数是输入框的占位符。对话框返回输入内容和一个布尔值,如果点击的是OK按钮,布尔值就返回True。
if ok:
    self.le.setText(str(text))
把得到的字符串放到输入框里。

python PyQt5学习笔记 事件和信号 有注释 p2_第4张图片

 选取颜色

QColorDialog提供颜色的选择。

from PyQt5.QtWidgets import (QWidget, QPushButton, QFrame,
    QColorDialog, QApplication)
from PyQt5.QtGui import QColor
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):

        col = QColor(0, 0, 0)

        self.btn = QPushButton('Dialog', self)
        self.btn.move(20, 20)

        self.btn.clicked.connect(self.showDialog)

        self.frm = QFrame(self)
        self.frm.setStyleSheet("QWidget { background-color: %s }"
            % col.name())
        self.frm.setGeometry(130, 22, 100, 100)

        self.setGeometry(300, 300, 250, 180)
        self.setWindowTitle('Color dialog')
        self.show()
    def showDialog(self):

        col = QColorDialog.getColor()
        print("%s"% col.name())
        if col.isValid():
            # self.frm.setStyleSheet("%s"% col.name())#无效
            self.frm.setStyleSheet("QWidget { background-color: %s }"
                % col.name())
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

例子里有一个按钮和一个QFrame,默认的背景颜色为黑色,我们可以使用QColorDialog改变背景颜色。
col = QColor(0, 0, 0)
初始化QtGui.QFrame的背景颜色。

col = QColorDialog.getColor()
弹出一个QColorDialog对话框。

if col.isValid():
    self.frm.setStyleSheet("QWidget { background-color: %s }"
        % col.name()
)
我们可以预览颜色,如果点击取消按钮,没有颜色值返回,如果颜色是我们想要的,就从取色框里选择这个颜色。

python PyQt5学习笔记 事件和信号 有注释 p2_第5张图片

选择字体

QFontDialog能做字体的选择。

from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QPushButton,
    QSizePolicy, QLabel, QFontDialog, QApplication)
import sys

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        vbox = QVBoxLayout()
        btn = QPushButton('Dialog', self)
        btn.setSizePolicy(QSizePolicy.Fixed,
            QSizePolicy.Fixed)

        btn.move(20, 20)

        vbox.addWidget(btn)

        btn.clicked.connect(self.showDialog)

        self.lbl = QLabel('Knowledge only matters 查看字体', self)
        self.lbl.move(130, 20)

        vbox.addWidget(self.lbl)
        self.setLayout(vbox)

        self.setGeometry(300, 300, 250, 180)
        self.setWindowTitle('Font dialog')
        self.show()
    def showDialog(self):
        font, ok = QFontDialog.getFont()
        print(font)
        if ok:
            self.lbl.setFont(font)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

我们创建了一个有一个按钮和一个标签的QFontDialog的对话框,我们可以使用这个功能修改字体样式。
font, ok = QFontDialog.getFont()
弹出一个字体选择对话框。getFont()方法返回一个字体名称和状态信息。状态信息有OK和其他两种。
if ok:
    self.label.setFont(font)
如果点击OK,标签的字体就会随之更改。

python PyQt5学习笔记 事件和信号 有注释 p2_第6张图片

选择文件

QFileDialog给用户提供文件或者文件夹选择的功能。能打开和保存文件。

from PyQt5.QtWidgets import (QMainWindow, QTextEdit,
    QAction, QFileDialog, QApplication)
from PyQt5.QtGui import QIcon
import sys
class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.textEdit = QTextEdit()
        self.setCentralWidget(self.textEdit)
        self.statusBar()

        openFile = QAction('Open', self)
        openFile.setShortcut('Ctrl+O')
        openFile.setStatusTip('Open new File')
        openFile.triggered.connect(self.showDialog)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(openFile)

        self.setGeometry(300, 300, 350, 300)
        self.setWindowTitle('File dialog')
        self.show()
    def showDialog(self):
        fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')
        print(fname)
        if fname[0]:
            try:
                f = open(fname[0], 'r')
                with f:
                    data = f.read()
                    self.textEdit.setText(data)
            except:
                self.textEdit.setText('错误:'+fname[0])

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

本例中有一个菜单栏,一个置中的文本编辑框,一个状态栏。点击菜单栏选项会弹出一个QtGui.QFileDialog对话框,在这个对话框里,你能选择文件,然后文件的内容就会显示在文本编辑框里。

self.textEdit = QTextEdit()
self.setCentralWidget(self.textEdit)

这里设置了一个文本编辑框,文本编辑框是基于QMainWindow组件的
fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')
弹出QFileDialog窗口
getOpenFileName()方法的第一个参数是说明文字,第二个参数是默认打开的文件夹路径。默认情况下显示所有类型的文件
if fname[0]:
    f = open(fname[0], 'r')

    with f:
        data = f.read()
        self.textEdit.setText(data)
读取选中的文件,并显示在文本编辑框内(但是打开HTML文件时,是渲染后的结果,汗)。

python PyQt5学习笔记 事件和信号 有注释 p2_第7张图片

控件(1)

控件就像是应用这座房子的一块块砖。PyQt5有很多的控件,比如按钮,单选框,滑动条,复选框等等。在本章,我们将介绍一些很有用的控件:QCheckBoxToggleButtonQSliderQProgressBarQCalendarWidget。(复选框、切换按钮、滑块、进度条和日历)

QCheckBox

QCheckBox 组件有俩状态:开和关。通常跟标签一起使用,用在激活和关闭一些选项的场景。

from PyQt5.QtWidgets import QWidget, QCheckBox, QApplication
from PyQt5.QtCore import Qt
import sys

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        cb = QCheckBox('Show title', self)
        cb.move(20, 20)
        cb.toggle()
        cb.stateChanged.connect(self.changeTitle)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('QCheckBox')
        self.show()
    def changeTitle(self, state):
        print(state, Qt.Checked)
        if state == Qt.Checked:
            self.setWindowTitle('QCheckBox')
        else:
            self.setWindowTitle(' ')
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

这个例子中,有一个能切换窗口标题的单选框。
cb = QCheckBox('Show title', self)
这个是QCheckBox的构造器。

cb.toggle()#切换 
要设置窗口标题,我们就要检查单选框的状态。默认情况下,窗口没有标题,单选框未选中。
cb.stateChanged.connect(self.changeTitle)
changeTitle()方法和stateChanged信号关联起来。这样,changeTitle()就能切换窗口标题了。
def changeTitle(self, state):

    if state == Qt.Checked:
        self.setWindowTitle('QCheckBox')
    else:
        self.setWindowTitle('')
控件的状态是由changeTitle()方法控制的,如果空间被选中,我们就给窗口添加一个标题,如果没被选中,就清空标题。

python PyQt5学习笔记 事件和信号 有注释 p2_第8张图片

切换按钮

切换按钮就是QPushButton的一种特殊模式。 它只有两种状态:按下和未按下。我们在点击的时候切换两种状态,有很多场景会使用到这个功能。

from PyQt5.QtWidgets import (QWidget, QPushButton,
    QFrame, QApplication)
from PyQt5.QtGui import QColor
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.col = QColor(0, 0, 0)
        self.col.setRgb(102,111,0)#设置rpg颜色

        redb = QPushButton('Red', self)
        redb.setCheckable(True)
        redb.move(10, 10)

        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)

        jia=QPushButton('加1',self)
        jia.move(10,160)
        jia.clicked.connect(self.color_1)

        xianshi=QPushButton('显示',self)
        xianshi.move(160,160)
        xianshi.clicked.connect(self.color_xian)


        self.square = QFrame(self)
        self.square.setGeometry(150, 20, 100, 100)
        self.square.setStyleSheet("QWidget { background-color: %s }" %
            self.col.name())

        self.setGeometry(300, 400, 280, 200)#从屏幕上(300,400)位置开始(即为最左上角的点),显示一个280*200的界面(宽280,高200)
        self.setWindowTitle('Toggle button')
        self.show()
    def setColor(self, pressed):
        source = self.sender()
        if pressed:
            val = 255
        else: val = 0

        if source.text() == "Red":
            self.col.setRed(val)
        elif source.text() == "Green":
            self.col.setGreen(val)
        elif source.text() == "Blue":
            self.col.setBlue(val)
        self.square.setStyleSheet("QFrame { background-color: %s }" %
            self.col.name())
    def color_1(self):
        self.col.setRed(self.col.red()+1)
        self.col.setBlue(self.col.blue() + 1)
        self.col.setGreen(self.col.green() + 1)
        self.square.setStyleSheet("QFrame { background-color: %s }" %
            self.col.name())
    def color_xian(self):
        print(self.col.red(),self.col.green(),self.col.blue())
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

我们创建了一个切换按钮和一个QWidget,并把QWidget的背景设置为黑色。点击不同的切换按钮,背景色会在红、绿、蓝之间切换(而且能看到颜色合成的效果,而不是单纯的颜色覆盖)。


self.col = QColor(0, 0, 0)
设置颜色为黑色。

redb = QPushButton('Red', self)
redb.setCheckable(True)
redb.move(10, 10)
创建一个QPushButton,然后调用它的setCheckable()的方法就把这个按钮编程了切换按钮
redb.clicked[bool].connect(self.setColor)
把点击信号和我们定义好的函数关联起来,这里是把点击事件转换成布尔值


source = self.sender()
获取被点击的按钮。


if source.text() == "Red":
    self.col.setRed(val)
如果是标签为“red”的按钮被点击,就把颜色更改为预设好的对应颜色。


self.square.setStyleSheet("QFrame { background-color: %s }" %
    self.col.name())
使用样式表(就是CSS的SS)改变背景色

python PyQt5学习笔记 事件和信号 有注释 p2_第9张图片

滑块

QSlider是个有一个小滑块的组件,这个小滑块能拖着前后滑动,这个经常用于修改一些具有范围的数值,比文本框或者点击增加减少的文本框(spin box)方便多了。

本例用一个滑块和一个标签展示。标签为一个图片,滑块控制标签(的值)。

先准备四个分别表示静音、小音量、中音量、大音量的图标,文件名分别叫mute.png, min.png, med.png, max.png

from PyQt5.QtWidgets import (QWidget, QSlider,
    QLabel, QApplication)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        sld = QSlider(Qt.Horizontal, self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setGeometry(30, 40, 100, 30)
        sld.valueChanged[int].connect(self.changeValue)

        self.label = QLabel('声音',self)
        self.label.resize(200,20)
        self.label.move(80,100)

        # self.label.setPixmap(QPixmap('mute.png'))
        # self.label.setGeometry(160, 40, 80, 30)

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('QSlider')
        self.show()
    def changeValue(self, value):
        if value == 0:
            self.label.setText('mute:'+str(value))
            # self.label.setPixmap(QPixmap('mute.png'))
        elif value > 0 and value <= 30:
            self.label.setText('min:'+str(value))
            # self.label.setPixmap(QPixmap('min.png'))
        elif value > 30 and value < 80:
            self.label.setText('med:'+str(value))
            # self.label.setPixmap(QPixmap('med.png'))
        else:
            self.label.setText('max:'+str(value))
            # self.label.setPixmap(QPixmap('max.png'))
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

这里是模拟的音量控制器。拖动滑块,能改变标签位置的图片。
sld = QSlider(Qt.Horizontal, self)
创建一个水平的QSlider。


self.label = QLabel(self)
self.label.setPixmap(QPixmap('mute.png'))
创建一个QLabel组件并给它设置一个静音图标。


sld.valueChanged[int].connect(self.changeValue)
把valueChanged信号跟changeValue()方法关联起来。
if value == 0:
    self.label.setPixmap(QPixmap('mute.png'))
...
根据音量值的大小更换标签位置的图片。这段代码是:如果音量为0,就把图片换成 mute.png。

python PyQt5学习笔记 事件和信号 有注释 p2_第10张图片

进度条

进度条是用来展示任务进度的。它的滚动能让用户了解到任务的进度。QProgressBar组件提供了水平和垂直两种进度条,进度条可以设置最大值和最小值,默认情况是0~99。

from PyQt5.QtWidgets import (QWidget, QProgressBar,
    QPushButton, QApplication)
from PyQt5.QtCore import QBasicTimer
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(30, 40, 200, 25)

        self.btn = QPushButton('Start', self)
        self.btn.move(40, 80)
        self.btn.clicked.connect(self.doAction)

        self.timer = QBasicTimer()
        self.step = 0

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('QProgressBar')
        self.show()
    def timerEvent(self, e):
        if self.step >= 200:
            self.timer.stop()
            self.btn.setText('Finished')
            return
        self.step = self.step + 10
        print(self.step)
        self.pbar.setValue(self.step)
    def doAction(self):
        if self.timer.isActive():
            self.timer.stop()
            self.btn.setText('Start')
        else:
            self.timer.start(200, self)
            self.btn.setText('Stop')
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

我们创建了一个水平的进度条和一个按钮,这个按钮控制进度条的开始和停止。
self.pbar = QProgressBar(self)
新建一个QProgressBar进度条。

self.timer = QtCore.QBasicTimer()
用时间控制进度条。

self.timer.start(100, self)
调用start()方法加载一个时间事件。这个方法有两个参数:过期时间和事件接收者

def timerEvent(self, e):

    if self.step >= 100:

        self.timer.stop()
        self.btn.setText('Finished')
        return

    self.step = self.step + 1
    self.pbar.setValue(self.step)
每个QObject和又它继承而来的对象都有一个timerEvent()事件处理函数
。为了触发事件,我们重载了这个方法。
def doAction(self):

    if self.timer.isActive():
        self.timer.stop()
        self.btn.setText('Start')

    else:
        self.timer.start(100, self)
        self.btn.setText('Stop')
里面的doAction()方法是用来控制开始和停止的。

python PyQt5学习笔记 事件和信号 有注释 p2_第11张图片

 日历

QCalendarWidget提供了基于月份的日历插件,十分简易而且直观。

from PyQt5.QtWidgets import (QWidget, QCalendarWidget,
    QLabel, QApplication, QVBoxLayout)
from PyQt5.QtCore import QDate
import sys

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        vbox = QVBoxLayout(self)

        cal = QCalendarWidget(self)
        cal.setGridVisible(True)#设置网格可见
        cal.clicked[QDate].connect(self.showDate)

        vbox.addWidget(cal)

        self.lbl = QLabel(self)
        date = cal.selectedDate()
        self.lbl.setText(date.toString())

        vbox.addWidget(self.lbl)

        self.setLayout(vbox)

        self.setGeometry(300, 300, 350, 300)
        self.setWindowTitle('Calendar')
        self.show()

    def showDate(self, date):
        self.lbl.setText(date.toString())
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

这个例子有日期组件和标签组件组成,标签显示被选中的日期。
cal = QCalendarWidget(self)
创建一个QCalendarWidget。

cal.clicked[QDate].connect(self.showDate)
选择一个日期时,QDate的点击信号就触发了,把这个信号和我们自己定义的showDate()方法关联起来。
def showDate(self, date):     

    self.lbl.setText(date.toString())
使用selectedDate()方法获取选中的日期,然后把日期对象转成字符串,在标签里面显示出来

控件(2)

本章我们继续介绍PyQt5控件。这次的有QPixmapQLineEditQSplitter,和QComboBox

图片

QPixmap是处理图片的组件。本例中,我们使用QPixmap在窗口里显示一张图片。

from PyQt5.QtWidgets import (QWidget, QHBoxLayout,
    QLabel, QApplication)
from PyQt5.QtGui import QPixmap
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        hbox = QHBoxLayout(self)
        pixmap = QPixmap(r"C:\Users\user\Downloads\RE4wtd4.jpg")

        lbl = QLabel(self)
        lbl.setPixmap(pixmap)
        lbl.resize(300,200)

        hbox.addWidget(lbl)
        self.setLayout(hbox)

        self.setGeometry(100,100,300,200)
        self.setWindowTitle('Red Rock')
        self.show()
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

pixmap = QPixmap("redrock.png")
创建一个QPixmap对象,接收一个文件作为参数

lbl = QLabel(self)
lbl.setPixmap(pixmap)
把QPixmap实例放到QLabel组件里。

行编辑

QLineEdit组件提供了编辑文本的功能,自带了撤销、重做、剪切、粘贴、拖拽等功能。

import sys
from PyQt5.QtWidgets import (QWidget, QLabel,
    QLineEdit, QApplication)
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.lbl = QLabel(self)
        self.lbl.move(60, 40)
        qle = QLineEdit(self)
        qle.move(60, 100)

        qle.textChanged[str].connect(self.onChanged)

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('QLineEdit')
        self.show()
    def onChanged(self, text): #每二十个字符换行
        if len(text) >20:
            ce=''
            for i in range(len(text)):
                ce=ce+text[i]
                if i%20==0 and i>10:
                    ce = ce + '\n'
            self.lbl.setText(ce)
        else:
            self.lbl.setText(text)
        self.lbl.adjustSize()
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

 例子中展示了一个编辑组件和一个标签,我们在输入框里键入的文本,会立即在标签里显示出来。
qle = QLineEdit(self)
创建一个QLineEdit对象。


qle.textChanged[str].connect(self.onChanged)
如果输入框的值有变化,就调用onChanged()方法


def onChanged(self, text):

    self.lbl.setText(text)
    self.lbl.adjustSize()
在onChanged()方法内部,我们把文本框里的值赋值给了标签组件,然后调用adjustSize()方法让标签自适应文本内容。

python PyQt5学习笔记 事件和信号 有注释 p2_第12张图片

QSplitter

QSplitter组件能让用户通过拖拽分割线的方式改变子窗口大小的组件。本例中我们展示用两个分割线隔开的三个QFrame组件。

from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QFrame,
    QSplitter, QStyleFactory, QApplication)
from PyQt5.QtCore import Qt
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        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(300, 300, 300, 200)
        self.setWindowTitle('QSplitter')
        self.show()

    def onChanged(self, text):
        self.lbl.setText(text)
        self.lbl.adjustSize()
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

三个窗口和两个分割线的布局创建完成了,但是要注意,有些主题下,分割线的显示效果不太好。

topleft = QFrame(self)
topleft.setFrameShape(QFrame.StyledPanel)


为了更清楚的看到分割线,我们使用了设置好的子窗口样式。

splitter1 = QSplitter(Qt.Horizontal)
splitter1.addWidget(topleft)
splitter1.addWidget(topright)


创建一个QSplitter组件,并在里面添加了两个框架。

splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)


也可以在分割线里面再进行分割。

python PyQt5学习笔记 事件和信号 有注释 p2_第13张图片

下拉选框

QComboBox组件能让用户在多个选择项中选择一个。

from PyQt5.QtWidgets import (QWidget, QLabel,
    QComboBox, QApplication)
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.lbl = QLabel("Ubuntu", self)

        combo = QComboBox(self)
        combo.addItem("Ubuntu")
        combo.addItem("Mandriva")
        combo.addItem("Fedora")
        combo.addItem("Arch")
        for i in range(10):
            combo.addItem("Gentoo"+str(i))

        combo.move(50, 50)
        self.lbl.move(50, 150)

        self.lbl2 = QLabel("记录", self)
        self.lbl2.move(50, 100)

        combo.activated[str].connect(self.onActivated)

        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('QComboBox')
        self.show()
    def onActivated(self, text):
        self.lbl.setText(text)
        self.lbl.adjustSize()

        self.lbl2.setText(self.lbl2.text()+text)
        self.lbl2.adjustSize()#要设置长度,默认长度不显示后面的内容
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

本例包含了一个QComboBox和一个QLabel。下拉选择框有五个选项,都是Linux的发行版名称,标签内容为选定的发行版名称。

combo = QComboBox(self)
combo.addItem("Ubuntu")
combo.addItem("Mandriva")
combo.addItem("Fedora")
combo.addItem("Arch")
combo.addItem("Gentoo")


创建一个QComboBox组件和五个选项。

combo.activated[str].connect(self.onActivated)

在选中的条目上调用onActivated()方法。

def onActivated(self, text):

    self.lbl.setText(text)
    self.lbl.adjustSize()


在方法内部,设置标签内容为选定的字符串,然后设置自适应文本大小。

 python PyQt5学习笔记 事件和信号 有注释 p2_第14张图片

拖拽

在GUI里,拖放是指用户点击一个虚拟的对象,拖动,然后放置到另外一个对象上面的动作。一般情况下,需要调用很多动作和方法,创建很多变量。

拖放能让用户很直观的操作很复杂的逻辑。

一般情况下,我们可以拖放两种东西:数据和图形界面。把一个图像从一个应用拖放到另外一个应用上的实质是操作二进制数据。把一个表格从Firefox上拖放到另外一个位置 的实质是操作一个图形组。

简单的拖放

本例使用了QLineEditQPushButton。把一个文本从编辑框里拖到按钮上,更新按钮上的标签(文字)。

from PyQt5.QtWidgets import (QPushButton, QWidget,
                             QLineEdit, QApplication, QLabel)
import sys
class Button(QPushButton):
    def __init__(self, title, parent):
        super().__init__(title, parent)
        self.setAcceptDrops(True)
    def dragEnterEvent(self, e):
        if e.mimeData().hasFormat('text/plain'):
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        self.setText(e.mimeData().text())
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.edit = QLineEdit('拖放文本到这里', self)
        self.edit.setDragEnabled(True)
        self.edit.move(30, 65)

        self.edit.textChanged[str].connect(self.change_)

        self.lbl =QLabel('显示文本',self)
        self.lbl.move(20,20)

        button = Button("Button", self)
        button.move(190, 65)

        self.setWindowTitle('Simple drag and drop')
        self.setGeometry(300, 300, 300, 150)
    def change_(self):
        self.lbl.setText(self.edit.text())
        self.lbl.adjustSize()
        print(self.edit.text())
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    app.exec_()
class Button(QPushButton):

    def __init__(self, title, parent):
        super().__init__(title, parent)

        self.setAcceptDrops(True)


为了完成预定目标,我们要重构一些方法。首先用QPushButton上构造一个按钮实例。

self.setAcceptDrops(True)

激活组件的拖拽事件。

def dragEnterEvent(self, e):

    if e.mimeData().hasFormat('text/plain'):
        e.accept()
    else:
        e.ignore()


首先,我们重构了dragEnterEvent()方法。设定好接受拖拽的数据类型(plain text)。

def dropEvent(self, e):

    self.setText(e.mimeData().text())


然后重构dropEvent()方法,更改按钮接受鼠标的释放事件的默认行为。

edit = QLineEdit('', self)
edit.setDragEnabled(True)


QLineEdit默认支持拖拽操作,所以我们只要调用setDragEnabled()方法使用就行了。

python PyQt5学习笔记 事件和信号 有注释 p2_第15张图片

 python PyQt5学习笔记 事件和信号 有注释 p2_第16张图片

拖放按钮组件

class Button(QPushButton):

    def __init__(self, title, parent):
        super().__init__(title, parent)


从QPushButton继承一个Button类,然后重构QPushButton的两个方法: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-based数据。

dropAction = drag.exec_(Qt.MoveAction)


拖放事件开始时,用到的处理函数式start().

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()


指定放下的动作类型为moveAction。

python PyQt5学习笔记 事件和信号 有注释 p2_第17张图片

绘图

PyQt5绘图系统能渲染矢量图像、位图图像和轮廓字体文本。一般会使用在修改或者提高现有组件的功能,或者创建自己的组件。使用PyQt5的绘图API进行操作。

绘图由paintEvent()方法完成,绘图的代码要放在QPainter对象的begin()end()方法之间。是低级接口。

文本涂鸦

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
ZetCode PyQt5 tutorial 

In this example, we draw text in Russian Cylliric.

Author: Jan Bodnar
Website: zetcode.com 
Last edited: August 2017
"""

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5.QtCore import Qt

class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):      

        self.text = "Лев Николаевич Толстой\nАнна Каренина"

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('Drawing 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):
...
在绘画事件内完成绘画动作。
qp = QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
QPainter是低级的绘画类。所有的绘画动作都在这个类的begin()和end()方法之间完成,绘画动作都封装在drawText()内部了。
qp.setPen(QColor(168, 34, 3))
qp.setFont(QFont('Decorative', 10))
为文字绘画定义了笔和字体。
qp.drawText(event.rect(), Qt.AlignCenter, self.text)
drawText()方法在窗口里绘制文本,rect()方法返回要更新的矩形区域。

点的绘画

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
ZetCode PyQt5 tutorial 

In the example, we draw randomly 1000 red points 
on the window.

Author: Jan Bodnar
Website: zetcode.com 
Last edited: August 2017
"""

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt
import sys, random

class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):      

        self.setGeometry(300, 300, 300, 190)
        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个点。
qp.setPen(Qt.red)
设置笔的颜色为红色,使用的是预定义好的颜色。
size = self.size()
每次更改窗口大小,都会产生绘画事件,从size()方法里获得当前窗口的大小,然后把产生的点随机的分配到窗口的所有位置上。
qp.drawPoint(x, y)
drawPoint()方法绘图。

颜色

颜色是一个物体显示的RGB的混合色。RBG值的范围是0255。我们有很多方式去定义一个颜色,最常见的方式就是RGB和16进制表示法,也可以使用RGBA,增加了一个透明度的选项,透明度值的范围是01,0代表完全透明。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
ZetCode PyQt5 tutorial 

This example draws three rectangles in three
#different colours. 

Author: Jan Bodnar
Website: zetcode.com 
Last edited: August 2017
"""

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QBrush
import sys

class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):      

        self.setGeometry(300, 300, 350, 100)
        self.setWindowTitle('Colours')
        self.show()


    def paintEvent(self, e):

        qp = QPainter()
        qp.begin(self)
        self.drawRectangles(qp)
        qp.end()


    def drawRectangles(self, qp):

        col = QColor(0, 0, 0)
        col.setNamedColor('#d4d4d4')
        qp.setPen(col)

        qp.setBrush(QColor(200, 0, 0))
        qp.drawRect(10, 15, 90, 60)

        qp.setBrush(QColor(255, 80, 0, 160))
        qp.drawRect(130, 15, 90, 60)

        qp.setBrush(QColor(25, 0, 90, 200))
        qp.drawRect(250, 15, 90, 60)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

我们画出了三个颜色的矩形。
color = QColor(0, 0, 0)
color.setNamedColor('#d4d4d4')
使用16进制的方式定义一个颜色。
qp.setBrush(QColor(200, 0, 0))
qp.drawRect(10, 15, 90, 60)
定义了一个笔刷,并画出了一个矩形。笔刷是用来画一个物体的背景。drawRect()有四个参数,分别是矩形的x、y、w、h。 然后用笔刷和矩形进行绘画。

python PyQt5学习笔记 事件和信号 有注释 p2_第18张图片

 QPen

QPen是基本的绘画对象,能用来画直线、曲线、矩形框、椭圆、多边形和其他形状。

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setGeometry(300, 300, 280, 270)
        self.setWindowTitle('Pen styles')
        self.show()
    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawLines(qp)
        qp.end()

    def drawLines(self, qp):
        pen = QPen(Qt.black, 2, Qt.SolidLine)

        qp.setPen(pen)
        qp.drawLine(20, 40, 250, 40)

        pen.setStyle(Qt.DashLine)
        qp.setPen(pen)
        qp.drawLine(20, 80, 250, 80)

        pen.setStyle(Qt.DashDotLine)
        qp.setPen(pen)
        qp.drawLine(20, 120, 250, 120)

        pen.setStyle(Qt.DotLine)
        qp.setPen(pen)
        qp.drawLine(20, 160, 250, 160)

        pen.setStyle(Qt.DashDotDotLine)
        qp.setPen(pen)
        qp.drawLine(20, 200, 250, 200)

        pen.setStyle(Qt.CustomDashLine)
        pen.setDashPattern([1, 4, 5, 4])
        qp.setPen(pen)
        qp.drawLine(20, 240, 250, 240)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

在这个例子里,我们用不同的笔画了6条直线。PyQt5有五个预定义的笔,另外一个笔的样式使我们自定义的。

pen = QPen(Qt.black, 2, Qt.SolidLine)

新建一个QPen对象,设置颜色黑色,宽2像素,这样就能看出来各个笔样式的区别。Qt.SolidLine是预定义样式的一种。

pen.setStyle(Qt.CustomDashLine)
pen.setDashPattern([1, 4, 5, 4])
qp.setPen(pen)


这里我们自定义了一个笔的样式。定义为Qt.CustomDashLine然后调用setDashPattern()方法。数字列表是线的样式,要求必须是个数为奇数,奇数位定义的是空格,偶数位为线长,数字越大,空格或线长越大,比如本例的就是1像素线,4像素空格,5像素线,4像素空格。

python PyQt5学习笔记 事件和信号 有注释 p2_第19张图片

QBrush 

QBrush也是图像的一个基本元素。是用来填充一些物体的背景图用的,比如矩形,椭圆,多边形等。有三种类型:预定义、渐变和纹理。

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QBrush
from PyQt5.QtCore import Qt
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setGeometry(300, 300, 355, 280)
        self.setWindowTitle('Brushes')
        self.show()
    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawBrushes(qp)
        qp.end()
    def drawBrushes(self, qp):
        brush = QBrush(Qt.SolidPattern)
        qp.setBrush(brush)
        qp.drawRect(10, 15, 90, 60)

        brush.setStyle(Qt.Dense1Pattern)
        qp.setBrush(brush)
        qp.drawRect(130, 15, 90, 60)

        brush.setStyle(Qt.Dense2Pattern)
        qp.setBrush(brush)
        qp.drawRect(250, 15, 90, 60)

        brush.setStyle(Qt.DiagCrossPattern)
        qp.setBrush(brush)
        qp.drawRect(10, 105, 90, 60)

        brush.setStyle(Qt.Dense5Pattern)
        qp.setBrush(brush)
        qp.drawRect(130, 105, 90, 60)

        brush.setStyle(Qt.Dense6Pattern)
        qp.setBrush(brush)
        qp.drawRect(250, 105, 90, 60)

        brush.setStyle(Qt.HorPattern)
        qp.setBrush(brush)
        qp.drawRect(10, 195, 90, 60)

        brush.setStyle(Qt.VerPattern)
        qp.setBrush(brush)
        qp.drawRect(130, 195, 90, 60)

        brush.setStyle(Qt.BDiagPattern)
        qp.setBrush(brush)
        qp.drawRect(250, 195, 90, 60)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

我们画了9个不同的矩形。

brush = QBrush(Qt.SolidPattern)
qp.setBrush(brush)
qp.drawRect(10, 15, 90, 60)


创建了一个笔刷对象,添加笔刷样式,然后调用drawRect()方法画图。

python PyQt5学习笔记 事件和信号 有注释 p2_第20张图片

贝塞尔曲线

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPainterPath
from PyQt5.QtCore import Qt
import sys
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):

        self.setGeometry(300, 300, 380, 250)
        self.setWindowTitle('Bézier curve')
        self.show()

    def paintEvent(self, e):

        qp = QPainter()
        qp.begin(self)
        qp.setRenderHint(QPainter.Antialiasing)
        self.drawBezierCurve(qp)
        qp.end()


    def drawBezierCurve(self, qp):

        path = QPainterPath()
        path.moveTo(30, 30)
        path.cubicTo(30, 30, 200, 350, 350, 30)

        qp.drawPath(path)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

这个示例中,我们画出了一个贝塞尔曲线。
path = QPainterPath()
path.moveTo(30, 30)
path.cubicTo(30, 30, 200, 350, 350, 30)
用QPainterPath路径创建贝塞尔曲线。使用cubicTo()方法生成,分别需要三个点:起始点,控制点和终止点。
qp.drawPath(path)
drawPath()绘制最后的图像。

python PyQt5学习笔记 事件和信号 有注释 p2_第21张图片

自定义组件

PyQt5有丰富的组件,但是肯定满足不了所有开发者的所有需求,PyQt5只提供了基本的组件,像按钮,文本,滑块等。如果你还需要其他的模块,应该尝试自己去自定义一些。

自定义组件使用绘画工具创建,有两个基本方式:根据已有的创建或改进;通过自己绘图创建。

Burning widget

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
ZetCode PyQt5 tutorial 

In this example, we create a custom widget.

Author: Jan Bodnar
Website: zetcode.com 
Last edited: August 2017
"""

from PyQt5.QtWidgets import (QWidget, QSlider, QApplication, 
    QHBoxLayout, QVBoxLayout)
from PyQt5.QtCore import QObject, Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QColor, QPen
import sys

class Communicate(QObject):

    updateBW = pyqtSignal(int)


class BurningWidget(QWidget):

    def __init__(self):      
        super().__init__()

        self.initUI()


    def initUI(self):

        self.setMinimumSize(1, 30)
        self.value = 75
        self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]


    def setValue(self, value):

        self.value = value


    def paintEvent(self, e):

        qp = QPainter()
        qp.begin(self)
        self.drawWidget(qp)
        qp.end()


    def drawWidget(self, qp):

        MAX_CAPACITY = 700
        OVER_CAPACITY = 750

        font = QFont('Serif', 7, QFont.Light)
        qp.setFont(font)

        size = self.size()
        w = size.width()
        h = size.height()

        step = int(round(w / 10))


        till = int(((w / OVER_CAPACITY) * self.value))
        full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))

        if self.value >= MAX_CAPACITY:

            qp.setPen(QColor(255, 255, 255))
            qp.setBrush(QColor(255, 255, 184))
            qp.drawRect(0, 0, full, h)
            qp.setPen(QColor(255, 175, 175))
            qp.setBrush(QColor(255, 175, 175))
            qp.drawRect(full, 0, till-full, h)

        else:

            qp.setPen(QColor(255, 255, 255))
            qp.setBrush(QColor(255, 255, 184))
            qp.drawRect(0, 0, till, h)


        pen = QPen(QColor(20, 20, 20), 1, 
            Qt.SolidLine)

        qp.setPen(pen)
        qp.setBrush(Qt.NoBrush)
        qp.drawRect(0, 0, w-1, h-1)

        j = 0

        for i in range(step, 10*step, step):

            qp.drawLine(i, 0, i, 5)
            metrics = qp.fontMetrics()
            fw = metrics.width(str(self.num[j]))
            qp.drawText(i-fw/2, h/2, str(self.num[j]))
            j = j + 1


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):      

        OVER_CAPACITY = 750

        sld = QSlider(Qt.Horizontal, self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setRange(1, OVER_CAPACITY)
        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_())

本例中,我们使用了QSlider和一个自定义组件,由进度条控制。显示的有物体(也就是CD/DVD)的总容量和剩余容量。进度条的范围是1~750。如果值达到了700(OVER_CAPACITY),就显示为红色,代表了烧毁了的意思。
烧录组件在窗口的底部,这个组件是用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))
动态的渲染组件,随着窗口的大小而变化,这就是我们计算窗口大小的原因。最后一个参数决定了组件的最大范围,进度条的值是由窗口大小按比例计算出来的。最大值的地方填充的是红色。注意这里使用的是浮点数,能提高计算和渲染的精度。
绘画由三部分组成,黄色或红色区域和黄色矩形,然后是分割线,最后是添上代表容量的数字。
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组件,然后重新渲染Burning组件。

俄罗斯方块游戏

你可能感兴趣的:(python,qt,学习,开发语言)