QCompleter自定义完成规则

我正在使用Qt4.6,我有一个QCompleBox在其中。

通常的function是提供完成提示(这些可以在下拉而不是内联 – 这是我的用法)基于前缀。 例如,给出

chicken soup chilli peppers grilled chicken 

进入“ch”将匹配“鸡汤”和“辣椒”,而不是“烤鸡”。

我想要的是能够进入“ch”,并匹配所有的鸡,更具体地说,“鸡”,匹配“鸡汤”和“烤鸡”。
我也希望能够将“chs”这样的标签分配给“鸡汤”,以产生不仅仅是文本内容的另一个匹配。 我可以处理algorithm,但是,

QCompleter的哪些function需要重写?
我不确定我应该在哪里寻找…

基于@ j3frea的build议,这里是一个工作的例子(使用PySide )。 似乎每次调用splitPath都需要设置模型(在setModel中设置一次代理不起作用)。

 combobox.setEditable(True) combobox.setInsertPolicy(QComboBox.NoInsert) class CustomQCompleter(QCompleter): def __init__(self, parent=None): super(CustomQCompleter, self).__init__(parent) self.local_completion_prefix = "" self.source_model = None def setModel(self, model): self.source_model = model super(CustomQCompleter, self).setModel(self.source_model) def updateModel(self): local_completion_prefix = self.local_completion_prefix class InnerProxyModel(QSortFilterProxyModel): def filterAcceptsRow(self, sourceRow, sourceParent): index0 = self.sourceModel().index(sourceRow, 0, sourceParent) return local_completion_prefix.lower() in self.sourceModel().data(index0).lower() proxy_model = InnerProxyModel() proxy_model.setSourceModel(self.source_model) super(CustomQCompleter, self).setModel(proxy_model) def splitPath(self, path): self.local_completion_prefix = path self.updateModel() return "" completer = CustomQCompleter(combobox) completer.setCompletionMode(QCompleter.PopupCompletion) completer.setModel(combobox.model()) combobox.setCompleter(completer) 

基于@Bruno的答案,我使用标准的QSortFilterProxyModel函数setFilterRegExp来更改searchstring。 这样就不需要子分类了。

它还修复了@布鲁诺的答案中的一个错误,这使得在inputstring在input时被退格改正后,这些build议消失了。

 class CustomQCompleter(QtGui.QCompleter): """ adapted from: http://stackoverflow.com/a/7767999/2156909 """ def __init__(self, *args):#parent=None): super(CustomQCompleter, self).__init__(*args) self.local_completion_prefix = "" self.source_model = None self.filterProxyModel = QtGui.QSortFilterProxyModel(self) self.usingOriginalModel = False def setModel(self, model): self.source_model = model self.filterProxyModel = QtGui.QSortFilterProxyModel(self) self.filterProxyModel.setSourceModel(self.source_model) super(CustomQCompleter, self).setModel(self.filterProxyModel) self.usingOriginalModel = True def updateModel(self): if not self.usingOriginalModel: self.filterProxyModel.setSourceModel(self.source_model) pattern = QtCore.QRegExp(self.local_completion_prefix, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString) self.filterProxyModel.setFilterRegExp(pattern) def splitPath(self, path): self.local_completion_prefix = path self.updateModel() if self.filterProxyModel.rowCount() == 0: self.usingOriginalModel = False self.filterProxyModel.setSourceModel(QtGui.QStringListModel([path])) return [path] return [] class AutoCompleteComboBox(QtGui.QComboBox): def __init__(self, *args, **kwargs): super(AutoCompleteComboBox, self).__init__(*args, **kwargs) self.setEditable(True) self.setInsertPolicy(self.NoInsert) self.comp = CustomQCompleter(self) self.comp.setCompletionMode(QtGui.QCompleter.PopupCompletion) self.setCompleter(self.comp)# self.setModel(["Lola", "Lila", "Cola", 'Lothian']) def setModel(self, strList): self.clear() self.insertItems(0, strList) self.comp.setModel(self.model()) def focusInEvent(self, event): self.clearEditText() super(AutoCompleteComboBox, self).focusInEvent(event) def keyPressEvent(self, event): key = event.key() if key == 16777220: # Enter (if event.key() == QtCore.Qt.Key_Enter) does not work # for some reason # make sure that the completer does not set the # currentText of the combobox to "" when pressing enter text = self.currentText() self.setCompleter(None) self.setEditText(text) self.setCompleter(self.comp) return super(AutoCompleteComboBox, self).keyPressEvent(event) 

更新:

我想我以前的解决scheme工作,直到combobox中的string匹配没有任何列表项目。 然后QFilterProxyModel是空的,这又重新设置了combobox的text 。 我试图find一个优雅的解决这个问题,但我遇到了问题(引用删除的对象错误),每当我试图改变self.filterProxyModel上的self.filterProxyModel 。 所以现在黑客是每次更新self.filterProxyModel的模式更新时设置模型。 而且,只要模式不再匹配模型中的任何内容,就给它一个只包含当前文本的新模型(在splitPath称为path )。 这可能会导致性能问题,如果你正在处理非常大的模型,但对我来说,黑客工作得很好。

更新2:

我意识到这仍然不是完美的方法,因为如果在combobox中input新的string,并且用户按下Enter键,combobox将再次被清除。 input新string的唯一方法是在input后从下拉菜单中select它。

更新3:

现在input作品。 当用户按下回车键时,我通过简单地closurescombobox文本的复位工作。 但是我把它放回去了,这样就完成了function。 如果用户决定进行进一步的编辑。

