PyQt5 QAbstractTableModel,QStringListModel使用方法

本文介绍如何在PyQt中使用Model/View架构实现数据与视图的动态绑定,通过自定义CurrencyModel和使用QStringListModel,实现表格数据的动态增删改,提升界面的扩展性和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最新项目需要在PyQt中使用表格,列表等样式.一开始我的写法很简单,在Qt-designer中拖了QListWidget与QTableWidget.

向tableWdiget动态添加一行采用如下代码

    def _insert_to_table(self, tableWidget):
        '''将数据插入到表格中,self.table_data保存了一行所有数据'''
        tableWidget.insertRow(tableWidget.rowCount())
        for column in range(tableWidget.columnCount()):
            # print(self.table_data[column])
            item = QTableWidgetItem(str(self.table_data[column]))
            tableWidget.setItem(tableWidget.rowCount() - 1, column, item)

向listWidget动态添加一行采用如下代码

self.listWidget.addItem(name)

但这样很恶心,动态扩展性很差.网上对PyQt中QAbstractTableModel/QStringListModel介绍的很少,下面介绍一种符合Model/view设计模式的方法.当然仅仅贴出一段例子,具体细节要是要去参考官方文档.

dialog.py

# @Author  : 张帆
# @Site    : 
# @File    : teamleadersdialog.py
# @Software: PyCharm
import sys

from PyQt5.QtCore import QStringListModel
from PyQt5.QtWidgets import QDialog, QListView, QAbstractItemView, QDialogButtonBox, QVBoxLayout, QTableView, \
    QApplication

from learnMVC.currencymodel import CurrencyModel


class TeamLeadersDialog(QDialog):
    def __init__(self, leaders):
        super(TeamLeadersDialog, self).__init__()

        self.model = QStringListModel(self)
        self.model.setStringList(leaders)

        self.listView = QListView()
        self.listView.setModel(self.model)
        self.listView.setEditTriggers(QAbstractItemView.AnyKeyPressed | QAbstractItemView.DoubleClicked)
        self.buttonBox = QDialogButtonBox()
        self.insertButton = self.buttonBox.addButton("Insert", QDialogButtonBox.ActionRole)
        self.deleteButton = self.buttonBox.addButton("Delete", QDialogButtonBox.ActionRole)
        self.buttonBox.addButton(QDialogButtonBox.Ok)
        self.buttonBox.addButton(QDialogButtonBox.Cancel)
        self.insertButton.clicked.connect(self.insert)
        self.deleteButton.clicked.connect(self.del_)
        self.currencyMap = {
            "header": ["姓名", "性别", "年龄"],
            "data": [["张帆", "男", 24], ["张帆", "男", 24], ["张帆", "男", 24], ["张帆", "男", 24], ["张帆", "男", 24],
                     ["张帆", "男", 24]]
        }
        self.currencyModel = CurrencyModel(self.currencyMap.get('data'),self.currencyMap.get('header'))
        self.tableView = QTableView()
        self.tableView.setModel(self.currencyModel)
        self.tableView.setAlternatingRowColors(True)
        self.tableView.setWindowTitle("Currencies")
        self.tableView.show()
        self.mainLayout = QVBoxLayout()
        self.mainLayout.addWidget(self.listView)
        self.mainLayout.addWidget(self.buttonBox)
        self.mainLayout.addWidget(self.tableView)
        self.setLayout(self.mainLayout)
        self.setWindowTitle("Team leaders")
        self.count = 0

    def leaders(self):
        return self.model.stringList()

    def insert(self):
        row = self.model.rowCount()
        print(row)
        self.model.insertRow(row)
        index = self.model.index(row)
        self.model.setData(index,"123")
        currencyMap = {
            "header": ["姓名", "性别", "年龄"],
            "data": [["张帆", "男", 242], ["张帆", "男", 24], ["张帆", "男", 24], ["张帆", "男", 24], ["张帆", "男", 24],
                     ["张帆", "男", 24],['sdaf','asdf','asdf']]
        }
        x = self.tableView.currentIndex()

        self.currencyModel.append_data(['zhangfan','nf',str(self.count)])
        self.count+=1
        print(self.currencyModel.data)



    def del_(self):
        self.model.removeRows(self.listView.currentIndex().row(), 1)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    leaders = ['1','2','3','4','5','6','6']
    teamLeadersDialog = TeamLeadersDialog(leaders)
    teamLeadersDialog.show()
    app.exec_()

