为了方便开发,在 GitHub 上放置 xinetzone / xinet 用于 GUI 开发。该仓库的 xinet 文件夹下放置了一些 Qt for Python 的 API。由于运行 GUI 的主循环是类似的,故而,可以写一个统一接口(xinet/run_qt.py):
from Qt.qt5 import QtWidgets
def run(window_type, *args, **kwargs):
import sys
app = QtWidgets.QApplication(sys.argv)
window = window_type(*args, **kwargs)
window.show()
app.exec_()
1 QPainter
QPaintEvent 事件包含一个需要更新的 region() 和一个 rect(),均返回该矩形区域的边界矩形实例。因为许多小部件不能充分利用 region(),而 rect() 可以比 region().boundingRect() 快得多,所以推荐使用 rect()。
在绘画事件的处理过程中,painting 被剪切到 region()。这种裁剪是由 Qt 的绘画系统执行的,并且与应用于绘画设备上的 QPainter 的任何 clipping 无关。

1.1 文本涂鸦
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets
from xinet.run_qt import run
class Drawing(QtWidgets.QWidget):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.setWindowTitle("在窗体中绘画出文字例子")
self.resize(300, 200)
self.text = '欢迎学习 PyQt5'
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.begin(self)
# 自定义的绘画方法
## 设置笔的颜色
painter.setPen(QtGui.QColor('purple'))
## 设置字体
painter.setFont(QtGui.QFont('SimSun', 20))
rect = event.rect() # event.region().boundingRect()
print(rect.getRect() == (0, 0, 300, 200))
# 画出在 widget 中央画出文本
painter.drawText(rect, QtCore.Qt.AlignCenter, self.text)
painter.end()
if __name__ == "__main__":
run(Drawing)
效果:

