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

如何为带日期索引的QAbstractTableModel插入指定格式新行?

问题描述

我正在使用PyQt开发一款小型应用,用于展示数据库中的表格,支持用户添加新行并填写内容。表格的数据取自包含两列和日期索引的pd.DataFrame,要求插入的新行索引为最后一个日期索引加1天,且包含两个可编辑的空项。

当前应用无法实现新增行的需求,点击按钮后没有预期效果。以下是完整可复现代码:

import sys

import pandas as pd
from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView, QWidget, QGridLayout, QPushButton


class TableModel(QAbstractTableModel):

    def __init__(self, table_data, parent=None):
        super().__init__(parent)
        self.table_data = table_data

    def rowCount(self, parent=None) -> int:
        return self.table_data.shape[0]

    def columnCount(self, parent=None) -> int:
        return self.table_data.shape[1]

    def data(self, index: QModelIndex, role: int = ...):
        if role == Qt.DisplayRole:
            row = self.table_data.index[index.row()]
            col = self.table_data.columns[index.column()]
            value = self.table_data.loc[row][col]
            return str(value)

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return str(self.table_data.columns[section])
            if orientation == Qt.Vertical:
                return str(self.table_data.index[section])

    def insertRows(self, row: int, count: int, parent: QModelIndex = ...) -> bool:
        self.beginInsertRows()
        # whats here?
        self.endInsertRows()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = QMainWindow()

    widget = QWidget()

    layout = QGridLayout()
    widget.setLayout(layout)
    indexes = ['2023-01-03', '2023-01-02', '2023-01-01']
    data = {'col1': ['aa', 'bb', 'cc'], 'col2': [44, 55, 66]}
    table_data = pd.DataFrame(data=data, index=indexes)
    model = TableModel(table_data=table_data)
    table = QTableView()
    table.setModel(model)
    layout.addWidget(table)

    button = QPushButton("add row")
    button.clicked.connect(model.insertRows)

    button_2 = QPushButton("save changes")

    layout.addWidget(button)
    layout.addWidget(button_2)
    win.setCentralWidget(widget)

    win.show()
    app.exec()
解决方案

要实现需求,需对代码做以下核心修改:

1. 实现表格编辑功能

在TableModel类中添加flags和setData方法,让单元格支持编辑,并将修改同步到DataFrame:

def flags(self, index: QModelIndex) -> Qt.ItemFlags:
    return Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsEnabled

def setData(self, index: QModelIndex, value, role: int = ...) -> bool:
    if role == Qt.EditRole:
        row_idx = self.table_data.index[index.row()]
        col_name = self.table_data.columns[index.column()]
        self.table_data.loc[row_idx, col_name] = value
        self.dataChanged.emit(index, index)
        return True
    return False

2. 完善新增行逻辑

修改insertRows方法,计算新日期索引并添加空行,同时正确触发视图更新:

def insertRows(self, row: int, count: int, parent: QModelIndex = QModelIndex()) -> bool:
    # 默认在最后一行插入
    insert_pos = self.rowCount() if row == -1 else row
    # 转换索引为日期格式,计算新日期
    date_indexes = pd.to_datetime(self.table_data.index)
    new_date = date_indexes.max() + pd.Timedelta(days=1)
    new_date_str = new_date.strftime('%Y-%m-%d')

    self.beginInsertRows(parent, insert_pos, insert_pos + count - 1)
    # 批量添加空行
    for _ in range(count):
        self.table_data.loc[new_date_str] = [None, None]
        new_date += pd.Timedelta(days=1)
        new_date_str = new_date.strftime('%Y-%m-%d')
    self.endInsertRows()
    return True

3. 修复按钮连接问题

按钮点击时需传递正确的插入参数,修改连接代码:

button.clicked.connect(lambda: model.insertRows(-1, 1))

完整修改后的代码

import sys

import pandas as pd
from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView, QWidget, QGridLayout, QPushButton


class TableModel(QAbstractTableModel):

    def __init__(self, table_data, parent=None):
        super().__init__(parent)
        self.table_data = table_data

    def rowCount(self, parent=None) -> int:
        return self.table_data.shape[0]

    def columnCount(self, parent=None) -> int:
        return self.table_data.shape[1]

    def data(self, index: QModelIndex, role: int = ...):
        if role == Qt.DisplayRole or role == Qt.EditRole:
            row = self.table_data.index[index.row()]
            col = self.table_data.columns[index.column()]
            value = self.table_data.loc[row][col]
            return str(value) if value is not None else ""

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return str(self.table_data.columns[section])
            if orientation == Qt.Vertical:
                return str(self.table_data.index[section])

    def flags(self, index: QModelIndex) -> Qt.ItemFlags:
        return Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsEnabled

    def setData(self, index: QModelIndex, value, role: int = ...) -> bool:
        if role == Qt.EditRole:
            row_idx = self.table_data.index[index.row()]
            col_name = self.table_data.columns[index.column()]
            self.table_data.loc[row_idx, col_name] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def insertRows(self, row: int, count: int, parent: QModelIndex = QModelIndex()) -> bool:
        insert_pos = self.rowCount() if row == -1 else row
        date_indexes = pd.to_datetime(self.table_data.index)
        new_date = date_indexes.max() + pd.Timedelta(days=1)
        new_date_str = new_date.strftime('%Y-%m-%d')

        self.beginInsertRows(parent, insert_pos, insert_pos + count - 1)
        for _ in range(count):
            self.table_data.loc[new_date_str] = [None, None]
            new_date += pd.Timedelta(days=1)
            new_date_str = new_date.strftime('%Y-%m-%d')
        self.endInsertRows()
        return True


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = QMainWindow()

    widget = QWidget()

    layout = QGridLayout()
    widget.setLayout(layout)
    indexes = ['2023-01-03', '2023-01-02', '2023-01-01']
    data = {'col1': ['aa', 'bb', 'cc'], 'col2': [44, 55, 66]}
    table_data = pd.DataFrame(data=data, index=indexes)
    model = TableModel(table_data=table_data)
    table = QTableView()
    table.setModel(model)
    layout.addWidget(table)

    button = QPushButton("add row")
    button.clicked.connect(lambda: model.insertRows(-1, 1))

    button_2 = QPushButton("save changes")

    layout.addWidget(button)
    layout.addWidget(button_2)
    win.setCentralWidget(widget)

    win.show()
    app.exec()

关键说明

  • flags方法设置单元格的可编辑、可选择属性,是编辑功能的基础。
  • setData方法将用户编辑的内容同步到DataFrame,并触发视图更新。
  • insertRows方法中,先将原索引转为日期格式,计算出最大日期加1天作为新行索引,再添加空行并通知视图刷新。
  • 按钮通过lambda传递参数,指定在表格末尾插入1条新行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:48:19