You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

QTableView删除行异常:点击删除按钮误删最后一行的修复方案

问题

点击Create按钮(快捷键Alt+C)可在QTableView中添加Port行,每行的Delete按钮本应删除对应行,但实际点击任意Delete按钮时,QTableView总是删除最后一行,而model.rows列表中能正确移除对应行。

问题代码

def deleteButtonPressed(self):
    # print('delete button  pressed', source)
    index = None
    port: Port
    for ind, port in enumerate(self.model.rows):
        if port.deleteButton is self.sender():
            index = ind
            break
    if index is not None:
        self.table.model().removeRow(index, QModelIndex())
        optionRow = self.model.rows.pop(index)
        self.table.model().layoutChanged.emit()
    else:
        print('Error index is None')

完整源码

import sys
import uuid
from typing import Any, Union
import random

from PySide6.QtWidgets import (QTableView, QWidget, QMainWindow, QApplication,
                               QPushButton, QToolBar, QVBoxLayout, QDoubleSpinBox)
from PySide6.QtCore import (Qt, QAbstractTableModel, QModelIndex, QPersistentModelIndex)
from PySide6.QtGui import (QAction)


##### Port ######

class Port:
    def __init__(self):
        self.id = str(uuid.uuid4())
        self.age = random.randint(22, 80)

        self.amt = QDoubleSpinBox()
        self.amt.setRange(0.0, float('inf'))
        self.amt.setValue(random.randint(0, 2e+3))
        self.amt.setSingleStep(1.0)
        self.amt.setPrefix('$')

        self.deleteButton = QPushButton('Del')

    def getId(self):
        return self.id

    def getAge(self):
        return self.age

    def setAge(self, age: int):
        self.age = age

    def getAmount(self):
        return self.amt.value()

    def setAmount(self, amt: float):
        self.amt.setValue(amt)


class TableModel(QAbstractTableModel):
    def __init__(self):
        super().__init__()
        self.columnsName = ['id', 'Age', 'Amt', 'Delete']
        self.rows: list[Port] = list()

    def data(self, index: Union[QModelIndex,
                                QPersistentModelIndex], role: int = ...) -> Any:
        row = index.row()
        col = index.column()
        port = self.rows[row]

        if role == Qt.DisplayRole:
            if col == 0:
                return port.getId()
            if col == 1:
                return port.getAge()

    def rowCount(self, parent: Union[QModelIndex, QPersistentModelIndex] = ...) -> int:
        return len(self.rows)

    def columnCount(self, parent: Union[QModelIndex, QPersistentModelIndex] = ...) -> int:
        return len(self.columnsName)

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> Any:
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return self.columnsName[section]
            if orientation == Qt.Vertical:
                return section + 1


##### MainWindow #####
class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("TableWindow")
        self.widget = QWidget()
        self.widget.layout = QVBoxLayout()
        self.widget.setLayout(self.widget.layout)

        ######### Initializing toolbar ########
        self.toolbar = QToolBar()
        self.initToolBar()
        self.addToolBar(self.toolbar)
        ########################################

        ######## Initializing table ###########
        self.table = QTableView()
        self.model = TableModel()
        self.table.setModel(self.model)
        self.widget.layout.addWidget(self.table)
        #######################################

        self.setCentralWidget(self.widget)

    def initToolBar(self):
        self.createAction = QAction("Create")
        self.createAction.setShortcut("Alt+C")
        self.toolbar.addAction(self.createAction)
        self.createAction.triggered.connect(self.createActionPressed)

    def createActionPressed(self):
        print('Create')
        self.port = Port()
        self.port.deleteButton.pressed.connect(self.deleteButtonPressed)
        self.appendRow(self.port)

    def appendRow(self, port: Port):
        index = len(self.table.model().rows)
        self.table.model().rows.append(port)
        self.table.setIndexWidget(self.table.model().index(index, 2), port.amt)
        self.table.setIndexWidget(self.table.model().index(index, 3), port.deleteButton)
        self.table.model().layoutChanged.emit()
        self.table.repaint()

    def deleteButtonPressed(self):
        # print('delete button  pressed', source)
        index = None
        port: Port
        for ind, port in enumerate(self.model.rows):
            if port.deleteButton is self.sender():
                index = ind
                break
        if index is not None:
            self.table.model().removeRow(index, QModelIndex())
            optionRow = self.model.rows.pop(index)
            self.table.model().layoutChanged.emit()
        else:
            print('Error index is None')


if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    app.exec()

演示效果

Table window


解决方案

问题根源

  1. 模型未遵循Qt规范:自定义的TableModel没有正确实现removeRow方法,直接调用默认的removeRow不会更新模型数据,后续手动修改rows列表再发送layoutChanged信号,会导致视图与模型状态不同步。
  2. 索引部件未清理:删除行时没有移除对应单元格的QDoubleSpinBox和QPushButton,这些部件会留在视图中,造成视觉上删除最后一行的错觉。

修复步骤

1. 给TableModel添加标准的removeRow实现

Qt模型视图架构要求修改数据时必须用beginRemoveRows和endRemoveRows包裹,通知视图数据变化:

class TableModel(QAbstractTableModel):
    # 保留原有方法,新增以下内容
    def removeRow(self, row: int, parent: Union[QModelIndex, QPersistentModelIndex] = ...) -> bool:
        if row < 0 or row >= len(self.rows):
            return False
        self.beginRemoveRows(parent, row, row)
        del self.rows[row]
        self.endRemoveRows()
        return True

2. 修改deleteButtonPressed方法,先清理部件再删除行

删除行前先移除对应单元格的部件,再通过模型的removeRow方法更新数据:

def deleteButtonPressed(self):
    index = None
    for ind, port in enumerate(self.model.rows):
        if port.deleteButton is self.sender():
            index = ind
            break
    if index is not None:
        # 清理对应列的索引部件
        amt_widget = self.table.indexWidget(self.model.index(index, 2))
        del_widget = self.table.indexWidget(self.model.index(index, 3))
        if amt_widget:
            amt_widget.deleteLater()
        if del_widget:
            del_widget.deleteLater()
        # 调用模型的removeRow方法,自动更新视图
        self.model.removeRow(index)
    else:
        print('Error index is None')

3. 优化appendRow方法(可选,遵循模型规范)

插入行时也用beginInsertRows和endInsertRows,让模型与视图状态保持一致:

def appendRow(self, port: Port):
    self.model.beginInsertRows(QModelIndex(), len(self.model.rows), len(self.model.rows))
    self.model.rows.append(port)
    self.model.endInsertRows()
    # 插入完成后再设置索引部件
    self.table.setIndexWidget(self.model.index(len(self.model.rows)-1, 2), port.amt)
    self.table.setIndexWidget(self.model.index(len(self.model.rows)-1, 3), port.deleteButton)

修复说明

  • 模型的removeRow方法通过beginRemoveRows和endRemoveRows通知视图行的移除,视图会自动更新显示,无需手动发送layoutChanged信号。
  • 删除行前清理索引部件,避免部件残留导致的显示混乱。
  • 插入行遵循模型规范,确保模型与视图状态完全同步,减少潜在的显示问题。

内容的提问来源于stack exchange,提问作者user8234870

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 16:57:34