也可以这样:
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets # , Signal, Slot
from xinet.run_qt import run
class DrawText(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
self.pen = QtGui.QPen()
def initUI(self):
self.setGeometry(500, 500, 280, 270)
def paintEvent(self, e):
self.painter.begin(self)
self.draw_something()
self.painter.end()
def draw_something(self):
from random import randint
self.pen.setWidth(1)
self.pen.setColor(QtGui.QColor('green'))
self.painter.setPen(self.pen)
font = QtGui.QFont()
font.setFamily('Times')
font.setBold(True)
font.setPointSize(30)
self.painter.setFont(font)
self.painter.drawText(50, 100, 'Hello, world!')
if __name__ == "__main__":
run(DrawText)
效果:

1.2 点的绘画
import math
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets
from xinet.run_qt import run
class DrawingPoint(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(300, 200)
self.setWindowTitle("在窗体中画点")
def paintEvent(self, event):
qp = QtGui.QPainter()
qp.begin(self)
# 自定义画点方法
self.draw_points(qp)
qp.end()
def draw_points(self, qp):
qp.setPen(QtCore.Qt.red)
size = self.size() # 得到窗口的当前大小
for i in range(1000):
# [-100, 100] 两个周期的正弦函数图像
x = 100 * (-1+2.0*i/1000) + size.width()/2.0
y = -50 * math.sin((x - size.width()/2.0) *
math.pi/50) + size.height()/2.0
qp.drawPoint(x, y)
if __name__ == "__main__":
run(DrawingPoint)
效果:

1.3 在 QLabel 上画图
前面都是在 QWidget 上绘制图形的,也可以在 QLabel 上绘制:
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets
from xinet.run_qt import run
class Label(QtWidgets.QLabel):
def __init__(self):
super().__init__()
canvas = QtGui.QPixmap(400, 300)
self.setPixmap(canvas)
self.draw_something()
def draw_something(self):
painter = QtGui.QPainter(self.pixmap())
painter.setPen(QtCore.Qt.red)
painter.drawLine(10, 10, 300, 200)
painter.end()
if __name__ == "__main__":
run(Label)
效果:


| Method | Description |
|---|---|
| drawLine(line) | Draw a QLine instance |
| drawLine(line) | Draw a QLineF instance |
| drawLine(x1, y1, x2, y2) | Draw a line between x1, y2 and x2, y2 (int) |
| drawLine(p1, p2) | Draw a line between p1 and p2 (both QPoint) |
| drawLine(p1, p2) | Draw a line between p1 and p2 (both QPointF) |
2 QPen
QPen(钢笔)是一个基本的图形对象,用于绘制直线、曲线或者给轮廓画出矩形、椭圆形、多边形及其他形状等。
2.1 画不同样式的线段
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets
from xinet.run_qt import run
class DrawLine(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
# 为了能更清晰地看清各线之间的差异,将颜色设置成黑色,宽度设置为2像素(px)
## QtCore.Qt.SolidLine是预定义的线条样式之一
self.pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)
def initUI(self):
self.setGeometry(500, 500, 280, 270)
self.setWindowTitle('钢笔样式例子')
def paintEvent(self, e):
self.painter.begin(self)
self.draw()
self.painter.end()
def change_style(self, style):
self.pen.setStyle(style)
self.painter.setPen(self.pen)
def draw(self):
self.painter.setPen(self.pen)
self.painter.drawLine(20, 40, 250, 40)
self.change_style(QtCore.Qt.DashLine)
self.painter.drawLine(20, 80, 250, 80)
self.change_style(QtCore.Qt.DashDotLine)
self.painter.drawLine(20, 120, 250, 120)
self.change_style(QtCore.Qt.DotLine)
self.painter.drawLine(20, 160, 250, 160)
self.change_style(QtCore.Qt.DashDotDotLine)
self.painter.drawLine(20, 200, 250, 200)
self.pen.setStyle(QtCore.Qt.CustomDashLine) # 创建线条样式
self.pen.setDashPattern([1, 4, 5, 4])
self.painter.setPen(self.pen)
self.painter.drawLine(20, 240, 250, 240)
if __name__ == "__main__":
run(DrawLine)
效果:

2.2 画不同大小的点
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets
from xinet.run_qt import run
class DrawPoints(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
# 将颜色设置成红色
self.pen = QtGui.QPen(QtGui.QColor('red'))
def initUI(self):
self.setGeometry(500, 500, 280, 270)
self.setWindowTitle('钢笔样式例子')
def paintEvent(self, e):
self.painter.begin(self)
self.draw()
self.painter.end()
def draw_point(self, x, y):
self.painter.setPen(self.pen)
self.painter.drawPoint(x, y)
def draw(self):
self.pen.setWidth(5)
self.draw_point(40, 40)
self.pen.setWidth(10)
self.draw_point(40, 80)
self.pen.setWidth(20)
self.draw_point(40, 120)
self.pen.setWidth(30)
self.draw_point(40, 160)
self.pen.setWidth(40)
self.draw_point(40, 200)
if __name__ == "__main__":
run(DrawPoints)
效果:

2.3 画一条带宽度的黄色线段
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets # , Signal, Slot
from xinet.run_qt import run
class DrawYellowLine(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
def initUI(self):
self.setGeometry(500, 500, 280, 270)
def paintEvent(self, e):
self.painter.begin(self)
self.draw()
self.painter.end()
def draw(self):
pen = QtGui.QPen(QtGui.QColor('yellow'), 20)
self.painter.setPen(pen)
self.painter.drawLine(QtCore.QPoint(20, 20), QtCore.QPoint(200, 200))
if __name__ == "__main__":
run(DrawYellowLine)
效果:

2.4 画矩形
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets # , Signal, Slot
from xinet.run_qt import run
class DrawRect(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
self.pen = QtGui.QPen()
def initUI(self):
self.setGeometry(500, 500, 280, 270)
def paintEvent(self, e):
self.painter.begin(self)
self.draw_something()
self.painter.end()
def draw_something(self):
self.pen.setWidth(3)
self.pen.setColor(QtGui.QColor("#FB51E0"))
self.painter.setPen(self.pen)
self.painter.drawRect(50, 50, 100, 100)
self.painter.drawRect(60, 60, 150, 100)
self.painter.drawRect(70, 70, 100, 150)
self.painter.drawRect(80, 80, 150, 100)
self.painter.drawRect(90, 90, 100, 150)
if __name__ == "__main__":
run(DrawRect)
效果:

其实,上面的画矩形的代码可简写为:
def draw_something(self):
self.pen.setWidth(3)
self.pen.setColor(QtGui.QColor("#FB51E0"))
self.painter.setPen(self.pen)
self.painter.drawRects([QtCore.QRect(50, 50, 100, 100),
QtCore.QRect(60, 60, 150, 100),
QtCore.QRect(70, 70, 100, 150),
QtCore.QRect(80, 80, 150, 100),
QtCore.QRect(90, 90, 100, 150)])
2.5 画圆角矩形
修改 DrawRect 代码为:
def draw_something(self):
col = QtGui.QColor(0, 0, 0)
col.setNamedColor('green')
self.painter.setPen(col)
self.painter.drawRoundedRect(40, 40, 100, 100, 10, 10)
self.painter.drawRoundedRect(80, 80, 100, 100, 10, 50)
self.painter.drawRoundedRect(120, 120, 100, 100, 50, 10)
self.painter.drawRoundedRect(160, 160, 100, 100, 50, 50)
效果:

2.6 画椭圆
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets # , Signal, Slot
from xinet.run_qt import run
class DrawEllipse(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
self.pen = QtGui.QPen()
def initUI(self):
self.setGeometry(500, 500, 280, 270)
def paintEvent(self, e):
self.painter.begin(self)
self.draw_something()
self.painter.end()
def draw_something(self):
from random import randint
self.pen.setWidth(3)
self.pen.setColor(QtGui.QColor(204, 0, 0)) # r, g, b
self.painter.setPen(self.pen)
self.painter.drawEllipse(10, 10, 100, 100)
self.painter.drawEllipse(10, 10, 150, 200)
self.painter.drawEllipse(10, 10, 200, 300)
if __name__ == "__main__":
run(DrawEllipse)
效果:

也可以使用如下方式进行绘制:
painter.drawEllipse(QPoint(100, 100), 10, 10)
painter.drawEllipse(QPoint(100, 100), 15, 20)
painter.drawEllipse(QPoint(100, 100), 20, 30)
painter.drawEllipse(QPoint(100, 100), 25, 40)
painter.drawEllipse(QPoint(100, 100), 30, 50)
painter.drawEllipse(QPoint(100, 100), 35, 60)
效果为:

3 QBrush
QBrush 也是图像的一个基本元素。是用来填充一些物体的背景图用的,比如矩形,椭圆,多边形等。有三种类型:预定义、渐变和纹理。
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets # , Signal, Slot
from xinet.run_qt import run
class Drawing(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
self.pen = QtGui.QPen()
def initUI(self):
self.setGeometry(500, 500, 380, 270)
def paintEvent(self, e):
self.painter.begin(self)
self.draw_something()
self.painter.end()
def draw_rect(self, brush, bbox):
self.painter.setBrush(brush)
self.painter.drawRect(*bbox)
def draw_something(self):
brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
bbox = 10, 15, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.Dense1Pattern)
bbox = 130, 15, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.Dense2Pattern)
bbox = 250, 15, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.Dense3Pattern)
bbox = 10, 105, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.DiagCrossPattern)
bbox = 10, 15, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.Dense5Pattern)
bbox = 130, 105, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.Dense6Pattern)
bbox = 250, 105, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.HorPattern)
bbox = 10, 195, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.VerPattern)
bbox = 130, 195, 90, 60
self.draw_rect(brush, bbox)
brush = QtGui.QBrush(QtCore.Qt.BDiagPattern)
bbox = 250, 195, 90, 60
self.draw_rect(brush, bbox)
if __name__ == "__main__":
run(Drawing)
效果:

