【问题标题】:Creating a complex custom widget in PyQt5 and adding it to a QGridlayout在 PyQt5 中创建复杂的自定义小部件并将其添加到 QGridlayout
【发布时间】:2019-12-09 12:21:06
【问题描述】:

我必须创建一个如下所示的自定义小部件:

custom_widget_sketch

每个自定义小部件代表一个 LIPO 电池,并显示电池电压(V)、状态文本(charging, discharging, etc)、电池序列号(S/N) 和三个状态LEDs(黄色、绿色和红色)

创建自定义小部件后,我需要在 6*5 的网格中添加其中的 30 个。我在这里的假设是,一旦我创建了自定义小部件,它应该就像在 QGridLayout 中添加一个 QPushButton 一样简单,如下所示:

custom_layput = QGridLayout()
custom_layout.addWidget(custom_widget, 0, 0)
custom_layout.addWidget(custom_widget, 0, 1)

.
.
.

custom_layout.addWidget(custom_widget, 6, 5)

最终屏幕如下所示:

main_window_sketch

考虑到所有这些要求,我有以下问题:

  1. 我能否使用 PyQt5 创建如此复杂/丰富的自定义小部件?可行吗?
  2. 这是创建自定义小部件的正确方法吗:首先使用 QPainter 绘制一个正方形(这是 custom_widget_sketch 中的蓝色正方形),然后添加 QLineEdits 以显示电压 (V) 文本、序列号 (S/N) text 和 Status 文本,添加一个 QLabel 用于在自定义小部件中显示“V”、“S/N”和“STATUS”标签,然后绘制三个圆圈:黄色、绿色和红色各一个LEDs,然后使用QVBoxLayout 和QHBoxLayout 的组合来排列QLineEditsQLabels、正方形和圆形(LED 指示灯)
  3. 如何打包此自定义小部件,以便我可以像添加QPushButtonQLineEdit 一样简单地将其添加到布局中?

PS:custom_widget_sketch 还包含一条线和一个正方形,其中左上角有三条线。这是为了描述 LIPO 电池的连接器。现在实施它可能太复杂了。因此,即使我能够实现这两个元素以外的所有内容,我也会很高兴

我已经解决了一些 SO 问题,但它们都参考了一个教程,这不是我的最终目标。

我将不胜感激任何代码 sn-ps、代码概要/要遵循的步骤或任何文章/教程的链接,这些文章/教程创建了类似于我希望创建的自定义小部件。

【问题讨论】:

  • 您可以使用 qtdesigner 为单个小部件创建一个类或 ui,并将其多次添加到布局中,如您所说。对于闪烁的 LED,您可以创建 QLabels 并向它们添加红色、黄色、绿色图标,并使用 QTimer 显示/隐藏模仿闪烁 LED 的图标
  • 你也可以用 QPropertyAnimation 做一些事情。 zetcode.com/pyqt/qpropertyanimation
  • 嗨 @Drees,感谢您的建议,我最终为每个 LED 创建了一个标签,并使用 self.yellow_led_label.setStyleSheet("QLabel {background-color : yellow; border-color : black; border-width : 1px; border-style : solid; border-radius : 10px; min-height: 20px; min-width: 20px}") 创建了一个圆形标签并为 LED 设置颜色。我还没有实现动画,但也感谢那个链接。

标签: python pyqt pyqt5 customization


【解决方案1】:

蓝色方块不需要使用 QPainter,因为您可以为整个小部件使用样式表,诀窍是使用选择器。

我尝试创建您的小部件并使用此样式表:

    Battery {
        background-color: white;
    }
    QFrame#statusFrame {
        background-color: rgb(64, 112, 190);
    }
    QFrame#statusFrame QLabel {
        color: white;
        font-weight: bold;
        font-size: 24pt;
    }
    QLineEdit {
        border: 1px solid black;
        font-size: 24pt;
    }
    #serialLabel {
        font-weight: bold;
        font-size: 16pt;
    }

