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

如何使带筛选的CheckableComboBox中筛选条目保持可勾选?

问题

我基于Qt实现了一个带筛选功能的CheckableComboBox,使用QSortFilterProxyModel作为筛选模型、QCompleter实现自动补全,通过on_completer_activated、setModel、setModelColumn函数维护筛选逻辑。但发现通过筛选选中的条目会变为不可勾选状态,请问如何修改代码使这些筛选后的条目保持可勾选?

以下是实现代码:

class CheckableComboBox(QComboBox):
    def __init__(self):
        super().__init__()
        self._changed = False
        self.view().pressed.connect(self.handleItemPressed)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setEditable(True)
        self.pFilterModel = QSortFilterProxyModel(self)
        self.pFilterModel.setSourceModel(self.model())
        self.completer = QCompleter(self.pFilterModel, self)
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.setCompleter(self.completer)
        self.lineEdit().textEdited.connect(self.pFilterModel.setFilterFixedString)
        self.completer.activated.connect(self.on_completer_activated)
    
    def setItemChecked(self, index, checked=False):
        item = self.model().item(index, self.modelColumn()) # QStandardItem object
        if checked:
            item.setCheckState(Qt.Checked)
        else:
            item.setCheckState(Qt.Unchecked)

    def handleItemPressed(self, index):
        item = self.model().itemFromIndex(index)

        if item.checkState() == Qt.Checked:
            item.setCheckState(Qt.Unchecked)
        else:
            item.setCheckState(Qt.Checked)
        self._changed = True
   
    def hidePopup(self):
        if not self._changed:
           super().hidePopup()
        self._changed = False

    def itemChecked(self, index):
        item = self.model().item(index, self.modelColumn())
        return item.checkState() == Qt.Checked

    def on_completer_activated(self, text):
        if text:
            index = self.findText(text)
            self.setCurrentIndex(index)
            self.activated[str].emit(self.itemText(index))
    
    def setModel(self, model):
        super(CheckableComboBox, self).setModel(model)
        self.pFilterModel.setSourceModel(model)
        self.completer.setModel(self.pFilterModel)

    def setModelColumn(self, column):
        self.completer.setCompletionColumn(column)
        self.pFilterModel.setFilterKeyColumn(column)
        super(CheckableComboBox, self).setModelColumn(column)    

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(500, 150)
        mainLayout = QVBoxLayout()
        self.combo = CheckableComboBox()
        mainLayout.addWidget(self.combo)

        for i in range(7):
            self.combo.addItem('Item {0}'.format(str(i)))
            self.combo.setItemChecked(i, False)     
            self.setLayout(mainLayout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyleSheet('QWidget {font-size: 40px;}')

    myApp = MyApp()
    myApp.show()
    app.exit(app.exec_())

问题截图:
筛选后条目不可勾选的界面截图


解决方案

核心问题是筛选后的条目属于代理模型(QSortFilterProxyModel),但原代码直接用原模型索引操作条目,导致无法正确关联筛选后的条目状态。需要做以下关键修改:

修改后的完整代码

from PyQt5.QtWidgets import (QComboBox, QSortFilterProxyModel, QCompleter, 
                             QWidget, QVBoxLayout, QApplication, QStandardItemModel)
from PyQt5.QtCore import Qt, QModelIndex
import sys

class CheckableComboBox(QComboBox):
    def __init__(self):
        super().__init__()
        self._changed = False
        self.setFocusPolicy(Qt.StrongFocus)
        self.setEditable(True)
        
        # 初始化代理模型和自动补全
        self.pFilterModel = QSortFilterProxyModel(self)
        self.completer = QCompleter(self.pFilterModel, self)
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.setCompleter(self.completer)
        
        # 关联信号
        self.lineEdit().textEdited.connect(self.pFilterModel.setFilterFixedString)
        self.completer.activated.connect(self.on_completer_activated)
        # 监听补全弹窗的条目点击事件
        self.completer.popup().pressed.connect(self.handleCompleterItemPressed)
        
        # 让ComboBox下拉视图使用代理模型
        self.setModel(self.pFilterModel)

    def setItemChecked(self, index, checked=False):
        # index为原模型索引,转换后操作条目
        source_model = self.pFilterModel.sourceModel()
        item = source_model.item(index, self.modelColumn())
        item.setCheckState(Qt.Checked if checked else Qt.Unchecked)

    def handleCompleterItemPressed(self, proxy_index):
        # 将代理模型索引转换为原模型索引
        source_index = self.pFilterModel.mapToSource(proxy_index)
        item = self.pFilterModel.sourceModel().itemFromIndex(source_index)
        
        # 切换勾选状态
        item.setCheckState(Qt.Unchecked if item.checkState() == Qt.Checked else Qt.Checked)
        self._changed = True
        # 关闭补全弹窗
        self.completer.popup().hide()
   
    def hidePopup(self):
        if not self._changed:
           super().hidePopup()
        self._changed = False

    def itemChecked(self, index):
        # 检查原模型条目的勾选状态
        source_model = self.pFilterModel.sourceModel()
        item = source_model.item(index, self.modelColumn())
        return item.checkState() == Qt.Checked

    def on_completer_activated(self, text):
        if text:
            # 找到代理模型中的条目,转换为原模型索引后设置当前项
            matches = self.pFilterModel.match(self.pFilterModel.index(0, self.modelColumn()), Qt.DisplayRole, text)
            if matches:
                proxy_index = matches[0]
                source_index = self.pFilterModel.mapToSource(proxy_index)
                self.setCurrentIndex(self.pFilterModel.mapFromSource(source_index).row())
                self.activated[str].emit(self.itemText(self.pFilterModel.mapFromSource(source_index).row()))
    
    def setModel(self, model):
        # 确保代理模型关联原模型,ComboBox使用代理模型
        self.pFilterModel.setSourceModel(model)
        super(CheckableComboBox, self).setModel(self.pFilterModel)
        self.completer.setModel(self.pFilterModel)

    def setModelColumn(self, column):
        self.completer.setCompletionColumn(column)
        self.pFilterModel.setFilterKeyColumn(column)
        super(CheckableComboBox, self).setModelColumn(column)    

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(500, 150)
        mainLayout = QVBoxLayout()
        self.combo = CheckableComboBox()
        mainLayout.addWidget(self.combo)
        
        # 显式创建可勾选的条目模型
        model = QStandardItemModel()
        for i in range(7):
            item = QStandardItem(f'Item {i}')
            item.setCheckable(True)
            item.setCheckState(Qt.Unchecked)
            model.appendRow(item)
        self.combo.setModel(model)
        
        self.setLayout(mainLayout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyleSheet('QWidget {font-size: 40px;}')

    myApp = MyApp()
    myApp.show()
    sys.exit(app.exec_())

关键修改说明

  1. 索引转换处理:使用mapToSource将代理模型的索引转换为原模型索引,确保操作的是正确的条目
  2. 监听补全弹窗事件:替换原self.view().pressed为self.completer.popup().pressed,因为筛选后的条目显示在补全弹窗中
  3. ComboBox使用代理模型:初始化时设置self.setModel(self.pFilterModel),让下拉视图直接显示筛选后的内容
  4. 显式创建可勾选模型:在MyApp中用QStandardItemModel创建条目,确保每个条目默认开启可勾选属性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 21:50:20