Achieving alignment of objects within window, adding graphs from another .py file

Josh E. :

So, I would like to ask you about better organizing multiple buttons, TextBox, Graphs in PyQt5. Previously, I’ve been helped a bit to use a grid to achieve a better alignment of the object I put into My Window.

However, the finale I’m trying to code is still a bit far from happening.

Here is what I’ve got so far:

from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import *
import sys


class Okno(QMainWindow):
    def __init__(self):
        super(Okno, self).__init__()
        self.setGeometry(500, 500, 900, 500)
        self.setWindowTitle("My window")
        self.button1 = QtWidgets.QPushButton()
        self.button2 = QtWidgets.QPushButton()
        self.TextBox1 = QLineEdit(self)  # Parameter self - add text box to my Window "Okno"
        self.TextBox2 = QLineEdit(self)
        self.TextBox3 = QLineEdit(self)
        self.TextBox4 = QLineEdit(self)   # Baudrate
        self.TextBox5 = QLineEdit(self)   # Parity
        self.TB_TEMP_IN = QLineEdit(self)   # Temp_IN
        self.TB_TEMP_OUT = QLineEdit(self)   # Temp_OUT
        self.label1 = QtWidgets.QLabel()  # TextBox1, pressure_IN
        self.label2 = QtWidgets.QLabel()
        self.label3 = QtWidgets.QLabel()
        self.label4 = QtWidgets.QLabel()  # TextBox3, pressure_OUT
        self.label_TEMP_IN = QtWidgets.QLabel()
        self.label_TEMP_OUT = QtWidgets.QLabel()
        self.iniUI()

    # Window objects
    def iniUI(self):
        w = QtWidgets.QWidget()
        self.setCentralWidget(w)
        grid = QtWidgets.QGridLayout(w)

        self.button1.setText("Open file")
        self.button1.setMinimumWidth(150)
        self.button1.clicked.connect(self.open_file)
        self.button2.setText("Exit")
        self.button2.clicked.connect(self.close)

        grid.addWidget(self.button1, 10, 0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom)
        grid.addWidget(self.button2, 10, 6, QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom)

        # Text Box
        grid.addWidget(self.TextBox1, 3, 1, QtCore.Qt.AlignCenter | QtCore.Qt.AlignCenter)

        self.TextBox2.setPlaceholderText("Enter port name")
        grid.addWidget(self.TextBox2, 3, 3, QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop)

        grid.addWidget(self.TextBox3, 3, 6, QtCore.Qt.AlignLeft | QtCore.Qt.AlignCenter)

        self.TextBox4.setPlaceholderText("Baudrate")
        grid.addWidget(self.TextBox4, 3, 3, QtCore.Qt.AlignCenter | QtCore.Qt.AlignCenter)

        self.TextBox5.setPlaceholderText("Parity")
        grid.addWidget(self.TextBox5, 3, 3, QtCore.Qt.AlignCenter | QtCore.Qt.AlignBottom)

        grid.addWidget(self.TB_TEMP_IN, 3, 1, QtCore.Qt.AlignCenter | QtCore.Qt.AlignBottom)
        grid.addWidget(self.TB_TEMP_OUT, 3, 6, QtCore.Qt.AlignCenter | QtCore.Qt.AlignBottom)

        # Label (Text Box name)
        self.label1.setText("Pressure_IN")
        grid.addWidget(self.label1, 3, 0, QtCore.Qt.AlignRight| QtCore.Qt.AlignCenter)

        self.label4.setText("Pressure_OUT")
        grid.addWidget(self.label4, 3, 5, QtCore.Qt.AlignRight | QtCore.Qt.AlignCenter)

        self.label_TEMP_IN.setText("Temp_IN")
        grid.addWidget(self.label_TEMP_IN, 3, 0, QtCore.Qt.AlignRight| QtCore.Qt.AlignBottom)

        self.label_TEMP_OUT.setText("Temp_OUT")
        grid.addWidget(self.label_TEMP_OUT, 3, 5, QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom)

        self.label2.setText("Data Input")
        grid.addWidget(self.label2, 0, 0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        self.label2.setStyleSheet('color: red')

        self.label3.setText("Data output")
        grid.addWidget(self.label3, 0, 6, QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
        self.label3.setStyleSheet('color: blue')

    def open_file(self):
        print("Open file")


def window():
    app = QApplication(sys.argv)
    okno = Okno()
    okno.show()
    sys.exit(app.exec_())


window()

One thing that I’ve noticed while adding more objects using Grid is that it’s hard to get it organized. I had to expand the grid to 10 x 6 if I understand correctly how it works, but the alignment (left, centre, right) doesn’t sort the object in aligned order such as rows / columns defined by absolute boundaries.

As for the events for Buttons, TextBoxes, … I should be able to program them, I just don’t have many experienced with Windows app while mostly doing console apps.

However, how should I add two graphs into My Window? I’m plotting them in another .py file using matplotlib.pyplot, I load part of the data from .xls files using pandas and the other part (data output) will be received over either serial (RS232) or TCP/IP interface. I have to plot them in the real-time as well as being able to store them (as .txt or .csv, it doesn’t really matter).

Could you please give me some hints about choosing the right concept of My Window to achieve the example I have attached below and to be able to “easily” add other objects in the future if necessary.

I definitely think that the more objects are in the window, the better organization is needed, because both: method iniUI(self) and class Okno(QMainWindow) are getting pretty long.

I have made a simple illustration of what I’m trying to achieve:

enter image description here

musicamante :

Setting the alignment of widgets when adding them to the layout has no effect on the widget alignment, but only on their alignment within the space the layout provides them. Also, many widgets automatically try to expand themselves (like QLabels or item views).

For example, a QLabel has a left/vertically centered default alignment for the text, so you need to set that alignment, but it still will try to expand its available space if other widgets allow it.

To get what you need, you'll have to consider spacing between widgets, their sizePolicy (which represents the widget's "willingness" about its size when used in a layout, should it have a fixed size, can it grow/expand, can it be shrunk, etc).

Since you want some space between the columns, the most simple thing to do is to leave empty columns in the layout, and set a stretch factor on those columns using setColumnStretch() so that they will try to expand as much as possible.

Finally, for complex structures, it's always better to use nested layouts, meaning that you'll have a main layout set for the widget, and "child" layouts are added to it, so that each "section" has its own layout manager, independent from the others. In the following example I've implemented the structure like this:

    +-------------- main vertical layout ---------------+
    |                                                   |
    |  +------------ button grid layout -------------+  |
    |  |title|    |(empty)|      |(empty)|     |title|  |
    |  +-----+----+-------+------+-------+-----+-----+  |
    |  |label|edit|       |input |       |label|edit |  |
    |  +-----+----+-------+------+-------+-----+-----+  |
    |  |label|edit|       |input |       |label|edit |  |
    |  +-----+----+-------+------+-------+-----+-----+  |
    |  |label|edit|       |input |       |label|edit |  |
    |  +-----+----+-------+------+-------+-----+-----+  |
    +---------------------------------------------------+
    |                                                   |
    |  +---------- graph horizontal layout ----------+  |
    |  |                      |                      |  |
    |  |       left graph     |      right graph     |  |
    |  |                      |                      |  |
    |  +----------------------+----------------------+  |
    +---------------------------------------------------+
    |                                                   |
    |  +--------- button horizontal layout ----------+  |
    |  |        |                           |        |  |
    |  | button |         (stretch)         | button |  |
    |  |        |                           |        |  |
    |  +--------+---------------------------+--------+  |
    +---------------------------------------------------+ 

Here's how it appears:

screenshot of the layout

And this is the code. Note that I removed widget creation from the init (if you use an initUi function it doesn't make a lot of sense that you create them elsewhere).

from PyQt5 import QtWidgets, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys


class Okno(QtWidgets.QMainWindow):
    def __init__(self):
        super(Okno, self).__init__()
        self.setGeometry(500, 500, 900, 500)
        self.setWindowTitle("My window")
        self.iniUI()

    def iniUI(self):
        w = QtWidgets.QWidget()
        self.setCentralWidget(w)

        mainLayout = QtWidgets.QVBoxLayout(w)

        grid = QtWidgets.QGridLayout()
        mainLayout.addLayout(grid)

        self.dataInputLabel = QtWidgets.QLabel('Data Input')
        grid.addWidget(self.dataInputLabel, 0, 0)
        self.dataInputLabel.setStyleSheet('color: red')

        portSettingLabel = QtWidgets.QLabel('Port setting', alignment=QtCore.Qt.AlignCenter)
        grid.addWidget(portSettingLabel, 0, 3)

        self.dataOutputLabel = QtWidgets.QLabel('Data Output', alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
        grid.addWidget(self.dataOutputLabel, 0, 6)
        self.dataOutputLabel.setStyleSheet('color: blue')

        # set the vertical policy to Maximum for labels, so they don't try to
        # expand themselves if there's more available space
        for label in (self.dataInputLabel, portSettingLabel, self.dataOutputLabel):
            label.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)

        grid.addWidget(QtWidgets.QLabel('Pressure [kPa]'), 1, 0)
        self.inputPressure = QtWidgets.QLineEdit(readOnly=True)
        grid.addWidget(self.inputPressure, 1, 1)

        grid.addWidget(QtWidgets.QLabel('Temperature [K]'), 2, 0)
        self.inputTemp = QtWidgets.QLineEdit(readOnly=True)
        grid.addWidget(self.inputTemp, 2, 1)

        grid.addWidget(QtWidgets.QLabel('Humidity [%]'), 3, 0)
        self.inputHumidity = QtWidgets.QLineEdit(readOnly=True)
        grid.addWidget(self.inputHumidity, 3, 1)

        self.portEdit = QtWidgets.QLineEdit(placeholderText='Enter port')
        grid.addWidget(self.portEdit, 1, 3)
        self.baudEdit = QtWidgets.QLineEdit(placeholderText='Baudrate')
        grid.addWidget(self.baudEdit, 2, 3)
        self.parityEdit = QtWidgets.QLineEdit(placeholderText='Parity')
        grid.addWidget(self.parityEdit, 3, 3)

        grid.addWidget(QtWidgets.QLabel('Pressure [kPa]'), 1, 5)
        self.outputPressure = QtWidgets.QLineEdit(readOnly=True)
        grid.addWidget(self.outputPressure, 1, 6)

        grid.addWidget(QtWidgets.QLabel('Temperature [K]'), 2, 5)
        self.outputTemp = QtWidgets.QLineEdit(readOnly=True)
        grid.addWidget(self.outputTemp, 2, 6)

        grid.addWidget(QtWidgets.QLabel('Humidity [%]'), 3, 5)
        self.outputHumidity = QtWidgets.QLineEdit(readOnly=True)
        grid.addWidget(self.outputHumidity, 3, 6)

        grid.setColumnStretch(2, 1)
        grid.setColumnStretch(4, 1)

        graphLayout = QtWidgets.QHBoxLayout()
        mainLayout.addLayout(graphLayout)
        # here insert your graphs...
        self.graphLeft = FigureCanvas(Figure())
        graphLayout.addWidget(self.graphLeft)
        self.graphRight = FigureCanvas(Figure())
        graphLayout.addWidget(self.graphRight)

        buttonLayout = QtWidgets.QHBoxLayout()
        mainLayout.addLayout(buttonLayout)

        self.openButton = QtWidgets.QPushButton('Open file')
        self.openButton.setMinimumWidth(150)
        self.openButton.clicked.connect(self.open_file)
        buttonLayout.addWidget(self.openButton)

        # add an empty "stretch", which acts as an expanding spacer on box layouts
        buttonLayout.addStretch()

        self.exitButton = QtWidgets.QPushButton('Exit')
        self.exitButton.clicked.connect(self.close)
        buttonLayout.addWidget(self.exitButton)

Finally, consider using Qt Designer, which is useful for creating complex layouts (or, at least, visualize them while you're in the process of designing them, if you still want to do everything by code). Note that in this case you should follow the guidelines suggested on using Designer (most importantly, never edit the python files generated with the pyuic utility), or use loadUi('yourguifile.ui', self) (where self is the widget/window you want to set up with the ui) from the PyQt5.uic module.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=376492&siteId=1