Home > front end >  A good way to create and edit PyQt elements in one line
A good way to create and edit PyQt elements in one line

Time:11-28

this is my first stackoverflow post so I hope I'm doing this right. I'm working on a gui in python using PyQt6 and I'm doing this in pure code as I prefer the control over the Qt designer. As I'm adding elements to the gui I'm noticing my code bloating and getting harder to navigate. Here is a picture of an element and the code I used to generate it:

lbl_size = QLabel("Size:")
lbl_size.setObjectName("lbl_size")

line_height = QLineEdit()
line_height.setObjectName("line_heigth")
line_height.setFixedWidth(40)

lbl_by = QLabel("by")
lbl_by.setObjectName("lbl_by")

line_width = QLineEdit()
line_width.setObjectName("line_width")
line_width.setFixedWidth(40)

combo_3d = QComboBox()
combo_3d.setObjectName("combo_3d")
combo_3d.addItems("2D", "3D")

hlayout_size = QHBoxLayout()
hlayout_size.addWidget(lbl_size)
hlayout_size.addWidget(line_height)
hlayout_size.addWidget(lbl_by)
hlayout_size.addWidget(line_width)
hlayout_size.addWidget(combo_3d)
hlayout_size.addStretch()
hlayout_size.setContentsMargins(10, 10, 10, 10)

What I like about this approach is that is quite legible and clear. What I dislike is that adding elements in such a way starts to add up, fast. I'm at a point where I feel bad for having such a large init function for some pages in my gui.

My main gripe is that I need to use functions for editing my widgets and layouts, rather than passing arguments into the creation of the widget for example. I made an attempt at solving this by creating custom classes that do take arguments to set up my widgets and layouts the way I want in only one line. These are examples of some custom classes I made:

class CQLabel(QLabel):
    def __init__(self, text=None, obj_name=None):
        QLabel.__init__(self)
        if text:
            self.setText(text)
        if obj_name:
            self.setObjectName(obj_name)


class CQLineEdit(QLineEdit):
    def __init__(self, placeholder_text=None, width=None, obj_name=None):
        QLineEdit.__init__(self)
        if placeholder_text:
            self.setPlaceholderText(placeholder_text)
        if width:
            self.setFixedWidth(width)
        if obj_name:
            self.setObjectName(obj_name)


class CQComboBox(QComboBox):
    def __init__(self, items=None, obj_name=None):
        QComboBox.__init__(self)
        if items:
            self.addItems(items)
        if obj_name:
            self.setObjectName(obj_name)


class CQHBoxLayout(QHBoxLayout):
    def __init__(self, items=None, stretch=False, margins=None, obj_name=None):
        QHBoxLayout.__init__(self)
        if items:
            for item in items:
                if isinstance(item, QWidget):
                    self.addWidget(item)
                elif isinstance(item, QLayout):
                    self.addLayout(item)
        if stretch:
            self.addStretch()
        if margins:
            self.setContentsMargins(margins)
        if obj_name:
            self.setObjectName(obj_name)

With these classes the code I need to generate the exact same element as before is the following:

lbl_size = CQLabel(text="Size:", obj_name="lbl_size")
line_height = CQLineEdit(width=40, obj_name="line_height")
lbl_by = CQLabel(text="by", obj_name="lbl_by")
line_width = CQLineEdit(width=40, obj_name="line_width")
combo_3d = CQComboBox(items=("2D", "3D"), obj_name="combo_3d")

hlayout_size = CQHBoxLayout(items=(lbl_size, line_height, lbl_by, line_width, combo_3d), stretch=True,
                            margins=(10, 10, 10, 10), obj_name="hlayout_size")

These kinds of statements are a bit harder to interpret at a glance than the straight foward function calls from before, but for me this works fully as intended. This way of building the gui gets better as I'm adding more elements and making more edits to my widgets.

My 'problem' now, is that this way I built these classes (mainly relying on if statements) is not something I've seen before and makes me wonder if it is a good approach at all. Seeing as I have almost no experience working on big projects I would really appreciate some comments on my approach. Is it good? Is it bad? Am I totally missing a more easy approach? Thanks for any help in advance!

(NB: I'm not focused on one-liners, just looking for a way to combat code bloat)

CodePudding user response:

PyQt natively supports setting properties in the class constructor using keyword arguments; in fact, it also allows direct signal connections (see the documentation).

For example:

label = QLabel('Hello', objectName='someLabel', font=QFont('Verdana'))
lineEdit = QLineEdit(placeholderText='type something')
button = QPushButton('click me', cliked=self.buttonClicked)

Note that this can only work:

  • for QObject subclasses (so, for instance, it's not possible for QStandardItem);
  • with writable Qt properties, if you want custom properties, use the pyqtProperty decorator;
  • using the exact property name (not the function getter, which might be different);

Also note that you should only set object names when you know that you'll need them (usually for later findChild* calls on dynamically created objects, or specific QSS selectors, or data serialization, like window settings), otherwise it's generally useless.

  • Related