Search code examples
pythonpyqtpyqt5qtableviewqlayout

how to expand /decrease QTable View without visible blank white space


I have a subclassed QAbstractTableModel
to display the data in the Table View in full size without scrolling I have turned of the Scrollbars
to get ridd of the white space around the Table View I have set the vertical/horizontal table length to a specific value.

enter image description here
Problem is that I have added a add/deleate row Method to the Model so the Table View expands/shrinks now
to adjust the Table View behavior to display the data in full size and without white space I have set the horizontal Header to table_view.horizontalHeader().setStretchLastSection(True)
which cuts off the white space in horicontal direction correctly
the same operation for the vertical header cuts of the white space too but does over strech the last row

overstretching

I tryed to set each row to a default size with

table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)
        table_view.verticalHeader().setDefaultSectionSize(40)

but this turns the white space on again

In short form: Im looking for a way to display the model data in the Table View in Full Size without white Space while beeing able to deleate/insert a row


code example

#!/usr/bin/env python

"""

"""

import sys
import re


from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5.QtCore import Qt

from PyQt5 import QtGui as qtg


class ViewModel(qtc.QAbstractTableModel):

    def __init__(self, input_data=None):
        super().__init__()

        self.input_data = input_data or [["data","data","data","data"],["data","data","data","data"]]

    #

    def data(self, index, role):  # parameter index, role are needed !
        """

        """
        if role == qtc.Qt.DisplayRole:
            try:
                text = self.input_data[index.row()][index.column()]
            except IndexError:
                text = None

            return text

    def rowCount(self, index=qtc.QModelIndex()):
        return 0 if index.isValid() else len(self.input_data)


    def columnCount(self, index):
        return len(self.input_data[0])


    def insertRows(self, position, rows, parent=qtc.QModelIndex()):

        print(position) # -1
        position = (position + self.rowCount()) if position < 0 else position
        start = position

        end = position + rows - 1

        if end <= 8:
            self.beginInsertRows(parent, start, end)
            self.input_data.append([])
            self.endInsertRows()
            return True
        else:
            return False


    def removeRows(self, position, rows, parent=qtc.QModelIndex()):
        position = (position + self.rowCount()) if position < 0 else position

        start = position

        end = position + rows - 1

        if end >= 1:
            self.beginRemoveRows(parent, start, end)
            del self.input_data[start:end + 1]
            self.endRemoveRows()
            return True
        else:
            return False



    def headerData(self, section, orientation, role):

        if role == qtc.Qt.DisplayRole:

            if orientation == qtc.Qt.Horizontal:
                return "hight " + str(section+1) + " /mm"
            if orientation == qtc.Qt.Vertical:
                return "width " + str(section+1)


    def flags(self, index):
        return qtc.Qt.ItemIsEditable | qtc.Qt.ItemIsSelectable | qtc.Qt.ItemIsEnabled

    def setData(self, index, value, role=qtc.Qt.EditRole):
        if role == qtc.Qt.EditRole:
            try:
                row = index.row()
                column = index.column()

                pattern = '^[\d]+(?:,[\d]+)?$'


                if re.fullmatch(pattern, value, flags=0):
                    print("true")
                    self.input_data[row][column] = value  # float

                else:
                    print("nope")
                    pass

                return True

            except ValueError:
                print("not a number")
                return False


    def display_model_data(self):
        print(self.input_data)


class MainWindow(qtw.QWidget):
    def __init__(self):
        super().__init__()

        # geometry
        self.setGeometry(900, 360, 700, 800)


        # View
        table_view = qtw.QTableView()



        # done # turn scroll bars off
        table_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        table_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)





        self.model = ViewModel()
        table_view.setModel(self.model)




        table_view.horizontalHeader().setStretchLastSection(True)
        # table_view.verticalHeader().setStretchLastSection(True)

        table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)
        table_view.verticalHeader().setDefaultSectionSize(24)

        table_view.verticalHeader().setStretchLastSection(True)
        #     verticalHeader->setSectionResizeMode(QHeaderView::Fixed);
        # verticalHeader->setDefaultSectionSize(24);



        # widgets
        self.insert_row_button = qtw.QPushButton("insert row")
        self.deleate_row_button = qtw.QPushButton("deleate row")

        # layout
        layout = qtw.QVBoxLayout()
        layout.addWidget(table_view)
        layout.addWidget(self.insert_row_button)
        layout.addWidget(self.deleate_row_button)


        self.setLayout(layout)
        self.show()

        # function
        self.insert_row_button.clicked.connect(lambda: self.model.insertRows(-1, 1))
        self.deleate_row_button.clicked.connect(lambda: self.model.removeRows(-1, 1))


if __name__ == '__main__':
    app = qtw.QApplication(sys.argv)
    w = MainWindow()
    sys.exit(app.exec_())


