Строки "Итого" в QTableView PyQt5

В QTableView добавил строки итого в виде таблицы self.inner_table, которые находятся прямо под основной таблицей. Если количество строк такое, эта таблица не помещается в экран, то при прокрутке вниз строки "Итого" появляются. Однако, проблема в том, что при прокрутке вверх строка "Итого" не исчезает так же, как и любые строки самой таблицы. Для большего понимания примените код.

Также мне нужно, чтобы централизация виджетов была внутри CustomTableView без передачи в него аргумента main_window.

Пожалуйста, поправьте код так, чтобы CustomTableView был самодостаточным в плане создания строки "Итого": чтобы не требовалось передавать в него main_window и не требовался класс ContentWidget

Код прилагаю:

    import sys
from PyQt5.QtWidgets import QTableView, QMainWindow, \
    QApplication, QAbstractItemView, QVBoxLayout, QWidget, \
    QHeaderView, QScrollArea, QHBoxLayout
from PyQt5.QtGui import QStandardItem, QStandardItemModel 
from PyQt5.QtCore import Qt, QSize


class ContentWidget(QWidget):
    def sizeHint(self):  # <--- sizeHint
        return QSize(1500, 1000)


class CustomTableView(QTableView):
    def __init__(self, main_window):
        super().__init__()
        self.setModel(QStandardItemModel(55, 10))
        self.horizontalHeader().setVisible(False)
        self.verticalHeader().setVisible(False)
        self.setVerticalScrollBarPolicy(
            Qt.ScrollBarPolicy.ScrollBarAlwaysOff) 
        self.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch)  
        self.setSelectionMode(
            QAbstractItemView.SelectionMode.SingleSelection)

        self.scrollArea = QScrollArea()
        self.content_widget = ContentWidget()
        self.scrollArea.setWidget(self.content_widget)

        self.add_total_row()
        self.setup_layout()

        main_window.setCentralWidget(self.scrollArea)

    def add_total_row(self):
        self.inner_model = QStandardItemModel(2, 10)
        self.inner_table = QTableView(self)
        self.inner_table.setMaximumHeight(82)
        self.inner_table.setModel(self.inner_model)
        self.inner_table.horizontalHeader().setVisible(False)
        self.inner_table.verticalHeader().setVisible(False)
        self.inner_table.setVerticalScrollBarPolicy(
            Qt.ScrollBarPolicy.ScrollBarAlwaysOff) 
        self.inner_table.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch)  
        self.inner_table.setEditTriggers(
            QAbstractItemView.EditTrigger.NoEditTriggers)
        self.inner_table.setSelectionMode(
            QAbstractItemView.SelectionMode.NoSelection)

    def setup_layout(self):
        self.layout = QVBoxLayout(self.content_widget)
        self.layout.setSpacing(0)
        self.layout.addWidget(self)
        self.layout.addWidget(
            self.inner_table)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.table = CustomTableView(self)  # Pass the MainWindow instance as parent
        self.resize(1100, 600)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

Ответы (2 шт):

Автор решения: S. Nick

Попробуйте так:

import sys
from PyQt5.QtWidgets import  QTableView, QMainWindow, \
    QApplication, QAbstractItemView, QVBoxLayout, QWidget, \
    QHeaderView
from PyQt5.QtGui import QStandardItem, QStandardItemModel 
from PyQt5.QtCore import Qt


class CustomTableView(QTableView):
    def __init__(self, parent= None):
        super().__init__(parent)
        
        self.table_row_count = 55
        
        self.outer_model = QStandardItemModel(
            self.table_row_count, 10)
        self.setModel(self.outer_model)
        self.horizontalHeader().setVisible(False)
        self.verticalHeader().setVisible(False)
        self.setSelectionMode(
            QAbstractItemView.SelectionMode.SingleSelection)
