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.
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
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_())
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_())
The most important aspect to consider is the sizeHint(), that is the recommended size a widget suggests to the layout that contains it.
Item views are tricky, though. They might have headers, their content could change many times during the lifespan of the program, and each item might have different sizes (which the user could interactively modify).
To achieve what you want, you have to use updateGeometry():
Notifies the layout system that this widget has changed and may need to change geometry.
Call this function if the sizeHint() or sizePolicy() have changed.
Note that calling adjustSize() is not suggested for this.
The size hint of an item view then must take into account the (visible) headers and the frame width, since all QAbstractItemView descendants inherit from QFrame.
Finally, to ensure that the size hint is dynamically adjusted and the layout system is notified about it, you should also connect all the correct signals that the model AND the header might send.
Note that, while you can connect all those signals externally, it is usually better to let the class itself take care of it internally.
class ExpandingTableView(qtw.QTableView):
shown = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
self.verticalHeader().sectionResized.connect(self.updateGeometry)
self.verticalHeader().sectionCountChanged.connect(self.updateGeometry)
def setVerticalHeader(self, header):
self.verticalHeader().sectionResized.disconnect(self.updateGeometry)
self.verticalHeader().sectionCountChanged.disconnect(self.updateGeometry)
super().setVerticalHeader(header)
header.sectionResized.connect(self.updateGeometry)
header.sectionCountChanged.connect(self.updateGeometry)
def setModel(self, model):
if self.model():
self.model().rowsInserted.disconnect(self.updateGeometry)
self.model().rowsRemoved.disconnect(self.updateGeometry)
super().setModel(model)
if model:
model.rowsInserted.connect(self.updateGeometry)
model.rowsRemoved.connect(self.updateGeometry)
self.updateGeometry()
# optional, if you want to ensure that a minimum height is always respected
def updateGeometry(self):
self.setMinimumHeight(min(self.sizeHint().height(),
self.verticalHeader().defaultSectionSize() * 8))
super().updateGeometry()
def sizeHint(self):
height = 0
if self.horizontalHeader().isVisible():
height += self.horizontalHeader().height()
height += self.verticalHeader().length() + self.frameWidth() * 2
return QSize(super().sizeHint().width(), height)
def showEvent(self, event):
super().showEvent(event)
# when the view is shown the first time it might not have computed the
# correct size hint, let's ensure that we notify the underlying
# layout manager(s)
if not self.shown:
self.shown = True
self.updateGeometry()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With