我创建了一个“容器”QWidget,状态矩形实际上是一个有自己布局的QFrame,我将其命名为statusFrame(您可以在设计器中设置,也可以通过setObjectName(str)进行设置)。
通过使用对象名称和子选择器,我能够使用QFrame#statusFrame QLabel 选择器为其标签设置特定字体(这意味着“应用于每个 QFrame 的子 QLabel”);我还将serialLabel 对象名称设置为s/n 标签,允许我设置不同的字体大小。

您可以通过代码或使用设计器执行此操作,只需记住设置正确的对象名称和父/子层次结构。

我也能够绘制“连接器”部分,这需要为主小部件设置固定边距:

class Battery(QtWidgets.QWidget):
    connPath = QtGui.QPainterPath()
    connPath.addRect(30, 10, 40, 28)
    connPath.moveTo(30, 16)
    connPath.lineTo(45, 16)
    connPath.moveTo(30, 24)
    connPath.lineTo(45, 24)
    connPath.moveTo(30, 32)
    connPath.lineTo(45, 32)
    cablePen = QtGui.QColor(77, 122, 194)

    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        # the following is only if you create the whole widget from code,
        # otherwise ensure to set both widget and layout contentsMargins
        # accordingly in designer
        self.setContentsMargins(25, 50, 10, 10)
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)
        layout.setContentsMargins(0, 0, 0, 0)
        # ...

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.setRenderHints(qp.Antialiasing)
        qp.translate(.5, .5)
        qp.drawPath(self.connPath)

        qp.setPen(self.cablePen)
        cablePath = QtGui.QPainterPath()
        cablePath.moveTo(30, 24)
        top = self.statusFrame.geometry().top()
        cablePath.quadTo(0, top + 20, 25, top + 40)
        qp.drawPath(cablePath)

如你所见,它和你的几乎一样。

【讨论】:

  • 感谢您的回复以及绘制连接器的代码 sn-p。出于空间考虑,我们最终将其排除在外。我最终为蓝色框创建了一个垂直布局,将其添加到容器小部件中,并使用setStyleSheet 将小部件上的颜色设置为蓝色。我这样做是因为我之前没有使用过QFrame。在这种情况下,您是否有特定原因更喜欢 QFrame 而不是 Layout?还是因为您想用它代替我在原始问题中发布的 QPainter 方法?
  • 我也很好奇您是如何获得蓝色的cablePen = QtGui.QColor(77, 122, 194) RBG 编号的?我最终使用了它,但想了解未来的参考。
  • QFrame确实是为了避免不必要的paintEvent覆盖,因为只有小部件可以用作样式表元素选择器,而不是布局;只要使用正确的选择器(对象名称或自定义属性),它也可以通过简单的 QWidget 来完成。我使用您的源图像通过颜色选择器(实际上是 Qt 的像素工具)获取电缆颜色;因为它是抗锯齿的,所以我只选择与您的示例最匹配的颜色。
【解决方案2】:

我最终创建的自定义小部件的 Python 代码。小部件如下所示:

from PyQt5.QtGui import QPainter, QPen,QBrush,QColor
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout,QPushButton, QLineEdit, QLabel, QVBoxLayout, QHBoxLayout, QSizePolicy, QGroupBox
import sys

