Search code examples
pythonpython-2.7classpysideencapsulation

OOP techniques with Python GUI (PySide) elements


Objective: create a line item object that contains a textbox for a label, value, and value units in PySide.

Background: I am creating a control panel for a device that is run off of a Raspberry Pi using Python PySide (QtPython) to handle the GUI. I am using the grid layout, and have a common motif I am trying to encapsulate in a class to avoid repeating myself. I need some help building that class.

Typically, my code looks like this:

class Form(QDialog):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)

        self.pressure_label = QLabel('Pressure:')
        self.pressure_value = QLabel()
        self.pressure_units = QLabel('psi')

        self.temperature_label = QLabel('Temperature:')
        self.temperature_value = QLabel()
        self.temperature_units = QLabel('oC')
        ...

        grid = QGridLayout()

        grid.addWidget(pressure_label, 0, 0)
        grid.addWidget(pressure_value, 0, 1)
        grid.addWidget(pressure_units, 0, 1)

        grid.addWidget(temperature_label, 1, 0)
        grid.addWidget(temperature_value, 1, 1)
        grid.addWidget(temperature_units, 1, 1)
        ...

        self.setLayout(grid)

    def update(self):
        self.temperature_value.setText(t_sensor.read())
        self.pressure_value.setText(p_sensor.read())

What I have tried:

With GUI elements, I am not really sure where I need to put my classes, or what parent object they need to inherit. I have tried to create an object in the following way, but it is just a framework, and obviously won't compile.

class LineItem(object):
    def __init__(self, label_text, unit_text, grid, row):
        self.value = None
        self.units = None

        self.label_field = QLabel(label_text)
        self.value_field = QLabel()
        self.units_field = QLabel(unit_text)

        grid.addWidget(self.label_field, row, 0)
        grid.addWidget(self.value_field, row, 1)
        grid.addWidget(self.units_field, row, 2)

    @property
    def value(self):
         return self.value

    @value.setter
    def value(self, val):
        self.value = val
        self.value_field.setText(val)

    @property
    def units(self):
        return self.value

    @value.setter
    def units(self, val):
        self.units = val
        self.units_field.setText(val)

class Form(QDialog):
    def __init__(self, parent=None):
        grid = QGridLayout()

        row_number = itertools.count()
        tb_encoder_1 = LineItem('Distance:', 'm', grid, next(row_number))
        tb_encoder_2 = LineItem('Distance:', 'm', grid, next(row_number))

        self.setLayout(grid)

What I need:

What I am hoping to do is encapsulate this label, value, units structure into a class, so that I don't have to repeat myself so much.

Where does a class like this go? What does it inherit? How do I give it access to the grid object (does it even need access)?

What I struggle with is understanding how classes and encapsulation translate to PySide forms and widgets. Most of the tutorials I have seen so far don't go that route, they just put all the logic and creating in one big Form(QDialog) class.


Solution

  • You just need a QWidget subclass to act as a container for the other widgets. Its structure will be very similar to a normal form - the main difference is that it will end up as a child widget of another form, rather than as a top-level window.

    class LineItem(QWidget):
        def __init__(self, label_text, unit_text, parent=None):
            super(LineItem, self).__init__(parent)
    
            self.label_field = QLabel(label_text)
            self.value_field = QLabel()
            self.units_field = QLabel(unit_text)
    
            layout = QVBoxLayout()
            layout.setContentsMargins(0, 0, 0, 0)
    
            layout.addWidget(self.label_field)
            layout.addWidget(self.value_field)
            layout.addWidget(self.units_field)
    
            self.setLayout(layout)
    
    class Form(QDialog):
        def __init__(self, parent=None):
            super(Form, self).__init__(parent)
    
            self.pressure_line = LineItem('Pressure:', 'psi', self)
            self.temperature_line = LineItem('Temperature:', 'oC', self)
    
            layout = QHBoxLayout()
    
            layout.addWidget(self.pressure_line)
            layout.addWidget(self.temperature_line)
    
            self.setLayout(layout)