currencymodel.py

# -*- coding: utf-8 -*-
# @Time    : 18-12-1 下午3:26
# @Author  : 张帆
# @Site    : 
# @File    : currencymodel.py
# @Software: PyCharm
from PyQt5.QtCore import QAbstractTableModel, QVariant, Qt


class CurrencyModel(QAbstractTableModel):
    def __init__(self,data:list,header):
        super(CurrencyModel, self).__init__()
        self.data = data
        self.header = header


    def append_data(self,x):
        self.data.append(x)
        self.layoutChanged.emit()

    def remove_row(self,row):
        self.data.pop(row)
        self.layoutChanged.emit()

    def rowCount(self, parent=None, *args, **kwargs):
        return len(self.data)

    def columnCount(self, parent=None, *args, **kwargs):
        if len(self.data) > 0:
            return len(self.data[0])
        return 0


    def get_data(self):
        return self
    # 返回一个项的任意角色的值,这个项被指定为QModelIndex
    def data(self, QModelIndex, role=None):
        if not QModelIndex.isValid():
            print("行或者列有问题")
            return QVariant()
        # if role == Qt.TextAlignmentRole:
        #     return int(Qt.AlignRight | Qt.AlignVCenter)
        # elif role == Qt.DisplayRole:
        #     row = QModelIndex.row()
        #     column = QModelIndex.column()
        #     return self.currencyMap.get('data')[row][column]
        # # print("查数据")
        # row = QModelIndex.row()
        # # print('role:',role)
        # if role is None:
        #     return self.currencyMap.get('data')[row]
        elif role != Qt.DisplayRole:
            return QVariant()
        return QVariant(self.data[QModelIndex.row()][QModelIndex.column()])


    def headerData(self, p_int, Qt_Orientation, role=None):
        # if role != Qt.DisplayRole:
        #     return QVariant()
        # else:
        #     if Qt_Orientation == Qt.Horizontal:
        #         if len(self.currencyMap.get('header')) > p_int:
        #             return self.currencyMap.get('header')[p_int]
        if Qt_Orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return QVariant(self.header[p_int])
        return QVariant

这种方法有什么好的呢?我认为这样可以实现动态绑定,我们通过修改列表中的数据,就可以该表视图中的数据.优势是不言而喻的.QStringListModel就不在解释了,挺简单的.下面对使用到currencymodel.py 简单的解释以下,我自己也不太明白,如有问题,请指出.

 

1.首先该类继承了QAbstractTableModel,就必须要实现rowCount(),columnCount(),data(),headerData()

2.初始化:

    1.指定表格数据结构,我设计的结构为 [['xxx','xxx','xxx'],['ttt','ttt','ttt'],...,]

    2.指定表头数据结构,我设计的结构为['xxx','xxx','xxx']

2.rowCount() 要返回该表格的行数

3.columnCount() 返回该表格的列数

4.data()    此方法我也不太了解,但调试时发现,他会经常逐表格调用,QModelIndex的值会根据行数列数的值,遍历每一个单元格

所以此处根据QModelIndex返回每一个单元格的值

5.headerData 与4相似,会根据列数自动调用

6.append_data  如果我们要动态的增加一行,只需要向self.data列表增加一行,之后调用self.layoutChanged.emit()即可

7.remove_data 同上,如果要动态删除一行,只需要将self.data指定行删除,之后调用self.layoutChanged.emit()即可.

至此,我们就可以实现数据与视图绑定,进一步实现set_data 应该可以实现双向绑定(vue....)

网上关于pyqt的资料真的很少,这点东西,都是花了1天的时间,看了不少C++代码才写出来的,感觉qt相对于前端氛围还是太差了,是不是要被淘汰呢...总而言之,希望这些对大家有所帮助.

