Пользовательские правила завершения QCompleter - PullRequest
24 голосов
/ 26 февраля 2011

Я использую Qt4.6, и у меня есть QComboBox с QCompleter в нем.

Обычная функциональность заключается в предоставлении подсказок о завершении (они могут быть в раскрывающемся списке, а не в строке - это мое использование) на основе префикса.Например, если

chicken soup
chilli peppers
grilled chicken

ввести ch, то будет соответствовать chicken soup и chilli peppers, но не grilled chicken.

То, что я хочу, - возможность ввода chи сопоставьте их все или, более конкретно, chicken и сопоставьте chicken soup и grilled chicken.Я также хочу иметь возможность назначать теги, такие как chs - chicken soup, чтобы получить другое совпадение, которое относится не только к текстовому содержимому.Я могу обработать алгоритм, но,

Какие функции QCompleter мне нужно переопределить?Я не совсем уверен, где я должен искать ...

Ответы [ 6 ]

10 голосов
/ 14 октября 2011

На основе предложения @ 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)
7 голосов
/ 29 января 2015

Использовать 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)
6 голосов
/ 18 октября 2014

Опираясь на ответ @Bruno, я использую стандартную QSortFilterProxyModel функцию setFilterRegExp для изменения строки поиска. Таким образом, подклассификация не требуется.

Это также исправляет ошибку в ответе @ Bruno, из-за которой предложения по некоторым причинам исчезли после того, как во время ввода введенная строка была исправлена ​​с помощью backspace.

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:

Теперь ввод работает также. Я работал над сбросом текста в выпадающем списке, просто снимая его, когда пользователь нажимает ввод. Но я вернул его обратно, чтобы функциональность завершения осталась на месте. Если пользователь решает продолжить редактирование.

2 голосов
/ 26 марта 2011

Спасибо Торбьерн, Я действительно решил проблему, унаследовав от 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;
}
1 голос
/ 25 марта 2011

К сожалению, ответ в настоящее время, что это невозможно.Для этого вам нужно дублировать большую часть функциональности QCompleter в вашем собственном приложении (Qt Creator делает это для своего локатора, см. src/plugins/locator/locatorwidget.cpp для магии, если вам интересно).

Между тем, вы можете проголосовать за QTBUG-7830 , что позволяет настроить способ сопоставления элементов завершения так, как вы хотите.Но не задерживай дыхание на этом.

0 голосов
/ 30 января 2015

Вы можете обойти QTBUG-7830, как упомянуто выше, предоставив пользовательскую роль и выполнив эту роль. В обработчике этой роли вы можете сделать так, чтобы QCompleter знал, что этот элемент есть. Это будет работать, если вы также переопределите filterAcceptsRow в своей модели SortFilterProxy.

...