【问题标题】:how to expand /decrease QTable View without visible blank white space如何在没有可见空白的情况下扩展/减少 QTable 视图
【发布时间】:2020-10-01 06:51:31
【问题描述】:

我有一个子类QAbstractTableModel
在不滚动的情况下以全尺寸显示表格视图中的数据我已经关闭了滚动条
为了摆脱表格视图周围的空白,我将垂直/水平表格长度设置为特定值。


问题是 我已向模型添加了添加/删除行方法,因此表视图现在扩展/收缩
调整表格视图行为以全尺寸显示数据并且没有空白我已将水平标题设置为 table_view.horizontalHeader().setStretchLastSection(True)
正确切断水平方向的空白
对空白区域的垂直标题切割也进行相同的操作,但会过度拉伸最后一行


我尝试将每一行设置为默认大小

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

但这会再次打开空白

简而言之:我正在寻找一种方法来在表格视图中以全尺寸显示模型数据而没有空白,同时能够删除/插入一行


代码示例

#!/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_())

【问题讨论】:

  • 我不明白。如果你想让最后一行拉伸,它显然会占据所有剩余的可用垂直空间。你能澄清一下你的想法吗?
  • @musicamante 我添加了一张图片以供澄清
  • 我还是不明白。难道你不能避免使用setStretchLastSectionsetDefaultSectionSize 和固定的调整大小模式吗?
  • @musicamante table_view.verticalHeader().setSectionResizeMode(qtw.QHeaderView.Fixed)table_view.verticalHeader().setDefaultSectionSize(x) 将行设置为相同的高度,但会拉伸垂直空白
  • 如果您使用setStretchLastSection,它将拉伸“垂直空白”,这正是 setStretchLastSection 所做的。我开始相信你问错了问题。也许您希望 table “缩小”它的高度,以便它只占用行所需的垂直空间?

标签: python pyqt pyqt5 qtableview qlayout


【解决方案1】:

空间不会神奇地消失。假设表格总高度为 600。如果表格中有两行,则第一行是 40。然后,如果您不想在表格底部留空,则第二行是 600 - 40 = 560 .如果将每行的高度设置为 40,则空白空间的高度将为 600 - 2 * 40 = 520。您不能要求(总高度 600)+(两行,每行 40)+(无空白底部的空间)。

所以,让我猜猜,你想要(a.底部没有空格)+(b,空间被均匀地分成一行,这样最后一行就不会看起来很奇怪。)。如果是这种情况,我已将您的代码编辑到下面解释了所有内容:

"""

"""

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_())

编辑:表格根据行自动调整高度

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_())

【讨论】:

  • 您的解决方案在开始时给了我两个超大的行,而不是退出我正在寻找的内容,是否有可能在布局中添加垫片并在添加/删除行时减小/增加大小所以行不会过度拉伸?
  • 让我更明白你想要什么:如果你的窗口高度是800,显示内容的区域高度是600(不包括标题栏、菜单栏等),并且表格中有三行,你想要的第一行的高度是多少?你想要的第二行的高度是多少?你想要的第三行的高度是多少?你想要的桌子高度是多少?
  • 每一行的高度应该相等,比如 (20,20,20) 这就是table_view.verticalHeader().setStretchLastSection(True) 的问题,它会拉伸最后一行以填充左侧空间
  • 那么,如果表格高度为600,空白区域的高度为600 - 20 * 3 = 540。那么,您是否要保持表格高度为600,并且空白区域的高度为540里面?或者将表格高度改为60,表格内部没有空白区域,但是表格底部和窗口底部之间有空白区域?
  • 这些是我坚持使用的选项。如果您查看我发布的代码示例并描述了问题 /////eather 在按钮上有 tableview 空白,或者是过度拉伸的桌子,我正在尝试什么达到的目的是增加/减少 table_view 的高度而没有空格,同时所有行都保持相同的高度
【解决方案2】:

要考虑的最重要方面是sizeHint(),这是一个小部件建议包含它的布局的推荐大小。

不过,项目视图很棘手。它们可能有标题,它们的内容在程序的生命周期中可能会发生多次变化,并且每个项目可能有不同的大小(用户可以交互修改)。

要实现你想要的,你必须使用updateGeometry()

通知布局系统此小部件已更改并且可能需要更改几何图形。

如果 sizeHint() 或 sizePolicy() 发生变化,则调用此函数。

请注意,不建议为此调用adjustSize()

项目视图的大小提示必须考虑(可见)标题帧宽度,因为所有 QAbstractItemView 后代都继承自 QFrame。

最后,为确保动态调整尺寸提示并通知布局系统,您还应该连接模型和标头可能发送的所有正确信号。
请注意,虽然您可以在外部连接所有这些信号,但通常最好让类自己在内部处理它。

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()

【讨论】:

    最近更新 更多