S Desai
S Desai

Reputation: 41

Creating a complex custom widget in PyQt5 and adding it to a QGridlayout

I have to create a custom widget that looks like the following:

custom_widget_sketch

Each custom widget is a representation of one LIPO battery, and displays the battery volatge (V), status text (charging, discharging, etc), serial number of the battery (S/N) and three status LEDs (yellow, green and red)

After I have created the custom widget I need to add 30 of these in a grid of 6*5. My assumption here is that once I have created that custom widget it should be as simple as adding say a QPushButton in a QGridLayout like so:

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

.
.
.

custom_layout.addWidget(custom_widget, 6, 5)

The final screen would look like this:

main_window_sketch

Considering all of these requirements I have the following questions:

  1. Will I able able to create such a complex/rich custom widget using PyQt5? Is it doable?
  2. Is this the correct approach to create the custom widget: first draw a square using QPainter (this is the blue square in the custom_widget_sketch), then add QLineEdits to dispaly the voltage (V) text, serial number (S/N) text and the Status text, add a QLabel for displaying the "V", "S/N" and "STATUS" labels in the custom widget, then draw the three circles: one each for the yellow, green and red LEDs, then use a combination of QVBoxLayout and QHBoxLayout to arrange the QLineEdits, QLabels, the square and the circles (LED indicators)
  3. How do I package this custom widget such that I can simply add it to a layout like I would add a QPushButton or a QLineEdit?

PS: The custom_widget_sketch also contains a line and a square with three lines inside it in the top left corner. This is to depict the connector for the LIPO battery. It may be too complex to implement that right now. So I would be happy even if I am able to implement everything other than these two elements

I have been through a few SO questions but they all refer to one tutorial, which is not my end goal.

I would appreciate any code snippets, general outline of code/steps to follow or links to any articles/tutorials that create custom widgets similar to the one I wish to create.

Upvotes: 2

Views: 4535

Answers (2)

S Desai
S Desai

Reputation: 41

Python code for the custom widget I ended up creating. The widget looks as follows:

custom_widget_screeenshot

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

I was able to easily create 30 instances of the custom widget and add it to a QGridLayout as I posted in my question. The final GUI screen looks as follows:

final_GUI_window_screenshot

Upvotes: 2

musicamante
musicamante

Reputation: 48231

There's no need to use QPainter for the blue square, as you can use stylesheets for your whole widget, the trick is to use selectors.

I tried to create your widget and used this stylesheet:

    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;
    }

I created a "container" QWidget, the status rectangle is actually a QFrame with its own layout, which I named statusFrame (you can set it in designer, or by means of setObjectName(str)).
By using object names and child selectors, I was able to set specific fonts for its labels by using the QFrame#statusFrame QLabel selector (which means "apply to each QLabel that is a child of a QFrame"); I also set the serialLabel object name to the s/n label, allowing me to set a different font size.

You can do this from code or using designer, just remember to set the right object names and parent/children hierarchy.

I was able to draw the "connector" part too, which requires to set fixed margins to the main widget:

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)

As you can see, it's almost the same as yours.

screenshot of the result

Upvotes: 0

Related Questions