#        self.add_total_row()

    def add_total_row(self):
        self.inner_model = QStandardItemModel(2, 10)
        self.inner_table = QTableView(self)
        self.inner_table.setObjectName('inner_table')            # +
        self.inner_table.setMaximumHeight(82)                    # +
        self.inner_table.setMinimumHeight(82)                    # +
        
        self.inner_table.setModel(self.inner_model)
        self.inner_table.horizontalHeader().setVisible(False)    # +
        self.inner_table.verticalHeader().setVisible(False)      # +
        self.inner_table.setEditTriggers(
            QAbstractItemView.EditTrigger.NoEditTriggers)
        self.inner_table.setSelectionMode(
            QAbstractItemView.SelectionMode.NoSelection)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        pass
        '''
        self.inner_table.setGeometry(
            0, self.rowHeight(0)*self.table_row_count,
            self.viewport().width(), self.rowHeight(0)*2+5)
        '''

# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.central_widget = QWidget()                          # +++
        self.setCentralWidget(self.central_widget)

        self.table = CustomTableView()
        self.table.setAlternatingRowColors(True)
        self.table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 
        self.table.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)         
        self.table.add_total_row()
        self.table.inner_table.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        
        self.layout = QVBoxLayout(self.central_widget)           # +++  
        self.layout.setSpacing(0)        
        self.layout.addWidget(self.table, stretch=9) 
        self.layout.addWidget(self.table.inner_table, 
            stretch=0, alignment=Qt.AlignBottom)
        self.layout.addStretch(1) 

        for i in range(self.table.outer_model.rowCount()): 
            it = QStandardItem(f"{i}")
            self.table.outer_model.setItem(i, 0, it)

Stylesheet = '''
/* тут вы можете установить свои стили для виджетов */
#inner_table { 
    border: 2px solid #f9009B;
    background-color: #8EDE81;
    selection-background-color: #999;
}
'''
        

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyleSheet(Stylesheet)
    app.setStyleSheet
    window = MainWindow()
    window.resize(1100, 600)
    window.show()
    sys.exit(app.exec())
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

введите сюда описание изображения

введите сюда описание изображения



Update:

Попробуйте так:

import sys
from PyQt5.QtWidgets import  QTableView, QMainWindow, \
    QApplication, QAbstractItemView, QVBoxLayout, QWidget, \
    QHeaderView, QScrollArea, QHBoxLayout
from PyQt5.QtGui import QStandardItem, QStandardItemModel 
from PyQt5.QtCore import Qt, QSize


class ContentWidget(QWidget):
        
    def sizeHint(self):                                 # <--- sizeHint
        return QSize(1500, 1000)
    

class CustomTableView(QTableView):
    def __init__(self, parent= None):
        super().__init__(parent)
        
        self.table_row_count = 55
        
        self.outer_model = QStandardItemModel(
            self.table_row_count, 10)
        self.setModel(self.outer_model)
        self.horizontalHeader().setVisible(False)
        self.verticalHeader().setVisible(False)
        self.setSelectionMode(
            QAbstractItemView.SelectionMode.SingleSelection)

    def add_total_row(self):
        self.inner_model = QStandardItemModel(2, 10)
        self.inner_table = QTableView(self)
        self.inner_table.setObjectName('inner_table')  
        self.inner_table.setMaximumHeight(82)         
        self.inner_table.setMinimumHeight(82)         
        
        self.inner_table.setModel(self.inner_model)
        self.inner_table.horizontalHeader().setVisible(False)
        self.inner_table.verticalHeader().setVisible(False)  
        self.inner_table.setEditTriggers(
            QAbstractItemView.EditTrigger.NoEditTriggers)
        self.inner_table.setSelectionMode(
            QAbstractItemView.SelectionMode.NoSelection)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        pass
        '''
        self.inner_table.setGeometry(
            0, self.rowHeight(0)*self.table_row_count,
            self.viewport().width(), self.rowHeight(0)*2+5)
        '''


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
  
        self.scrollArea = QScrollArea()                         # +++
        self.setCentralWidget(self.scrollArea)
        
        self.content_widget = ContentWidget()                   # +++
        self.scrollArea.setWidget(self.content_widget)
        
        self.table = CustomTableView()
        self.table.setAlternatingRowColors(True)
        self.table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 
        self.table.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)         
        self.table.add_total_row()
        self.table.inner_table.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        
        self.layout = QVBoxLayout(self.content_widget)   
 
        self.layout.setSpacing(0)        
        self.layout.addWidget(self.table, stretch=9) 
        self.layout.addWidget(self.table.inner_table, 
            stretch=0, alignment=Qt.AlignBottom)
        self.layout.addStretch(1) 

        for i in range(self.table.outer_model.rowCount()): 
            it = QStandardItem(f"{i}")
            self.table.outer_model.setItem(i, 0, it)


