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

PyQt5应用修改关联QComboBox的QTableView条目时崩溃

问题描述

开发基于PyQt5的应用,采用内存SQLite数据库,QTableView关联QSortFilterProxyModel,product列通过QComboBox筛选。编辑表格product列值时,通过pyqtSignal更新QComboBox选项,确保包含该列唯一内容。

未使用下拉框筛选时编辑正常,但通过ComboBox筛选某产品后修改其名称,程序无报错挂起崩溃。尝试过setUpdatesEnabled(False)、blockSignals、qWait等方法均未解决。

问题根源

崩溃源于递归循环的筛选与模型更新:

  • 编辑筛选后的product值,源模型dataChanged触发update_combo_items更新ComboBox选项
  • 原筛选值已不存在于数据库,ComboBox自动选中第一个匹配项(如"All"),触发currentIndexChanged信号
  • 信号触发filter_table_view重置筛选条件,代理模型重新计算,再次触发相关信号,循环往复导致栈溢出

另外,MultiFilterProxyModel中regex.match()是从头匹配,不符合常规筛选的包含匹配逻辑,但不是崩溃直接原因。

修复方案
  1. 控制ComboBox选中逻辑:更新选项时,若原筛选值不存在,主动切换到"All",手动触发筛选,不依赖ComboBox信号
  2. 替换匹配方式:用regex.search()替代match(),支持包含匹配
  3. 优化信号触发:更新ComboBox时避免不必要的信号递归
修复后完整代码
import sys
import re
from functools import partial

from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QComboBox, QTableView, QVBoxLayout, QWidget
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
from PyQt5.QtCore import Qt, QSortFilterProxyModel

class AppDemo(QMainWindow):
    def __init__(self):
        super().__init__()

        # 主部件与布局
        self.main_widget = QWidget()
        self.setCentralWidget(self.main_widget)
        self.layout = QVBoxLayout()
        self.main_widget.setLayout(self.layout)

        # ComboBox初始化
        self.combo_box = QComboBox()
        self.layout.addWidget(self.combo_box)

        # TableView初始化
        self.table_view = QTableView()
        self.layout.addWidget(self.table_view)

        # 数据库设置
        self.setup_database()

        # 模型与代理模型设置
        self.model = QSqlTableModel()
        self.model.setTable("MainTable")
        self.model.select()
        
        self.proxymodel = MultiFilterProxyModel()
        self.proxymodel.setSourceModel(self.model)
        self.proxymodel.setDynamicSortFilter(True)
        self.table_view.setModel(self.proxymodel)
        self.table_view.setSortingEnabled(True)
        
        self.setFixedWidth(1000)
        self.setFixedHeight(300)

        # 初始化ComboBox选项
        header_name = [self.proxymodel.headerData(x, Qt.Horizontal) for x in range(self.proxymodel.columnCount())]
        self.product_col_idx = header_name.index("product")
        self.update_combo_items()
        
        # 信号连接
        self.combo_box.currentIndexChanged[str].connect(self.filter_table_view)
        self.model.dataChanged.connect(self.on_data_changed)

    def setup_database(self):
        # 内存SQLite数据库
        self.db = QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName(":memory:")
        if not self.db.open():
            print("无法打开数据源")

        query = self.db.exec()
        # 创建表
        query.exec(
            """
            CREATE TABLE MainTable (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT,
                age INTEGER,
                product TEXT,
                price REAL
            )
            """
        )
        # 插入测试数据
        query.exec("INSERT INTO MainTable (name, age, product, price) VALUES ('John Doe', 30, 'Product1', 9.99)")
        query.exec("INSERT INTO MainTable (name, age, product, price) VALUES ('Jane Smith', 28, 'Product2', 19.99)")
        query.exec("INSERT INTO MainTable (name, age, product, price) VALUES ('Dave Ryan', 55, 'Product1', 11.0)")
    
    def get_product_list(self):
        # 获取product列唯一值列表
        product_list = []
        for i in range(self.model.rowCount()):
            value = self.model.index(i, self.product_col_idx).data()
            if value:
                product_list.append(value)
        # 去重并添加空值和All选项
        unique_products = list(set(product_list))
        if "" in product_list:
            unique_products.append("")
        unique_products.append("All")
        return sorted(unique_products)
    
    def update_combo_items(self):
        # 更新ComboBox选项,避免递归触发
        current_text = self.combo_box.currentText()
        product_list = self.get_product_list()
        
        self.combo_box.blockSignals(True)
        self.combo_box.clear()
        self.combo_box.addItems(product_list)
        
        # 若原文本不存在,切换到All
        if current_text not in product_list:
            current_text = "All"
        self.combo_box.setCurrentText(current_text)
        self.combo_box.blockSignals(False)
        
        # 手动更新筛选,不依赖ComboBox信号
        self.filter_table_view(current_text)
    
    def filter_table_view(self, text):
        # 表格筛选逻辑
        if text == "All" or text == "":
            self.proxymodel.setFilterByColumn(self.product_col_idx, "")
        else:
            self.proxymodel.setFilterByColumn(self.product_col_idx, text)
    
    def on_data_changed(self, indx1, indx2):
        # 仅当修改的是product列时更新ComboBox
        if indx2.column() == self.product_col_idx:
            self.update_combo_items()

class MultiFilterMode:
    AND = 0
    OR = 1

class MultiFilterProxyModel(QSortFilterProxyModel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.filters = {}
        self.multi_filter_mode = MultiFilterMode.AND

    def setFilterByColumn(self, column, regex):
        if isinstance(regex, str):
            # 使用search模式,支持包含匹配
            regex = re.compile(regex, re.IGNORECASE)
        self.filters[column] = regex
        self.invalidateFilter()

    def clearFilter(self, column):
        if column in self.filters:
            del self.filters[column]
            self.invalidateFilter()

    def clearFilters(self):
        self.filters.clear()
        self.invalidateFilter()

    def filterAcceptsRow(self, source_row, source_parent):
        if not self.filters:
            return True

        results = []
        for key, regex in self.filters.items():
            index = self.sourceModel().index(source_row, key, source_parent)
            text = self.sourceModel().data(index, Qt.DisplayRole) or ""
            # 用search替代match,支持包含匹配
            results.append(regex.search(text) is not None)

        if self.multi_filter_mode == MultiFilterMode.OR:
            return any(results)
        return all(results)

if __name__ == "__main__": 
    app = QApplication(sys.argv)
    demo = AppDemo()
    demo.show()
    sys.exit(app.exec_())

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 00:44:51