2011-02-26 22 views
22

Estoy usando Qt4.6 y tengo un QComboBox con un QCompleter en él.Reglas de finalización de encargo de QCompleter

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

chicken soup 
chilli peppers 
grilled chicken 

entrar en la "ch" se correspondería con "sopa de pollo" y "chiles", pero no "pollo asado".

Lo que quiero es poder ingresar "ch" y unir todas ellas o, más específicamente, "pollo" y unir "sopa de pollo" y "pollo a la parrilla".
También quiero poder asignar una etiqueta como "chs" a "sopa de pollo" 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 ...

Respuesta

8

Sobre la base de la sugerencia @ j3frea, aquí es un ejemplo de trabajo (usando PySide). Parece que el modelo debe establecerse cada vez que se llama a splitPath (la configuración del 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) 
+0

cosas buenas. si uno quiere usarlo, asegúrese de establecer todas las opciones en el cuadro combinado y completar como en el código mostrado arriba –

+1

Extendí su solución en una respuesta separada, porque experimento un error en su solución. El cuadro combinado sugiere los elementos correctos mientras escribe. Pero tan pronto como toco el retroceso para corregir la cadena que estoy buscando, el cuadro combinado ya no sugiere ningún elemento. –

1

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

Mientras tanto, puede votar en QTBUG-7830, que trata sobre la posibilidad de personalizar la manera en que se completan los elementos de terminación, como desee. Pero no contengas la respiración en eso.

2

Gracias Thorbjørn, En realidad resolví el problema heredando de QSortFilterProxyModel.

El método filterAcceptsRow se debe sobrescribir y, luego, se debe devolver verdadero o falso según desee o no 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).

[EDIT]
Pensé en tirar esto en la solución ya que es [básicamente] lo que terminé haciendo (porque la solución anterior no era adecuada). Solía ​​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; 
} 
+0

Es bueno saber que 'QSortFilterProxyModel' al menos te permite personalizar la forma en que se filtran los artículos. Por cierto, ¿está seguro de que después de que su filtro de clasificación haya filtrado los resultados, el QCompleter todavía no aplicará también su filtro incorporado? –

+0

@ Thorbjørn Lindeijer, para evitar el filtro 'QCompleter', es posible usar' '' 'como ruta y mostrar todo en el modelo filtrado (Acabo de publicar un ejemplo). Posiblemente no sea el más eficiente, pero parece funcionar. – Bruno

6

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

También corrige un error en la respuesta de @ Bruno, lo que hizo 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://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) 

Actualización:

que pensé que mi solución anterior trabajó hasta la cadena en el cuadro combinado corresponde ninguno de los elementos de la lista. Luego, el QFilterProxyModel estaba vacío y esto a su vez restableció 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 es establecer el modelo de self.filterProxyModel cada vez que se actualiza cuando se actualiza su patrón. Y cada vez que el patrón ya no coincide 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 esto todavía no es el camino perfecto para ir, porque si una nueva cadena se escribe en el cuadro combinado y el usuario pulsa Intro, el cuadro combinado se borra de nuevo. La única forma de ingresar una nueva cadena es seleccionarla del menú desplegable después de escribir.

Actualización 3:

Ahora entrar en las obras también. 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.

+0

Con respecto a la constante y comentario "QtCore.Qt.Key_Enter) no funciona por alguna razón ". Puede usar' QtCore.Qt.Key_Return' en su lugar. –

6

Usar filterMode : Qt::MatchFlags la propiedad. Esta propiedad contiene cómo se realiza el filtrado. Si filterMode está establecido en Qt::MatchStartsWith, solo se mostrarán 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 se introdujo en Qt 5.2.

funciones de acceso:

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

En realidad, yo también quería controlar el orden de las coincidencias presentadas, por lo que esto aún sería inadecuado por lo que entiendo – jcuenod

+0

En implementación actual No puede, en lo posible, mirar los valores posibles de Qt :: MatchFlags. También puede ordenar previamente su modelo para obtener el orden deseado. –

0

Puede moverse QTBUG-7830 como se ha mencionado anteriormente, proporcionando función personalizada y haciendo finalización de ese papel. 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.

+0

Hola @psp. La respuesta parece buena, pero podría ser aún más clara con un ejemplo de código si crees que podrías agregar uno? – yochannah