<think>我们正在讨论PyQt5中的QAbstractTableModel的详细使用方法。用户已经提供了一个引用,但该引用是关于QML的,并且可能不直接适用于当前问题。用户的核心需求是了解如何在PyQt5中详细使用QAbstractTableModel,包括方法和示例。 我们将按照以下结构进行回答: 1. QAbstractTableModel简介 2. 必须重写的方法 3. 完整示例代码(包括数据模型、视图和主窗口) 4. 高级功能(如编辑、排序、自定义渲染等) 5. 常见问题解答 6. 相关问题生成 注意:用户提供的引用中提到了columnCount必须重写,并给出了一个返回roleNames长度的例子。但在表格模型中,columnCount通常返回列数,而roleNames用于定义角色名称(在QML中常用)。在PyQt5的表格视图中,我们通常不需要重写roleNames,除非使用QML。因此,我们将按照标准表格模型来实现。 步骤: 1. 创建模型类,继承QAbstractTableModel。 2. 重写rowCount, columnCount, data, headerData等方法。 3. 可选:重写setData和flags以实现编辑功能。 4. 创建视图(QTableView)并设置模型。 下面开始详细回答。</think># PyQt5 QAbstractTableModel 详细使用指南 ## 一、核心概念与基础实现 ### 1. 模型-视图架构 PyQt5 使用模型-视图架构实现数据与显示的分离: - **模型 (Model)**:管理数据存储和访问逻辑 - **视图 (View)**:负责数据可视化 - **委托 (Delegate)**:控制数据渲染和编辑方式 ### 2. 必须重写的抽象方法 实现自定义表格模型必须重写以下三个核心方法: ```python from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex class CustomTableModel(QAbstractTableModel): def __init__(self, data): super().__init__() self._data = data # 存储数据(二维列表或DataFrame) # 1. 返回行数 def rowCount(self, parent=QModelIndex()): return len(self._data) if self._data else 0 # 2. 返回列数 def columnCount(self, parent=QModelIndex()): return len(self._data[0]) if self._data and self._data[0] else 0 # 3. 获取单元格数据 def data(self, index, role=Qt.DisplayRole): if not index.isValid() or role != Qt.DisplayRole: return None row, col = index.row(), index.column() return str(self._data[row][col]) ``` ## 二、完整示例代码 ### 1. 基础模型实现(含表头) ```python import sys import pandas as pd from PyQt5.QtWidgets import QApplication, QTableView, QMainWindow from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex class DataFrameModel(QAbstractTableModel): def __init__(self, data): super().__init__() self._data = data.values.tolist() self._columns = data.columns.tolist() self._index = data.index.tolist() def rowCount(self, parent=QModelIndex()): return len(self._data) def columnCount(self, parent=QModelIndex()): return len(self._columns) def data(self, index, role=Qt.DisplayRole): if not index.isValid() or role != Qt.DisplayRole: return None row, col = index.row(), index.column() return str(self._data[row][col]) # 添加表头支持 def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return str(self._columns[section]) elif orientation == Qt.Vertical: return str(self._index[section]) return None class MainWindow(QMainWindow): def __init__(self): super().__init__() # 创建示例数据 data = pd.DataFrame({ 'ID': [1, 2, 3], '名称': ['产品A', '产品B', '产品C'], '价格': [19.99, 29.99, 39.99], '库存': [100, 50, 75] }) # 创建模型和视图 model = DataFrameModel(data) self.tableView = QTableView() self.tableView.setModel(model) # 视图配置 self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.setCentralWidget(self.tableView) self.resize(600, 400) self.setWindowTitle("QAbstractTableModel 示例") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) ``` ### 2. 实现数据编辑功能 ```python class EditableTableModel(DataFrameModel): def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole and index.isValid(): row, col = index.row(), index.column() try: # 尝试转换数据类型 original_type = type(self._data[row][col]) if original_type == float: self._data[row][col] = float(value) elif original_type == int: self._data[row][col] = int(value) else: self._data[row][col] = value # 通知视图数据已更改 self.dataChanged.emit(index, index) return True except ValueError: return False return False def flags(self, index): # 设置单元格可编辑 return super().flags(index) | Qt.ItemIsEditable ``` ## 三、高级功能实现 ### 1. 自定义单元格渲染(进度条) ```python def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None row, col = index.row(), index.column() # 进度条渲染 if role == Qt.DecorationRole and col == 3: # 库存列 progress = self._data[row][col] / 100.0 return self.createProgressPixmap(progress, 100, 20) # 文本显示 if role == Qt.DisplayRole: return str(self._data[row][col]) return None def createProgressPixmap(self, progress, width, height): pixmap = QPixmap(width, height) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) painter.setPen(Qt.NoPen) # 背景 painter.setBrush(QBrush(QColor(200, 200, 200))) painter.drawRect(0, 0, width, height) # 进度条 painter.setBrush(QBrush(QColor(0, 150, 0))) painter.drawRect(0, 0, int(width * progress), height) painter.end() return pixmap ``` ### 2. 数据排序实现 ```python def sort(self, column, order): self.layoutAboutToBeChanged.emit() # 获取列数据 col_data = [row[column] for row in self._data] # 创建索引映射 indexes = list(range(len(self._data))) indexes.sort(key=lambda i: col_data[i], reverse=(order == Qt.DescendingOrder)) # 重新排序数据 self._data = [self._data[i] for i in indexes] self._index = [self._index[i] for i in indexes] self.layoutChanged.emit() self.dataChanged.emit( self.index(0, 0), self.index(self.rowCount()-1, self.columnCount()-1) ) ``` ### 3. 大数据分页处理 ```python class PagedTableModel(QAbstractTableModel): def __init__(self, data, page_size=50): super().__init__() self.full_data = data self.page_size = page_size self.current_page = 0 self.total_pages = (len(data) + page_size - 1) // page_size self._update_page_data() def _update_page_data(self): start = self.current_page * self.page_size end = start + self.page_size self.page_data = self.full_data[start:end] def rowCount(self, parent=QModelIndex()): return len(self.page_data) def columnCount(self, parent=QModelIndex()): return len(self.page_data[0]) if self.page_data else 0 def data(self, index, role=Qt.DisplayRole): if not index.isValid() or role != Qt.DisplayRole: return None row, col = index.row(), index.column() return str(self.page_data[row][col]) def goToPage(self, page): if 0 <= page < self.total_pages: self.beginResetModel() self.current_page = page self._update_page_data() self.endResetModel() return True return False ``` ## 四、最佳实践与性能优化 ### 1. 数据更新策略 | 操作类型 | 推荐方法 | 说明 | |---------|----------|------| | 单单元格更新 | `setData()` + `dataChanged.emit()` | 局部更新,效率高 | | 整行更新 | `dataChanged.emit()` 带行范围 | 减少信号发送次数 | | 批量插入 | `beginInsertRows()` + `endInsertRows()` | 通知视图结构变化 | | 批量删除 | `beginRemoveRows()` + `endRemoveRows()` | 通知视图结构变化 | | 全量更新 | `beginResetModel()` + `endResetModel()` | 重置整个模型 | ### 2. 性能优化技巧 1. **批量操作优化**: ```python # 批量插入100行 self.beginInsertRows(QModelIndex(), start_row, start_row+99) for i in range(100): self._data.insert(start_row+i, new_row_data) self.endInsertRows() ``` 2. **视图渲染优化**: ```python # 禁用视图刷新 self.tableView.setUpdatesEnabled(False) # 执行批量操作... # 启用视图刷新 self.tableView.setUpdatesEnabled(True) self.tableView.viewport().update() ``` 3. **延迟加载大数据**: ```python def fetchMore(self, index): if self.canFetchMore(index): items_to_add = min(100, len(self.full_data) - len(self._data)) self.beginInsertRows(QModelIndex(), len(self._data), len(self._data) + items_to_add - 1) self._data.extend(self.full_data[len(self._data):len(self._data)+items_to_add]) self.endInsertRows() def canFetchMore(self, index): return len(self._data) < len(self.full_data) ``` ## 五、常见问题解决 ### 1. 抽象方法未实现错误 当出现 `QAbstractTableModel.columnCount() is abstract and must be overridden` 错误时: - 确保实现了所有抽象方法:`rowCount()`, `columnCount()`, `data()` - 检查方法签名是否正确: ```python # 正确 def columnCount(self, parent=QModelIndex()): # 错误 - 缺少默认参数 def columnCount(self, parent): ``` ### 2. 性能问题处理 对于大型数据集: - 使用分页模型 (`PagedTableModel`) - 实现延迟加载 (`fetchMore/canFetchMore`) - 避免在 `data()` 方法中进行复杂计算 - 使用 `QSortFilterProxyModel` 进行高效过滤 ### 3. 数据同步问题 当外部数据源更新时: ```python def updateData(self, new_data): self.beginResetModel() self._data = new_data self.endResetModel() ``` 通过以上实现,您可以创建高效、灵活的表格应用,处理从简单到复杂的数据展示需求[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值