如何使带筛选的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_())
关键修改说明
- 索引转换处理:使用
mapToSource将代理模型的索引转换为原模型索引,确保操作的是正确的条目 - 监听补全弹窗事件:替换原
self.view().pressed为self.completer.popup().pressed,因为筛选后的条目显示在补全弹窗中 - ComboBox使用代理模型:初始化时设置
self.setModel(self.pFilterModel),让下拉视图直接显示筛选后的内容 - 显式创建可勾选模型:在MyApp中用QStandardItemModel创建条目,确保每个条目默认开启可勾选属性
内容的提问来源于stack exchange,提问作者user13892757
相关产品推荐
相关产品推荐