Stylesheet = '''
/* тут вы можете установить свои стили для виджетов */
#inner_table { 
    border: 2px solid #f9009B;
    background-color: #8EDE81;
    selection-background-color: #999;
}
'''
        

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyleSheet(Stylesheet)
    window = MainWindow()
    window.resize(1100, 600)
    window.show()
    sys.exit(app.exec())

введите сюда описание изображения

введите сюда описание изображения

→ Ссылка
Автор решения: needKVAS

Мне кажется, что размещать два виджета рядом — это не самый лучший путь решения вашей задачи. Я бы на вашем месте использовал бы proxy-модель, которая будет отвечать за то, чтобы добавлять в изначальную модель дополнительную строку.

Вот пример реализации:

import sys
from PyQt5.QtWidgets import  QTableView, QMainWindow, QApplication, QAbstractItemView
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtCore import Qt, QSize, QIdentityProxyModel, QModelIndex, QVariant


class TotalRowProxyModel(QIdentityProxyModel):
    def __init__(self, parent = None):
        super().__init__(parent)
    
    def isTotalRow(self, row, column, parent):
        return (not parent.isValid() and row == super().rowCount(parent) and column < self.columnCount())
    
    def rowCount(self, index):
        if not index.isValid():
            return super().rowCount(index) + 1
        return super().rowCount(index)
        
    def index(self, row, column, parent):
        if self.isTotalRow(row, column, parent):
            return self.createIndex(row, column, self)
        return super().index(row, column, parent)
        
    def parent(self, child):
        if child.internalPointer() is self:
            return QModelIndex();
        return super().parent(child)
    
    def sibling(self, row, column, idx):
        if self.isTotalRow(row, column, self.parent(idx)):
            return self.createIndex(row, column, self)
        return super().sibling(row, column, idx)
        
    def mapToSource(self, proxyIndex):
        if proxyIndex.internalPointer() is self:
            return QModelIndex();
        return super().mapToSource(proxyIndex)
        
    def flags(self, index):
        if index.internalPointer() is self:
            return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemNeverHasChildren
        return super().flags(index)
    
    #Здесь вы подсовываете свои значения итого
    def data(self, proxyIndex, role):
        if proxyIndex.internalPointer() is self:
            if role == Qt.DisplayRole:
                if proxyIndex.column() == 2:
                    return QVariant("Итого: 2")
                return QVariant("Пустое итого")
            return QVariant()
        return super().data(proxyIndex, role)

class CustomTableView(QTableView):
    def __init__(self, parent= None):
        super().__init__(parent)
        self.table_row_count = 40
        self.outer_model = QStandardItemModel(
            self.table_row_count, 10)
        self.proxy_model = TotalRowProxyModel()
        self.proxy_model.setSourceModel(self.outer_model)
        self.setModel(self.proxy_model)
        self.horizontalHeader().setVisible(False)
        self.verticalHeader().setVisible(False)
        self.setSelectionMode(
            QAbstractItemView.SelectionMode.SingleSelection)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.table = CustomTableView()
        self.setGeometry(0, 0, 1550, 1000)
        self.setCentralWidget(self.table)


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

Здесь не реализовано добавление данных, но я думаю вы сами сможете это сделать. Если вам не нужно выделение строки итого, вы можете убрать соответствующий флаг.

→ Ссылка