qt qt4 qt4.6 qcombobox qcompleter

qt - Reglas de finalización personalizadas de QCompleter



qt4 qt4.6 (6)

Estoy usando Qt4.6 y tengo un QComboBox con un QCompleter.

La funcionalidad habitual es proporcionar pistas de finalización (pueden ser en un menú desplegable en lugar de en línea, que es mi uso) en función de un prefijo. Por ejemplo, dado

chicken soup chilli peppers grilled chicken

Al entrar en ch coincidiría con chicken soup y los chilli peppers pero no grilled chicken .

Lo que quiero es poder ingresar y combinar todas ellas o, más específicamente, chicken y chicken soup y grilled chicken .
También deseo poder asignar una etiqueta como chs a chicken soup para producir otra coincidencia que no esté solo en el contenido del texto. Puedo manejar el algoritmo pero,

¿Cuál de las funciones de QCompleter necesito anular?
No estoy seguro de dónde debería estar mirando ...


Basado en la sugerencia de @ j3frea, aquí hay un ejemplo de trabajo (usando PySide ). Parece que el modelo debe establecerse cada vez que se splitPath (configurar el proxy una vez en setModel no funciona).

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)


Gracias Thorbjørn, en realidad QSortFilterProxyModel el problema heredando de QSortFilterProxyModel .

El método filterAcceptsRow se debe sobrescribir y, a continuación, debe devolver verdadero o falso según si desea que se muestre ese elemento.

El problema con esta solución es que solo oculta elementos en una lista y, por lo tanto, nunca puede reorganizarlos (que es lo que quería hacer para dar prioridad a ciertos elementos).

[EDITAR]
Pensé en incluir esto en la solución, ya que es [básicamente] lo que terminé haciendo (porque la solución anterior no era adecuada). Usé 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; }


Lamentablemente, actualmente la respuesta es que no es posible. Para hacer eso, necesitarías duplicar gran parte de la funcionalidad de QCompleter en tu propia aplicación (Qt Creator hace eso para su Locator, mira src/plugins/locator/locatorwidget.cpp para la magia si estás interesado).

Mientras tanto, podría votar en QTBUG-7830 , que trata sobre la posibilidad de personalizar la manera en que se complementan los elementos de finalización, como lo desea. Pero no contengas la respiración en eso.


Puede obtener alrededor de QTBUG-7830 como se menciona anteriormente al proporcionar un rol personalizado y realizar la finalización de ese rol. En el controlador de ese rol, puede hacer el truco para que QCompleter sepa que ese elemento está allí. Esto funcionará si también reemplaza filterAcceptsRow en su modelo SortFilterProxy.


Sobre la base de la respuesta de @Bruno, estoy usando la función estándar QSortFilterProxyModel setFilterRegExp para cambiar la cadena de búsqueda. De esta forma, no es necesario subclasificar.

También corrige un error en la respuesta de @ Bruno, que hacía desaparecer las sugerencias por alguna razón una vez que la cadena de entrada se corrigió con el retroceso mientras se escribía.

class CustomQCompleter(QtGui.QCompleter): """ adapted from: http://.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)

Actualizar:

Pensé que mi solución anterior funcionaba hasta que la cadena en el cuadro combinado no coincidía con ninguno de los elementos de la lista. Entonces el QFilterProxyModel estaba vacío y esto a su vez reseteó el text del cuadro combinado. Traté de encontrar una solución elegante a este problema, pero me encontré con problemas (haciendo referencia a los errores de objetos eliminados) cada vez que intenté cambiar algo en self.filterProxyModel . Así que ahora el truco consiste en establecer el modelo de self.filterProxyModel cada vez que sea nuevo cuando se actualice su patrón. Y siempre que el patrón ya no coincida con nada en el modelo, para darle un nuevo modelo que solo contenga el texto actual (también conocido como path en splitPath ). Esto puede ocasionar problemas de rendimiento si se trata de modelos muy grandes, pero para mí el truco funciona bastante bien.

Actualización 2:

Me di cuenta de que este no es el camino perfecto, porque si se escribe una nueva cadena en el cuadro combinado y el usuario presiona enter, el cuadro combinado se borra nuevamente. La única forma de ingresar una nueva cadena es seleccionarla del menú desplegable después de escribir.

Actualización 3:

Ahora ingrese también a las obras. Trabajé alrededor del reinicio del texto del combobox simplemente quitándolo de la carga cuando el usuario presiona enter. Pero lo vuelvo a poner, para que la funcionalidad de finalización permanezca en su lugar. Si el usuario decide hacer más ediciones.


Utilice la propiedad filterMode : Qt::MatchFlags . Esta propiedad contiene cómo se realiza el filtrado. Si filterMode se establece en Qt::MatchStartsWith , solo se Qt::MatchStartsWith las entradas que comiencen con los caracteres escritos. Qt::MatchContains mostrará las entradas que contienen los caracteres escritos, y Qt::MatchEndsWith los que terminan con los caracteres escritos. Actualmente, solo estos tres modos están implementados . Establecer filterMode en cualquier otro Qt::MatchFlag emitirá una advertencia y no se realizará ninguna acción. El modo predeterminado es Qt::MatchStartsWith .

Esta propiedad fue introducida en Qt 5.2.

Funciones de acceso:

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