使用filterMode : Qt::MatchFlags属性。 该属性保存如何执行过滤。 如果filterMode设置为Qt::MatchStartsWith ,则只会显示以键入字符开头的条目。 Qt::MatchContains将显示包含input字符的条目,而Qt::MatchEndsWithinput字符结尾。 目前只有这三种模式被执行 。 将filterMode设置为任何其他的Qt::MatchFlag将会发出警告,并且不会执行任何操作。 默认模式是Qt::MatchStartsWith

这个属性是在Qt 5.2中引入的。

访问function:

 Qt::MatchFlags filterMode() const void setFilterMode(Qt::MatchFlags filterMode) 

感谢Thorbjørn,我实际上通过inheritanceQSortFilterProxyModel解决了这个问题。

filterAcceptsRow方法必须被覆盖,然后根据是否需要显示该项目而返回true或false。

这个解决scheme的问题是,它只隐藏列表中的项目,所以你永远不能重新排列(这是我想要给予某些项目优先)。

[编辑]
我想我会把这个解决scheme,因为它基本上是我最终做的(因为上面的解决scheme是不够的)。 我用http://www.cppblog.com/biao/archive/2009/10/31/99873.html

 #include "locationlineedit.h" #include <QKeyEvent> #include <QtGui/QListView> #include <QtGui/QStringListModel> #include <QDebug> LocationLineEdit::LocationLineEdit(QStringList *words, QHash<QString, int> *hash, QVector<int> *bookChapterRange, int maxVisibleRows, QWidget *parent) : QLineEdit(parent), words(**&words), hash(**&hash) { listView = new QListView(this); model = new QStringListModel(this); listView->setWindowFlags(Qt::ToolTip); connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(setCompleter(const QString &))); connect(listView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(completeText(const QModelIndex &))); this->bookChapterRange = new QVector<int>; this->bookChapterRange = bookChapterRange; this->maxVisibleRows = &maxVisibleRows; listView->setModel(model); } void LocationLineEdit::focusOutEvent(QFocusEvent *e) { listView->hide(); QLineEdit::focusOutEvent(e); } void LocationLineEdit::keyPressEvent(QKeyEvent *e) { int key = e->key(); if (!listView->isHidden()) { int count = listView->model()->rowCount(); QModelIndex currentIndex = listView->currentIndex(); if (key == Qt::Key_Down || key == Qt::Key_Up) { int row = currentIndex.row(); switch(key) { case Qt::Key_Down: if (++row >= count) row = 0; break; case Qt::Key_Up: if (--row < 0) row = count - 1; break; } if (listView->isEnabled()) { QModelIndex index = listView->model()->index(row, 0); listView->setCurrentIndex(index); } } else if ((Qt::Key_Enter == key || Qt::Key_Return == key || Qt::Key_Space == key) && listView->isEnabled()) { if (currentIndex.isValid()) { QString text = currentIndex.data().toString(); setText(text + " "); listView->hide(); setCompleter(this->text()); } else if (this->text().length() > 1) { QString text = model->stringList().at(0); setText(text + " "); listView->hide(); setCompleter(this->text()); } else { QLineEdit::keyPressEvent(e); } } else if (Qt::Key_Escape == key) { listView->hide(); } else { listView->hide(); QLineEdit::keyPressEvent(e); } } else { if (key == Qt::Key_Down || key == Qt::Key_Up) { setCompleter(this->text()); if (!listView->isHidden()) { int row; switch(key) { case Qt::Key_Down: row = 0; break; case Qt::Key_Up: row = listView->model()->rowCount() - 1; break; } if (listView->isEnabled()) { QModelIndex index = listView->model()->index(row, 0); listView->setCurrentIndex(index); } } } else { QLineEdit::keyPressEvent(e); } } } void LocationLineEdit::setCompleter(const QString &text) { if (text.isEmpty()) { listView->hide(); return; } /* This is there in the original but it seems to be bad for performance (keeping listview hidden unnecessarily - havn't thought about it properly though) */ // if ((text.length() > 1) && (!listView->isHidden())) // { // return; // } model->setStringList(filteredModelFromText(text)); if (model->rowCount() == 0) { return; } int maxVisibleRows = 10; // Position the text edit QPoint p(0, height()); int x = mapToGlobal(p).x(); int y = mapToGlobal(p).y() + 1; listView->move(x, y); listView->setMinimumWidth(width()); listView->setMaximumWidth(width()); if (model->rowCount() > maxVisibleRows) { listView->setFixedHeight(maxVisibleRows * (listView->fontMetrics().height() + 2) + 2); } else { listView->setFixedHeight(model->rowCount() * (listView->fontMetrics().height() + 2) + 2); } listView->show(); } //Basically just a slot to connect to the listView's click event void LocationLineEdit::completeText(const QModelIndex &index) { QString text = index.data().toString(); setText(text); listView->hide(); } QStringList LocationLineEdit::filteredModelFromText(const QString &text) { QStringList newFilteredModel; //do whatever you like and fill the filteredModel return newFilteredModel; } 

不幸的是,目前的答案是不可能的。 要做到这一点,您需要在自己的应用程序中复制QCompleter的大部分function(Qt Creator将其作为定位器,如果您感兴趣,请参阅src/plugins/locator/locatorwidget.cpp )。

同时你可以在QTBUG-7830上进行投票,这样可以自定义完成项目匹配的方式,就像你想的那样。 但不要屏住呼吸。

如上所述,您可以通过提供自定义angular色并完成该angular色来绕过QTBUG-7830。 在该angular色的处理程序中,可以让QCompleter知道该项目在那里。 如果你也在你的SortFilterProxy模型中重写filterAcceptsRow,这将工作。