如何合并多个QPdfSearchModel并关联QListView实现PDF多关键词搜索?
合并多个QPdfSearchModel后QListView点击项无法联动PDFViewer的解决方法
问题背景
需要在QListView中展示PDF的多关键词搜索结果(同时匹配带空格和不带空格的关键词,如“ABC DEF”和“ABCDEF”),但QPdfSearchModel仅支持单个搜索词,于是自定义CombinedListModel合并多个搜索模型的结果。目前ListView能正常显示所有匹配项,但点击列表项后,PDFViewer无法定位到对应的搜索结果位置,关联完全失效。
问题原因
自定义的CombinedListModel仅重写了rowCount和基础的data方法(仅处理DisplayRole),但QPdfSearchModel的核心功能依赖多个未实现的方法和数据角色:
- 未处理
QPdfSearchModel.ResultRole等关键角色,无法传递搜索结果的位置信息给QPdfView - 未实现
resultsOnPage、setCurrentSearchResultIndex等核心方法,QPdfView无法正确识别和定位搜索结果 - 点击事件中的行号未映射到对应子模型的实际索引,导致调用
setCurrentSearchResultIndex时无效
解决方案
修改CombinedListModel,实现所有必要的虚函数和数据角色处理,同时调整行号到子模型的映射逻辑:
import sys from PySide6.QtCore import Qt, QModelIndex from PySide6.QtWidgets import QApplication, QMainWindow, QListView, QVBoxLayout, QWidget, QLineEdit from PySide6.QtPdf import QPdfDocument, QPdfSearchModel, QPdfSearchResult from PySide6.QtPdfWidgets import QPdfView from PySide6 import QtGui class CombinedListModel(QPdfSearchModel): def __init__(self, models, parent=None): super().__init__(parent) self.models = models # 监听子模型变化,同步更新合并模型 for model in self.models: model.rowsInserted.connect(self.layoutChanged.emit) model.rowsRemoved.connect(self.layoutChanged.emit) model.dataChanged.connect(self.dataChanged.emit) def mapRowToSubModel(self, row): """将合并后的全局行号映射到对应的子模型和子模型内的行号""" current_row = row for model in self.models: row_count = model.rowCount(QModelIndex()) if current_row < row_count: return model, current_row current_row -= row_count return None, -1 def rowCount(self, parent=QModelIndex()): if parent.isValid(): return 0 return sum(model.rowCount(QModelIndex()) for model in self.models) def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None model, sub_row = self.mapRowToSubModel(index.row()) if not model: return None sub_index = model.index(sub_row) # 处理搜索结果核心角色,确保QPdfView能获取位置信息 if role == QPdfSearchModel.ResultRole: return model.data(sub_index, role) elif role == Qt.DisplayRole: # 自定义显示文本,增加页面号提示 result = model.data(sub_index, QPdfSearchModel.ResultRole) return f"Page {result.page()+1}: {result.text()}" return model.data(sub_index, role) def resultsOnPage(self, page): """合并所有子模型的指定页面搜索结果""" all_results = [] for model in self.models: all_results.extend(model.resultsOnPage(page)) return all_results def setCurrentSearchResultIndex(self, row): """设置当前搜索结果索引,自动映射到子模型""" model, sub_row = self.mapRowToSubModel(row) if model: model.setCurrentSearchResultIndex(sub_row) self.currentResultIndexChanged.emit(self.index(row)) def currentResultIndex(self): """返回当前结果在合并模型中的全局索引""" for model in self.models: sub_index = model.currentResultIndex() if sub_index.isValid(): # 计算子模型索引对应的全局行号 offset = sum(m.rowCount(QModelIndex()) for m in self.models[:self.models.index(model)]) return self.index(offset + sub_index.row()) return QModelIndex() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("PDF Search Viewer") self.search_bar = QLineEdit(self) self.search_bar.textChanged.connect(self.on_search_text_changed) self.list_view = QListView(self) self.list_view.clicked.connect(self.on_list_view_clicked) layout = QVBoxLayout() layout.addWidget(self.search_bar) layout.addWidget(self.list_view) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) self.pdf_document = QPdfDocument(self) self.pdf_document.load(r"C:\temp\OCR_highlighted.pdf") # 替换为你的PDF文件路径 self.pdf_viewer = QPdfView(self) self.pdf_viewer.setDocument(self.pdf_document) self.pdf_viewer.setPageMode(QPdfView.PageMode.MultiPage) layout.addWidget(self.pdf_viewer) self.search_bar.setText("ABCDEF;ABC DEF") def on_search_text_changed(self, text): # 拆分并过滤空关键词 search_terms = [term.strip() for term in text.split(';') if term.strip()] self.search_models = [] for term in search_terms: model = QPdfSearchModel(self) model.setDocument(self.pdf_document) model.setSearchString(term) self.search_models.append(model) # 统计关键词匹配数量 result_count = sum(len(model.resultsOnPage(i)) for i in range(self.pdf_document.pageCount())) print(f"关键词 '{term}' 找到 {result_count} 个结果") self.combined_model = CombinedListModel(self.search_models) self.list_view.setModel(self.combined_model) self.pdf_viewer.setSearchModel(self.combined_model) def on_list_view_clicked(self, index): # 委托合并模型处理索引映射 self.combined_model.setCurrentSearchResultIndex(index.row()) self.pdf_viewer.setCurrentSearchResultIndex(index.row()) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())
关键修改点说明
mapRowToSubModel方法:核心索引映射工具,解决全局行号到子模型行号的转换问题。- 扩展
data方法:处理QPdfSearchModel.ResultRole角色,确保QPdfView能获取搜索结果的位置坐标;同时优化了显示文本,增加页面号提示。 - 实现核心功能方法:重写
resultsOnPage、setCurrentSearchResultIndex、currentResultIndex,让QPdfView能正常调用搜索模型的全部功能。 - 子模型变化监听:同步子模型的行变更和数据变更信号,确保合并模型的结果实时更新。
- 点击事件优化:直接调用合并模型的索引设置方法,无需外部手动计算映射关系。
内容的提问来源于stack exchange,提问作者Robindp
相关产品推荐
相关产品推荐