4 颜色
颜色是一个物体显示的 RGB 的混合色。RBG 值的范围是 0~255。我们有很多方式去定义一个颜色,最常见的方式就是 RGB 和 16 进制表示法,也可以使用 RGBA,增加了一个透明度的选项,透明度值的范围是0~255,0 代表完全透明。
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets # , Signal, Slot
from xinet.run_qt import run
class Drawing(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
self.pen = QtGui.QPen()
def initUI(self):
self.setGeometry(500, 500, 380, 100)
def paintEvent(self, e):
self.painter.begin(self)
self.draw_something()
self.painter.end()
def draw_something(self):
col = QtGui.QColor(0, 0, 0)
col.setNamedColor('#d4d4d4')
self.painter.setPen(col)
self.painter.setBrush(QtGui.QColor(255, 0, 0, 110))
self.painter.drawRect(10, 15, 90, 60)
self.painter.setBrush(QtGui.QColor(255, 80, 0))
self.painter.drawRect(130, 15, 90, 60)
self.painter.setBrush(QtGui.QColor(25, 0, 90, 200))
self.painter.drawRect(250, 15, 90, 60)
if __name__ == "__main__":
run(Drawing)
效果:

5 贝塞尔曲线
贝塞尔曲线是立方线。PyQt5 中的贝塞尔曲线可以使用 QPainterPath 创建。绘制器路径是由许多图形构建基块(如矩形、椭圆、线条和曲线)组成的对象。
from xinet.Qt.qt5 import QtCore, QtGui, QtWidgets # , Signal, Slot
from xinet.run_qt import run
class DrawBezierCurve(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.painter = QtGui.QPainter()
self.pen = QtGui.QPen()
def initUI(self):
self.setGeometry(500, 500, 380, 280)
def paintEvent(self, e):
self.painter.begin(self)
# 反锯齿,边缘柔化、消除混叠、反走样
self.painter.setRenderHint(QtGui.QPainter.Antialiasing)
self.draw_something()
self.painter.end()
def draw_something(self):
path = QtGui.QPainterPath()
path.moveTo(30, 30)
path.cubicTo(30, 30, 200, 350, 350, 30)
self.painter.drawPath(path)
if __name__ == "__main__":
run(DrawBezierCurve)
效果:
