Motivation

In this post, I will share my experience in designing my GUI, which is how to fix the position of widgets. After learning the layout mechanism in PySide2 (link), I want to build a GUI that is similar to HTerm – a window app that I think has the best design consideration. Although HTerm may look less fancy, but it is an application that is designed for the users, emphasizing in usability. It is rare nowadays to find such a well-designed software.

However, I bumped into a big hurdle immediately. In HTerm, the first row after the menu bar has a fixed size before and after windows resizing. That is my goal in this post to replicate that effect.

What I have tried initially was:

from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtCore import Slot
from PySide2.QtWidgets import QFrame


class MainWindow(QtWidgets.QWidget):
    NumGridRows = 3
    NumButtons = 4

    def __init__(self, *args):
        #super(Dialog, self).__init__()
        QtWidgets.QWidget.__init__(self, *args)

        self.threadpool = QtCore.QThreadPool()

        self.createMenu()
        self.createConnectGroupBox()
        self.createGridGroupBox()
        self.createFormGroupBox()

        bigEditor = QtWidgets.QTextEdit()
        bigEditor.setPlainText("This widget takes up all the remaining space "
                "in the top-level layout.")
                
        self.info = QtWidgets.QLabel("Status: ")

        self.progressbar = QtWidgets.QProgressBar(self)
        self.progressbar.setRange(0,1)
               
        mainLayout = QtWidgets.QVBoxLayout()
        mainLayout.setMenuBar(self.menuBar)
        mainLayout.addWidget(self.horizontalGroupBox)
        
        mainLayout.addWidget(self.gridGroupBox)
        mainLayout.addWidget(bigEditor)
        
        mainLayout.addWidget(self.info)
        mainLayout.addWidget(self.progressbar)
        
        self.setLayout(mainLayout)
        self.setGeometry(150, 150, 1200, 800)
        self.setWindowTitle("TrimePod 0.1.0 alpha")

    def createMenu(self):
        self.menuBar = QtWidgets.QMenuBar()

        self.fileMenu = QtWidgets.QMenu("&File", self)
        self.exitAction = self.fileMenu.addAction("E&xit")
        self.menuBar.addMenu(self.fileMenu)
    
    def createConnectGroupBox(self):
        self.horizontalGroupBox = QtWidgets.QGroupBox()
        layout = QtWidgets.QHBoxLayout()
        
        self.buttonConnect = QtWidgets.QPushButton("Connect")
        self.buttonConnect.setFixedSize(QtCore.QSize(100,20))
        self.buttonConnect.move(20, 40)
        self.buttonConnect.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
        
        portLabel = QtWidgets.QLabel("Port")
        portLabel.setFixedSize(QtCore.QSize(30,20))
        portLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        
        self.comboPort = QtWidgets.QComboBox()
        self.comboPort.setFixedSize(QtCore.QSize(150,20))
        
        self.buttonRefreshPort = QtWidgets.QPushButton("R")
        self.buttonRefreshPort.setFixedSize(QtCore.QSize(20,20))
        
        self.buttonScan = QtWidgets.QPushButton("Scan")
        self.buttonScan.setFixedSize(QtCore.QSize(100,20))
        
        sernoLabel = QtWidgets.QLabel("Serno")
        sernoLabel.setFixedSize(QtCore.QSize(30,20))
        sernoLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
        
        self.comboSerno = QtWidgets.QComboBox()
        self.comboSerno.setFixedSize(QtCore.QSize(150,20))
        
        spacerLabel = QtWidgets.QLabel("")
        spacerLabel.setFixedSize(QtCore.QSize(20,20))
        spacerLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)

        layout.addWidget(self.buttonConnect)
        layout.addWidget(self.comboPort)
        layout.addWidget(self.buttonRefreshPort)
        layout.addWidget(self.buttonScan)
        layout.addWidget(sernoLabel)
        layout.addWidget(self.comboSerno)

        self.horizontalGroupBox.setLayout(layout)

    def createGridGroupBox(self):
        self.gridGroupBox = QtWidgets.QGroupBox("Grid layout")
        layout = QtWidgets.QGridLayout()

        for i in range(self.NumGridRows):
            label = QtWidgets.QLabel("Line %d:" % (i + 1))
            lineEdit = QtWidgets.QLineEdit()
            lineEdit.setFixedSize(QtCore.QSize(30,20))
            layout.addWidget(label, i + 1, 0)
            layout.addWidget(lineEdit, i + 1, 1)

        self.smallEditor = QtWidgets.QTextEdit()
        self.smallEditor.setPlainText("This widget takes up about two thirds "
                "of the grid layout.")

        layout.addWidget(self.smallEditor, 0, 2, 4, 1)

        layout.setColumnStretch(1, 10)
        layout.setColumnStretch(2, 20)
        self.gridGroupBox.setLayout(layout)

    def createFormGroupBox(self):
        self.formGroupBox = QtWidgets.QGroupBox("Form layout")
        layout = QtWidgets.QFormLayout()
        layout.addRow(QtWidgets.QLabel("Line 1:"), QtWidgets.QLineEdit())
        layout.addRow(QtWidgets.QLabel("Line 2, long text:"), QtWidgets.QComboBox())
        layout.addRow(QtWidgets.QLabel("Line 3:"), QtWidgets.QSpinBox())
        self.formGroupBox.setLayout(layout)
        
        
if __name__ == '__main__':

    import sys

    #app = QtWidgets.QApplication(sys.argv)
    #dialog = Dialog()
    #sys.exit(dialog.exec_())
    
    app = QtWidgets.QApplication(sys.argv)
    mainwindow = MainWindow()
    mainwindow.show()
    sys.exit(app.exec_())

This code is edited from the pyside2-examples Repo at examples→widgets→layouts→basiclayouts.py

The problem arises when I full-screen the application that the widgets are spread out.

Before full screen
After full screen

PySide2: How to fix the position of your widgets

To achieve the similar effect as Hterm, I resorted to using the freelayout instead.

def createConnectGroupBox(self):
        self.buttonConnect = QtWidgets.QPushButton(self)
        self.buttonConnect.setText("Connect")
        self.buttonConnect.setFixedSize(QtCore.QSize(100,20))
        self.buttonConnect.move(20, 30)
        
        portLabel = QtWidgets.QLabel(self)
        portLabel.setText("Port")
        portLabel.setFixedSize(QtCore.QSize(30,20))
        portLabel.move(140, 30)
        
        self.comboPort = QtWidgets.QComboBox(self)
        self.comboPort.setFixedSize(QtCore.QSize(150,20))
        self.comboPort.move(170, 30)
        
        for item in self.serial_ports():
            self.comboPort.addItem(item)

        self.buttonRefreshPort = QtWidgets.QPushButton(self)
        self.buttonRefreshPort.setText("R")
        self.buttonRefreshPort.setFixedSize(QtCore.QSize(20,20))
        self.buttonRefreshPort.move(330, 30)

        self.buttonScan = QtWidgets.QPushButton(self)
        self.buttonScan.setText("Scan")
        self.buttonScan.setFixedSize(QtCore.QSize(100,20))
        self.buttonScan.move(360, 30)
        
        sernoLabel = QtWidgets.QLabel(self)
        sernoLabel.setText("Serno")
        sernoLabel.setFixedSize(QtCore.QSize(30,20))
        sernoLabel.move(480, 30)
        
        self.comboSerno = QtWidgets.QComboBox(self)
        self.comboSerno.setFixedSize(QtCore.QSize(150,20))
        self.comboSerno.move(520, 30)

Note that by using the free layout approach, you do not need to attach the widgets/layouts back to the mainlayout.

Leave a comment

Your email address will not be published. Required fields are marked *