1

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.

Michael Molter
  • 1,296
  • 2
  • 14
  • 37
  • You have several ways to do it, do you know about Qt's Model/View programming ? http://doc.qt.io/qt-5/model-view-programming.html; else with a MVC, your first class would be a model, your second the view, and you could use signals to trigger autochange in GUI's components passing by your controller – PyNico Jul 26 '16 at 14:46
  • Thank you for pointing me to this resource, I'll have to give it a read over. – Michael Molter Jul 26 '16 at 14:46
  • But using Qt , LineItem could just be a widget. You can create your own widget. To to that, your custom widget should inherit from QWidget per exemple. your override what you need, write your gui and the control logic, then you can use it just like a Button or any Qt's Widget – PyNico Jul 26 '16 at 15:16

1 Answers1

1

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)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Okay, thank you for clearing that up. I was particularly confused about how the `layout` command is used when dealing with different classes. To me, this vaguely resembles a wxPython panel. I just need some time to weave it into my application and prove it works before I accept. – Michael Molter Jul 26 '16 at 18:20
  • Works (with some modifications)! The line `super(Form, self).__init__(parent)` needs to be added to the `Form(QDialog)` initiator, the `layout.setContentsMargin()` is `layout.setContentsMargins()`, and there are a couple of typos that prevent it from running. Can I edit? – Michael Molter Jul 26 '16 at 19:08
  • @MichaelMolter. Thanks - I think I've fixed everything now. – ekhumoro Jul 26 '16 at 19:29
  • The only problem I've run into with this code, is getting the labels, values, and units to line up vertically. In my original (though not working) technique, I passed in the same grid, and thus all the widgets were on that grid. Now that they are independent layouts, how do I get them to organize together? – Michael Molter Jul 26 '16 at 21:22
  • 1
    @MichaelMolter. In that case, it doesn't really make sense to encapsulate each line as an widget, because it cannot really function independently of the other lines. Perhaps a better approach would be to create a Grid widget, which would have a method for adding lines, plus accessor methods for getting/setting field values by row index. TBH, though, I'm not really convinced that an OOP approach is best here (but that's what you asked for, so that's how I answered). – ekhumoro Jul 26 '16 at 21:49
  • I had intended to put the LineItem fields in a separate widget class so I could make them a little more intelligent. For example, I'm working on this class so that it will accept a value in a base unit (e.g. 'm'), and display the value in whatever unit the user selects in an adjacent unit combo box (e.g. 'm/min', 'cm/s'). I believe you need OOP structure to have this type of logic for multiple LineItem boxes. Do you have something better in mind? – Michael Molter Jul 29 '16 at 12:17
  • @MichaelMolter. This is starting drift a long way from your original question, which was about avoiding repetition whilst creating a gui structure. For that *specific* task, I would usually just use either a loop or a local function inside `__init__`. I think you're now asking the sort of conceptual questions about software design which perhaps belong on [programmers.stackexchange](http://programmers.stackexchange.com/). SO is much more narrowly focussed on specific programming problems. – ekhumoro Jul 29 '16 at 17:27