steelej
steelej

Reputation: 33

Python QTableWidget not displaying full width

I am an experienced (if rather rusty) Windows programmer. I am trying to create a partial logic simulation of a computer (Ferranti Argus 400) I designed in 1963 (yes I am that old!

I am however new to Python programming and am having a formatting problem using a QTableWidget.

I want to display this on the right alongside a set of controls for entering test data for a simulated function (and to use the QTableWidget to display the steps the simulation will run through to) for example show the working of the multiplier bit by bit.

I am trying to use a QHBoxLayout() as my outer container with two embedded Boxes. The left hand QVBox will contain my data entry for the simulation and the right hand box will contain a QTablewidget object with scrolling display of the state of the computer registers.

The QTableWidget works (the one below is a simplification) correctly until I add a Vbox on the left which contains the controls (one set is included here). The Table shrinks from its full width and positions itself at the right of the remaining space. I would like it on the left next to the other controls and for it to scroll if there is insufficient room (the target machine has 32*24 bit registers to display although I will be testing initially with 8 bits)

Screen with a group on left

enter image description here

class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 simple window'

        self.left = 10
        self.top = 50
        self.width = 800
        self.height = 480

        self.initUI()

        def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        # Parent layout
        self.central_Widget = QWidget(self)
        self.setCentralWidget(self.central_Widget)
        self.central_Layout = QHBoxLayout()

        # Configure left panel as Vbox
        self.leftpanel_Layout = QVBoxLayout()
        #
        self.leftpanel_Layout.addWidget(DataSelectionGroup("Data entry mode"))
        #
        # Add stretch to bottom of panel
        self.leftpanel_Layout.addStretch(0)

        self.rightpanel_Layout = QVBoxLayout()
        self.myLogTable = LogTablex()
        self.rightpanel_Layout.addWidget(self.myLogTable)

        self.setLayout(self.rightpanel_Layout)


        # Add left panel layout to central layout
   # uncomment these lines to see the unwanted behaviour
        #self.central_Layout.addLayout(self.leftpanel_Layout)
        #self.central_Layout.addStretch(0)

        self.setLayout(self.central_Layout)

        self.central_Layout.addLayout(self.rightpanel_Layout)
        self.setLayout(self.central_Layout)

        self.central_Widget.setLayout(self.central_Layout)
        self.setWindowTitle("Demo")
        self.show()


class DataSelectionGroup(QGroupBox):
    """ Create a group of Radio Buttons to choose type of data entry """
    def __init__(self, title):
        super().__init__(title)

        self.buttonLayout = QVBoxLayout()

        # add radio buttons to choose which mode of data entry to use
        self.radio1 = QRadioButton("&Binary")
        self.buttonLayout.addWidget(self.radio1)
        self.radio1.setChecked(True)

        self.radio2 = QRadioButton("Decimal &Fraction")
        self.buttonLayout.addWidget(self.radio2)

        self.radio3 = QRadioButton("Decimal &Integer")
        self.buttonLayout.addWidget(self.radio3)

        self.setLayout(self.buttonLayout)

class LogTablex(QTableWidget):
    def __init__(self, WordLength:int = 24):
        super().__init__()

        self.WordLength = WordLength
        self.initTable(["Note", "X", "Q", "N", "C", "O"])

    def initTable(self, headerlist):
        font = QFont()
        font.setFamily = "Courier New"
        font.setPointSize(8)

        self.setFont(font)
        self.horizontalHeader().setStyleSheet("QHeaderView::section { background-color:lightgrey; }")

        self.setColumnCount(len(headerlist))
        self.setHorizontalHeaderLabels(headerlist)

        self.setRowCount(0)
        self.start_newrow()
        self.str = '0' * self.WordLength + ' '

        self.setCellWidget(0, 0, QLabel("note"))
        self.setCellWidget(0, 1, QLabel(self.str))
        self.setCellWidget(0, 2, QLabel(self.str))
        self.setCellWidget(0, 3, QLabel(self.str))
        self.setCellWidget(0, 4, QLabel("1"))
        self.setCellWidget(0, 5, QLabel("1"))

        self.verticalHeader().setDefaultSectionSize(22)
        self.horizontalHeader().setSectionResizeMode(3)

        self.resizeColumnsToContents()

    def start_newrow(self, note:str = "new note         "):
        self.insertRow(self.rowCount())
        self.setCellWidget(self.rowCount() - 1, 0, QLabel(note))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

This version produces the first image. If you uncomment the two lines shown it produces the second version. I have battled with this for a while and I am unable to fathom how to get the right hand table to behave. I am hoping that someone can shed light on it.

I am using PyQt5 with the latest version of Python downloaded about 1 week ago. I am running on Windows 10.

This is my first post here. I hope it is formatted correctly

Upvotes: 3

Views: 1662

Answers (1)

eyllanesc
eyllanesc

Reputation: 244282

First of all I recommend you execute a script in the CMD or terminal because many times the IDEs hide us errors like for example if I execute your code I get the following message:

QWidget::setLayout: Attempting to set QLayout "" on App "", which already has a layout
QWidget::setLayout: Attempting to set QLayout "" on App "", which already has a layout
QWidget::setLayout: Attempting to set QLayout "" on App "", which already has a layout

And that error because QMainWindow is a special widget that has a preset layout as shown in the following image:

enter image description here

And in your case you are trying to replace it, the correct thing is to set the layout to the centralwidget.

Going to the problem it seems that you are adding layouts repeatedly and that causes the problem, so I have improved your code to establish the positions (I recommend not to make the layouts attributes of the class since they are generally not reused)

from PyQt5 import QtCore, QtGui, QtWidgets


class App(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 simple window'
        self.left, self.top, self.width, self.height = 10, 50, 800, 480
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        # Parent layout
        self.central_Widget = QtWidgets.QWidget()
        self.setCentralWidget(self.central_Widget)
        central_Layout = QtWidgets.QHBoxLayout(self.central_Widget)

        leftpanel_Layout = QtWidgets.QVBoxLayout()
        leftpanel_Layout.addWidget(DataSelectionGroup("Data entry mode"))
        leftpanel_Layout.addStretch()

        rightpanel_Layout = QtWidgets.QVBoxLayout()
        self.myLogTable = LogTablex()
        rightpanel_Layout.addWidget(self.myLogTable)

        central_Layout.addLayout(leftpanel_Layout)
        central_Layout.addLayout(rightpanel_Layout)
        self.setWindowTitle("Demo")
        self.show()


class DataSelectionGroup(QtWidgets.QGroupBox):
    """ Create a group of Radio Buttons to choose type of data entry """
    def __init__(self, title):
        super().__init__(title)
        buttonLayout = QtWidgets.QVBoxLayout(self)
        # add radio buttons to choose which mode of data entry to use
        self.radio1 = QtWidgets.QRadioButton("&Binary")
        buttonLayout.addWidget(self.radio1)
        self.radio1.setChecked(True)
        self.radio2 = QtWidgets.QRadioButton("Decimal &Fraction")
        buttonLayout.addWidget(self.radio2)
        self.radio3 = QtWidgets.QRadioButton("Decimal &Integer")
        buttonLayout.addWidget(self.radio3)


class LogTablex(QtWidgets.QTableWidget):
    def __init__(self, WordLength:int = 24):
        super().__init__()
        self.WordLength = WordLength
        self.initTable(["Note", "X", "Q", "N", "C", "O"])

    def initTable(self, headerlist):
        font = QtGui.QFont("Courier New", 8)
        self.setFont(font)
        self.horizontalHeader().setStyleSheet("QHeaderView::section { background-color:lightgrey; }")
        self.setColumnCount(len(headerlist))
        self.setHorizontalHeaderLabels(headerlist)
        self.setRowCount(0)
        self.start_newrow()

        text = '0' * self.WordLength + ' '
        for i, val in enumerate(("note", text, text, text, "1", "1",)):
            self.setCellWidget(0, i, QtWidgets.QLabel(val))

        self.verticalHeader().setDefaultSectionSize(22)
        self.horizontalHeader().setSectionResizeMode(3)
        self.resizeColumnsToContents()

    def start_newrow(self, note: str = "new note         "):
        self.insertRow(self.rowCount())
        self.setCellWidget(self.rowCount() - 1, 0, QtWidgets.QLabel(note))


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

enter image description here

Update:

For the first layout to occupy the minimum space and the other one to expand, you must add a stretch of 1 when you add the rightpanel_Layout, so change it to:

central_Layout.addLayout(leftpanel_Layout)
central_Layout.addLayout(rightpanel_Layout, stretch=1) # <---

Also it is not necessary to establish fixed anchors, the layouts will do the work for you:

class DataEntryGroupLayout(QtWidgets.QGroupBox):
    def __init__(self, title):
        super().__init__(title)
        _form = QtWidgets.QFormLayout(self)
        # Add the form controls to the group box = list required
        _pairlist = ["x value", "n value"]
        for _pair in _pairlist:
            _label = QtWidgets.QLabel(_pair)
            _value = QtWidgets.QLineEdit()
            _form.addRow(_label, _value)

enter image description here

Upvotes: 3

Related Questions