自定义控件

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

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

Burning widget

这个组件我们会在Nero,K3B,或者其他CD/DVD烧录软件中见到。

  1. #!/usr/bin/python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. ZetCode PyQt5 tutorial
  5. In this example, we create a custom widget.
  6. Author: Jan Bodnar
  7. Website: zetcode.com
  8. Last edited: August 2017
  9. """
  10. from PyQt5.QtWidgets import (QWidget, QSlider, QApplication,
  11. QHBoxLayout, QVBoxLayout)
  12. from PyQt5.QtCore import QObject, Qt, pyqtSignal
  13. from PyQt5.QtGui import QPainter, QFont, QColor, QPen
  14. import sys
  15. class Communicate(QObject):
  16. updateBW = pyqtSignal(int)
  17. class BurningWidget(QWidget):
  18. def __init__(self):
  19. super().__init__()
  20. self.initUI()
  21. def initUI(self):
  22. self.setMinimumSize(1, 30)
  23. self.value = 75
  24. self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]
  25. def setValue(self, value):
  26. self.value = value
  27. def paintEvent(self, e):
  28. qp = QPainter()
  29. qp.begin(self)
  30. self.drawWidget(qp)
  31. qp.end()
  32. def drawWidget(self, qp):
  33. MAX_CAPACITY = 700
  34. OVER_CAPACITY = 750
  35. font = QFont('Serif', 7, QFont.Light)
  36. qp.setFont(font)
  37. size = self.size()
  38. w = size.width()
  39. h = size.height()
  40. step = int(round(w / 10))
  41. till = int(((w / OVER_CAPACITY) * self.value))
  42. full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
  43. if self.value >= MAX_CAPACITY:
  44. qp.setPen(QColor(255, 255, 255))
  45. qp.setBrush(QColor(255, 255, 184))
  46. qp.drawRect(0, 0, full, h)
  47. qp.setPen(QColor(255, 175, 175))
  48. qp.setBrush(QColor(255, 175, 175))
  49. qp.drawRect(full, 0, till-full, h)
  50. else:
  51. qp.setPen(QColor(255, 255, 255))
  52. qp.setBrush(QColor(255, 255, 184))
  53. qp.drawRect(0, 0, till, h)
  54. pen = QPen(QColor(20, 20, 20), 1,
  55. Qt.SolidLine)
  56. qp.setPen(pen)
  57. qp.setBrush(Qt.NoBrush)
  58. qp.drawRect(0, 0, w-1, h-1)
  59. j = 0
  60. for i in range(step, 10*step, step):
  61. qp.drawLine(i, 0, i, 5)
  62. metrics = qp.fontMetrics()
  63. fw = metrics.width(str(self.num[j]))
  64. qp.drawText(i-fw/2, h/2, str(self.num[j]))
  65. j = j + 1
  66. class Example(QWidget):
  67. def __init__(self):
  68. super().__init__()
  69. self.initUI()
  70. def initUI(self):
  71. OVER_CAPACITY = 750
  72. sld = QSlider(Qt.Horizontal, self)
  73. sld.setFocusPolicy(Qt.NoFocus)
  74. sld.setRange(1, OVER_CAPACITY)
  75. sld.setValue(75)
  76. sld.setGeometry(30, 40, 150, 30)
  77. self.c = Communicate()
  78. self.wid = BurningWidget()
  79. self.c.updateBW[int].connect(self.wid.setValue)
  80. sld.valueChanged[int].connect(self.changeValue)
  81. hbox = QHBoxLayout()
  82. hbox.addWidget(self.wid)
  83. vbox = QVBoxLayout()
  84. vbox.addStretch(1)
  85. vbox.addLayout(hbox)
  86. self.setLayout(vbox)
  87. self.setGeometry(300, 300, 390, 210)
  88. self.setWindowTitle('Burning widget')
  89. self.show()
  90. def changeValue(self, value):
  91. self.c.updateBW.emit(value)
  92. self.wid.repaint()
  93. if __name__ == '__main__':
  94. app = QApplication(sys.argv)
  95. ex = Example()
  96. sys.exit(app.exec_())

本例中,我们使用了QSlider和一个自定义组件,由进度条控制。显示的有物体(也就是CD/DVD)的总容量和剩余容量。进度条的范围是1~750。如果值达到了700(OVER_CAPACITY),就显示为红色,代表了烧毁了的意思。

烧录组件在窗口的底部,这个组件是用QHBoxLayoutQVBoxLayout组成的。

  1. class BurningWidget(QWidget):
  2. def __init__(self):
  3. super().__init__()

基于QWidget组件。

  1. self.setMinimumSize(1, 30)

修改组件进度条的高度,默认的有点小。

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

使用比默认更小一点的字体,这样更配。

  1. size = self.size()
  2. w = size.width()
  3. h = size.height()
  4. step = int(round(w / 10.0))
  5. till = int(((w / 750.0) * self.value))
  6. full = int(((w / 750.0) * 700))

动态的渲染组件,随着窗口的大小而变化,这就是我们计算窗口大小的原因。最后一个参数决定了组件的最大范围,进度条的值是由窗口大小按比例计算出来的。最大值的地方填充的是红色。注意这里使用的是浮点数,能提高计算和渲染的精度。

绘画由三部分组成,黄色或红色区域和黄色矩形,然后是分割线,最后是添上代表容量的数字。

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

这里使用字体去渲染文本。必须要知道文本的宽度,这样才能让文本的中间点正好落在竖线上。

  1. def changeValue(self, value):
  2. self.c.updateBW.emit(value)
  3. self.wid.repaint()

拖动滑块的时候,调用了changeValue()方法。这个方法内部,我们自定义了一个可以传参的updateBW信号。参数就是滑块的当前位置。这个数值之后还用来于Burning组件,然后重新渲染Burning组件。

burning widget