我正在使用Qt4.6并且我有一个带有QCompleter的QComboBox。
通常的功能是根据前缀提供完成提示(这些提示可以是下拉列表而不是内联 - 这是我的用法)。例如,给定
chicken soup
chilli peppers
grilled chicken
输入ch
将匹配chicken soup
和chilli peppers
,但不匹配grilled chicken
。
我想要的是能够输入ch
并匹配所有这些,或者更具体地说,chicken
并匹配chicken soup
和grilled chicken
。
我还希望能够将chs
这样的标记分配给chicken soup
,以产生另一个不仅仅在文本内容上的匹配。我可以处理算法,但是,
我需要覆盖哪些QCompleter的功能?
我不确定我应该在哪里看......
答案 0 :(得分:9)
根据@ j3frea建议,这是一个工作示例(使用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)
答案 1 :(得分:7)
使用filterMode : Qt::MatchFlags
属性。此属性保存过滤的执行方式。如果filterMode设置为Qt::MatchStartsWith
,则仅显示以键入字符开头的条目。 Qt::MatchContains
将显示包含键入字符的条目,Qt::MatchEndsWith
将显示以键入字符结尾的条目。 目前,只实施了这三种模式。将filterMode设置为任何其他Qt::MatchFlag
将发出警告,并且不会执行任何操作。默认模式为Qt::MatchStartsWith
。
此属性是在Qt 5.2中引入的。
访问功能:
Qt::MatchFlags filterMode() const
void setFilterMode(Qt::MatchFlags filterMode)
答案 2 :(得分:6)
基于@Bruno的答案,我使用标准QSortFilterProxyModel
函数setFilterRegExp
来更改搜索字符串。这样就不需要进行子分类了。
它还解决了@Bruno答案中的一个错误,一旦输入字符串在输入时用退格键更正,这些错误就会使建议消失。
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)
更新
我认为我之前的解决方案有效,直到组合框中的字符串与列表项都没有匹配。然后QFilterProxyModel
为空,这又重置了组合框的text
。我试图找到一个优雅的解决方案来解决这个问题,但每当我试图改变self.filterProxyModel
上的内容时,我都会遇到问题(引用已删除的对象错误)。所以现在黑客是在模式更新时每次都设置self.filterProxyModel
的模型。每当模式不再与模型中的任何内容匹配时,给它一个仅包含当前文本的新模型(在path
中也称为splitPath
)。如果你正在处理非常大的模型,这可能会导致性能问题,但对我来说黑客工作得很好。
更新2:
我意识到这仍然不是一个完美的方法,因为如果在组合框中键入一个新字符串并且用户按下回车键,组合框将再次被清除。输入新字符串的唯一方法是在输入后从下拉菜单中选择它。
更新3:
现在也输入作品。当用户按下回车键时,我只需将其取下即可完成组合框文本的重置。但我把它重新放入,以便完成功能保持不变。如果用户决定进行进一步编辑。
答案 3 :(得分:2)
感谢Thorbjørn,
我确实通过继承QSortFilterProxyModel
来解决问题。
必须覆盖filterAcceptsRow
方法,然后根据您是否希望显示该项目,只返回true或false。
此解决方案的问题在于它只隐藏列表中的项目,因此您永远不能重新排列它们(这是我想要优先处理某些项目的目的)。
[编辑]
我以为我会把它扔进解决方案,因为它基本上是我最终做的(因为上面的解决方案是不够的)。我使用了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;
}
答案 4 :(得分:1)
不幸的是,目前答案是不可能的。为此,您需要在自己的应用程序中复制QCompleter的大部分功能(Qt Creator为其定位器执行此操作,如果您感兴趣,请参阅src/plugins/locator/locatorwidget.cpp
了解魔法。)
与此同时,您可以对QTBUG-7830进行投票,这样就可以根据需要自定义完成项目的匹配方式。但是不要屏住呼吸。
答案 5 :(得分:0)
通过提供自定义角色并完成该角色,您可以如上所述绕过QTBUG-7830。在该角色的处理程序中,您可以使用QCompleter知道该项目在那里。如果您还在SortFilterProxy模型中覆盖filterAcceptsRow,这将有效。