Solution

  • space can't disappear magically. let's say the total table height is 600. if there are two rows in the table, the first row is 40. then, the second one is 600 - 40 = 560 if you don't wan't blank at the bottom of the table. if you set height of each row to 40, the height of the blank space would be 600 - 2 * 40 = 520. you can't require (a total height 600) + (two rows, 40 for each) + (no blank space at the bottom).

    So, let me guess, you want (a. no blank space at the bottom) + (b, space is evenly split into row, so that the last row won't look weird.). If that if the case, I've edited your code to below which explains everything:

    """
    
    """
    
    import sys
    import re
    
    
    from PyQt5 import QtWidgets as qtw
    from PyQt5 import QtCore as qtc
    from PyQt5.QtCore import Qt
    
    from PyQt5 import QtGui as qtg
    
    
    class ViewModel(qtc.QAbstractTableModel):
    
        def __init__(self, input_data=None):
            super().__init__()
    
            self.input_data = input_data or [["data","data","data","data"],["data","data","data","data"]]
    
        #
    
        def data(self, index, role):  # parameter index, role are needed !
            """
    
            """
            if role == qtc.Qt.DisplayRole:
                try:
                    text = self.input_data[index.row()][index.column()]
                except IndexError:
                    text = None
    
                return text
    
        def rowCount(self, index=qtc.QModelIndex()):
            return 0 if index.isValid() else len(self.input_data)
    
    
        def columnCount(self, index):
            return len(self.input_data[0])
    
    
        def insertRows(self, position, rows, parent=qtc.QModelIndex()):
    
            print(position) # -1
            position = (position + self.rowCount()) if position < 0 else position
            start = position
    
            end = position + rows - 1
    
            if end <= 8:
                self.beginInsertRows(parent, start, end)
                self.input_data.append([])
                self.endInsertRows()
                return True
            else:
                return False
    
    
        def removeRows(self, position, rows, parent=qtc.QModelIndex()):
            position = (position + self.rowCount()) if position < 0 else position
    
            start = position
    
            end = position + rows - 1
    
            if end >= 1:
                self.beginRemoveRows(parent, start, end)
                del self.input_data[start:end + 1]
                self.endRemoveRows()
                return True
            else:
                return False
    
    
    
        def headerData(self, section, orientation, role):
    
            if role == qtc.Qt.DisplayRole:
    
                if orientation == qtc.Qt.Horizontal:
                    return "hight " + str(section+1) + " /mm"
                if orientation == qtc.Qt.Vertical:
                    return "width " + str(section+1)
    
    
        def flags(self, index):
            return qtc.Qt.ItemIsEditable | qtc.Qt.ItemIsSelectable | qtc.Qt.ItemIsEnabled
    
        def setData(self, index, value, role=qtc.Qt.EditRole):
            if role == qtc.Qt.EditRole:
                try:
                    row = index.row()
                    column = index.column()
    
                    pattern = '^[\d]+(?:,[\d]+)?$'
    
    
                    if re.fullmatch(pattern, value, flags=0):
                        print("true")
                        self.input_data[row][column] = value  # float
    
                    else:
                        print("nope")
                        pass
    
                    return True
    
                except ValueError:
                    print("not a number")
                    return False
    
    
        def display_model_data(self):
            print(self.input_data)
    
    
    class NoBlankSpaceAtBottomEnvenlySplitTableView(qtw.QTableView):
        def sizeHintForRow(self, row):
            row_count = self.model().rowCount()
            height = self.viewport().height()
            row_height = int(height/row_count)
            if row < row_count - 1:
                return row_height
            else:
                return super().sizeHintForRow(row)
    
    
    class MainWindow(qtw.QWidget):
        def __init__(self):
            super().__init__()
    
            # geometry
            self.setGeometry(900, 360, 700, 800)
    
    
            # View
            # table_view = qtw.QTableView()
            table_view = NoBlankSpaceAtBottomEnvenlySplitTableView()
    
    
    
            # done # turn scroll bars off
            table_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            table_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
    
    
    
    
    
            self.model = ViewModel()
            table_view.setModel(self.model)
    
    
    
    
            table_view.horizontalHeader().setStretchLastSection(True)
            table_view.verticalHeader().setStretchLastSection(True)
    
            # table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)
            #table_view.verticalHeader().setDefaultSectionSize(24)
            table_view.verticalHeader().setSectionResizeMode(
                qtw.QHeaderView.ResizeToContents)  # Add this line
    
            table_view.verticalHeader().setStretchLastSection(True)
            #     verticalHeader->setSectionResizeMode(QHeaderView::Fixed);
            # verticalHeader->setDefaultSectionSize(24);
    
    
    
            # widgets
            self.insert_row_button = qtw.QPushButton("insert row")
            self.deleate_row_button = qtw.QPushButton("deleate row")
    
            # layout
            layout = qtw.QVBoxLayout()
            layout.addWidget(table_view)
            layout.addWidget(self.insert_row_button)
            layout.addWidget(self.deleate_row_button)
    
    
            self.setLayout(layout)
            self.show()
    
            # function
            self.insert_row_button.clicked.connect(lambda: self.model.insertRows(-1, 1))
            self.deleate_row_button.clicked.connect(lambda: self.model.removeRows(-1, 1))
    
    
    if __name__ == '__main__':
        app = qtw.QApplication(sys.argv)
        w = MainWindow()
        sys.exit(app.exec_())
    

    Edit: Table auto adjusts its height according to rows

    import sys
    import re
    
    
    from PyQt5 import QtWidgets as qtw
    from PyQt5 import QtCore as qtc
    from PyQt5.QtCore import Qt, QSize
    from PyQt5.QtWidgets import QSizePolicy
    
    from PyQt5 import QtGui as qtg
    
    
    class ViewModel(qtc.QAbstractTableModel):
    
        def __init__(self, input_data=None):
            super().__init__()
    
            self.input_data = input_data or [["data","data","data","data"],["data","data","data","data"]]
    
        #
    
        def data(self, index, role):  # parameter index, role are needed !
            """
    
            """
            if role == qtc.Qt.DisplayRole:
                try:
                    text = self.input_data[index.row()][index.column()]
                except IndexError:
                    text = None
    
                return text
    
        def rowCount(self, index=qtc.QModelIndex()):
            return 0 if index.isValid() else len(self.input_data)
    
    
        def columnCount(self, index):
            return len(self.input_data[0])
    
    
        def insertRows(self, position, rows, parent=qtc.QModelIndex()):
    
            print(position) # -1
            position = (position + self.rowCount()) if position < 0 else position
            start = position
    
            end = position + rows - 1
    
            if end <= 8:
                self.beginInsertRows(parent, start, end)
                self.input_data.append([])
                self.endInsertRows()
                return True
            else:
                return False
    
    
        def removeRows(self, position, rows, parent=qtc.QModelIndex()):
            position = (position + self.rowCount()) if position < 0 else position
    
            start = position
    
            end = position + rows - 1
    
            if end >= 1:
                self.beginRemoveRows(parent, start, end)
                del self.input_data[start:end + 1]
                self.endRemoveRows()
                return True
            else:
                return False
    
    
    
        def headerData(self, section, orientation, role):
    
            if role == qtc.Qt.DisplayRole:
    
                if orientation == qtc.Qt.Horizontal:
                    return "hight " + str(section+1) + " /mm"
                if orientation == qtc.Qt.Vertical:
                    return "width " + str(section+1)
    
    
        def flags(self, index):
            return qtc.Qt.ItemIsEditable | qtc.Qt.ItemIsSelectable | qtc.Qt.ItemIsEnabled
    
        def setData(self, index, value, role=qtc.Qt.EditRole):
            if role == qtc.Qt.EditRole:
                try:
                    row = index.row()
                    column = index.column()
    
                    pattern = '^[\d]+(?:,[\d]+)?$'
    
    
                    if re.fullmatch(pattern, value, flags=0):
                        print("true")
                        self.input_data[row][column] = value  # float
    
                    else:
                        print("nope")
                        pass
    
                    return True
    
                except ValueError:
                    print("not a number")
                    return False
    
    
        def display_model_data(self):
            print(self.input_data)
    
    
    class AutoExpandingTableView(qtw.QTableView):
        # def sizeHintForRow(self, row):
        #     row_count = self.model().rowCount()
        #     height = self.viewport().height()
        #     row_height = int(height/row_count)
        #     if row < row_count - 1:
        #         return row_height
        #     else:
        #         return super().sizeHintForRow(row)
    
        def sizeHint(self):
            viewport_size_hint = self.viewportSizeHint()
            return QSize(
                self.width(),
                viewport_size_hint.height()
            )
    
    
    class MainWindow(qtw.QWidget):
        def __init__(self):
            super().__init__()
    
            # geometry
            self.setGeometry(900, 360, 700, 800)
    
    
            # View
            # table_view = qtw.QTableView()
            table_view = AutoExpandingTableView()
            table_view.setSizePolicy(
                QSizePolicy.Expanding,
                QSizePolicy.Preferred
            )
    
            # done # turn scroll bars off
            table_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            table_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
    
            self.model = ViewModel()
            table_view.setModel(self.model)
            table_view.model().rowsInserted.connect(table_view.adjustSize)
            table_view.model().rowsRemoved.connect(table_view.adjustSize)
    
            table_view.horizontalHeader().setStretchLastSection(True)
            # table_view.verticalHeader().setStretchLastSection(True)
    
            # table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)
            #table_view.verticalHeader().setDefaultSectionSize(24)
            table_view.verticalHeader().setSectionResizeMode(
                qtw.QHeaderView.ResizeToContents)  # Add this line
    
            # widgets
            self.insert_row_button = qtw.QPushButton("insert row")
            self.deleate_row_button = qtw.QPushButton("deleate row")
    
            # layout
            layout = qtw.QVBoxLayout()
            layout.addWidget(table_view)
            layout.addStretch()
            layout.addWidget(self.insert_row_button)
            layout.addWidget(self.deleate_row_button)
    
    
            self.setLayout(layout)
            self.show()
    
            # function
            self.insert_row_button.clicked.connect(lambda: self.model.insertRows(-1, 1))
            self.deleate_row_button.clicked.connect(lambda: self.model.removeRows(-1, 1))
    
    
    if __name__ == '__main__':
        app = qtw.QApplication(sys.argv)
        w = MainWindow()
        sys.exit(app.exec_())