class BatteryStatusWidget(QWidget):
    def __init__(self):
        super(BatteryStatusWidget, self).__init__()
        #Voltage widgets
        self.voltage_text = QLineEdit()
        self.voltage_text.setReadOnly(True)
        self.voltage_label = QLabel("V")
        self.voltage_label.setStyleSheet("QLabel {color : white}")

        #Status widgets
        self.status_text = QLineEdit()
        self.status_text.setReadOnly(True)
        self.status_label = QLabel("STATUS")
        self.status_label_font = QtGui.QFont()
        self.status_label_font.setPointSize(12)
        self.status_label.setFont(self.status_label_font)
        self.status_label.setAlignment(QtCore.Qt.AlignCenter)
        self.status_label.setStyleSheet("QLabel {color : white}")

        #Serial number
        self.serial_number_text = QLineEdit()
        self.serial_number_label = QLabel("S/N")

        #LED widgets
        self.yellow_led_label = QLabel()
        self.yellow_led_label.setStyleSheet("QLabel {background-color : yellow; border-color : black; border-width : 1px; border-style : solid; border-radius : 10px; min-height: 20px; min-width: 20px}")
        self.green_led_label = QLabel()
        self.green_led_label.setStyleSheet("QLabel {background-color : green; border-color : black; border-width : 1px; border-style : solid; border-radius : 10px; min-height: 20px; min-width: 20px}")
        self.red_led_label = QLabel()
        self.red_led_label.setStyleSheet("QLabel {background-color : red; border-color : black; border-width : 1px; border-style : solid; border-radius : 10px; min-height: 20px; min-width: 20px}")

        #Number Identifier Label
        #This label is for tagging the widget with the same label as on the PCB
        self.number_label = QLabel("Test")
        self.number_label.setAlignment(QtCore.Qt.AlignCenter)
        self.number_label_font = QtGui.QFont()
        self.number_label_font.setPointSize(12)
        self.number_label_font.setBold(True)
        self.number_label.setFont(self.number_label_font)

        #Layouts
        #voltage layout
        self.voltage_layout = QHBoxLayout()
        self.voltage_layout.addWidget(self.voltage_text)
        self.voltage_layout.addWidget(self.voltage_label)

        #Serial number layout
        self.serial_num_layout = QHBoxLayout()
        self.serial_num_layout.addWidget(self.serial_number_label)
        self.serial_num_layout.addWidget(self.serial_number_text)

        #Voltage and status box layouts
        self.blue_container = QWidget()
        self.blue_container.setStyleSheet("background-color:rgb(77, 122, 194);")
        self.blue_box_layout = QVBoxLayout()
        self.blue_box_layout.addLayout(self.voltage_layout)
        self.blue_box_layout.addWidget(self.status_text)
        self.blue_box_layout.addWidget(self.status_label)
        self.blue_container.setLayout(self.blue_box_layout)


        #Blue box+ serial num layout
        self.non_led_layout = QVBoxLayout()
        #self.non_led_layout.addWidget(self.number_label)
        self.non_led_layout.addWidget(self.blue_container)
        self.non_led_layout.addLayout(self.serial_num_layout)

        #LED layout
        self.led_layout = QVBoxLayout()
        self.led_layout.addWidget(self.yellow_led_label)
        self.led_layout.addWidget(self.green_led_label)
        self.led_layout.addWidget(self.red_led_label)
        self.led_layout.addStretch(1)

        #Main Layout
        self.main_layout = QHBoxLayout()
        self.main_layout.addLayout(self.non_led_layout)
        self.main_layout.addLayout(self.led_layout)

        #Main group box
        self.main_group_box = QGroupBox()
        self.main_group_box.setStyleSheet("QGroupBox{font-size: 10px}")
        self.main_group_box.setTitle("Chan #")
        self.main_group_box.setLayout(self.main_layout)

        #Outer main layout to accomodate the group box
        self.outer_main_layout = QVBoxLayout()
        self.outer_main_layout.addWidget(self.main_group_box)

        #Set the main layout
        self.setLayout(self.outer_main_layout)
        self.setWindowTitle("Battery Widget")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = BatteryStatusWidget()
    main_window.show()
    app.exec_()

正如我在问题中发布的那样,我能够轻松创建 30 个自定义小部件实例并将其添加到 QGridLayout。最终的 GUI 屏幕如下所示:

【讨论】:

  • 我需要这个代码来解决我的问题
猜你喜欢
  • 2017-11-06
  • 1970-01-01
  • 1970-01-01
  • 2017-09-04
  • 2014-04-08
  • 2015-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多