带防抖的QCompleter导航时高亮项自动选中问题求助
PyQt5 Typeahead组件箭头导航自动选中问题解决
问题描述
我正在开发一个基于PyQt5 QLineEdit和QCompleter的防抖Typeahead组件,用于从数据库获取数据。当前遇到的问题是:用向下箭头导航查询结果时,松开箭头键后高亮的结果会被自动选中。查阅QCompleter和其popup对应的QAbstractItemView文档,文档说明Ctrl+箭头键仅切换当前项但不选中,但实际按住Ctrl键导航时行为并无差异。
解决方案
问题根源是QCompleter默认的PopupCompletion模式下,箭头导航会触发activated信号,导致文本被自动填充。我们需要拦截popup的键盘事件,让箭头键仅移动高亮项,仅在按下Enter或点击鼠标时才完成选中操作。
修改后的完整代码
from PyQt5.QtWidgets import QLineEdit, QCompleter, QAbstractItemView from PyQt5.QtCore import QTimer, QStringListModel, Qt from utk_apis.sql_engine import SQLEngine class Typeahead(QLineEdit): def __init__(self, parent=None): super(Typeahead, self).__init__(parent) self.results = [] self.selected_result = None self.engine = None self.current_index = -1 self.ignore_text_change = False self.completer = QCompleter(self) self.completer.setWidget(self) self.completer.setCompletionMode(QCompleter.PopupCompletion) self.completer.popup().setSelectionMode(QAbstractItemView.SingleSelection) self.setCompleter(self.completer) self.completer.popup().setAlternatingRowColors(True) # 重写popup的键盘事件 self.popup = self.completer.popup() self.popup.keyPressEvent = self.popup_key_press_event # 连接鼠标点击选中事件 self.popup.clicked.connect(self.on_popup_clicked) # 防抖计时器 self.debounce_timer = QTimer(self) self.debounce_timer.setSingleShot(True) self.debounce_timer.setInterval(300) self.debounce_timer.timeout.connect(self.emit_text_changed) self.textChanged.connect(self.start_debounce) def start_debounce(self): self.debounce_timer.start() def emit_text_changed(self): print(f'Emitting text changed event: {self.text()} Ignore text change: {self.ignore_text_change}') if self.ignore_text_change is True: self.ignore_text_change = False return if self.engine is None: self.engine = SQLEngine(env=self.property("env")) data = self.engine.run_query(self._build_query(), results_as_dict=True) self.results = [ { "text": f"{row['primary_key_1']} ({row['primary_key_2']}, {row['primary_key_3']})", "primary_key_1": row["primary_key_1"], "primary_key_2": row["primary_key_2"], "primary_key_3": row["primary_key_3"], } for row in data ] self.update_completer() def update_completer(self): completer_model = QStringListModel([result["text"] for result in self.results]) self.completer.setModel(completer_model) longest_string = max([len(result["text"]) for result in self.results], default=0) self.completer.popup().setMinimumWidth(longest_string * 15) if self.results: self.completer.complete() def on_completer_activated(self, text): selected_item = next((result for result in self.results if result["text"] == text), None) if selected_item: self.ignore_text_change = True self.setText(selected_item["text"]) def _build_query(self): query = f"SELECT {','.join(self.property('primary_keys'))} FROM {self.property('targetTable')} WHERE {self.property('targetField')} LIKE '%{self.text()}%'" return query def popup_key_press_event(self, event): key = event.key() # 处理箭头键,仅移动高亮不触发选中 if key in (Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right): QAbstractItemView.keyPressEvent(self.popup, event) event.accept() # 处理Enter键,触发选中逻辑 elif key == Qt.Key_Return or key == Qt.Key_Enter: index = self.popup.currentIndex() if index.isValid(): text = index.data(Qt.DisplayRole) self.on_completer_activated(text) self.popup.hide() event.accept() # 其他事件交给默认处理 else: QAbstractItemView.keyPressEvent(self.popup, event) def on_popup_clicked(self, index): text = index.data(Qt.DisplayRole) self.on_completer_activated(text)
关键修改点
- 重写Popup键盘事件:拦截箭头键事件,让popup仅处理高亮移动,不触发
activated信号;仅在按下Enter时手动触发选中逻辑。 - 连接鼠标点击事件:确保用户点击popup项时仍能正常选中。
- 移除默认
activated信号连接:避免箭头导航时自动触发文本填充。 - 添加
ignore_text_change标记:设置文本时阻止防抖计时器触发,避免重复查询。
内容的提问来源于stack exchange,提问作者J.spenc
相关产品推荐
相关产品推荐

