如何强制 QCompleter 在 QLineEdit 中检查第二个单词。

7

我需要实现一个带有自动完成功能的文本框。

我找到了一个使用QLineEdit和QCompleter的代码。

我的字符串值是"one"、"two"、"three"等。

一旦我输入"on",自动完成器就会提示列表中每个以"on"为前缀的单词。但是当我选择列表中的"one"并尝试输入第二个单词时,自动完成器不起作用了。

在Qt中是否有提供这样的功能,或者在QCompleter中是否有此类功能。我在文档中没有找到相关信息。

请参考我找到的代码:

#include <QApplication>
#include<QStringList>
#include<QLineEdit>
#include<QCompleter>
#include<QHBoxLayout>
#include<QWidget>
#include<QLabel>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget *win=new QWidget();
    QHBoxLayout *lay=new QHBoxLayout();
    QStringList wordList;
    wordList << "alpha" << "omega" << "omicron" << "zeta"<<"america"<<"orion"<<"amit"<<"Odssey";
    QLabel *lbl=new QLabel("Select");
    QLineEdit *lineEdit = new QLineEdit();
    lbl->setBuddy(lineEdit);
    QCompleter *completer = new QCompleter(wordList);
    completer->setCaseSensitivity(Qt::CaseInsensitive); //Make caseInsensitive selectio
    lineEdit->setCompleter(completer);
    lay->addWidget(lbl);
    lay->addWidget(lineEdit);
    win->setLayout(lay);
    win->showMaximized();
    return a.exec();
}

QLineEdit的QCompleter不会检查每个单词,而是整个内容。如果您的字符串是“alpha omega”,自动完成器将为此字符串提供匹配项。您最好继承QLineEdit并设置实现以在每个单词上使用QCompleter,而不是整个内容。 - Sebastian Lange
如何为QLineEdit设置实现,使得QCompleter将检查每个单词而不是整个内容? - Eduard Rostomyan
很抱歉,我现在没有时间为您提供QLineEdit子类的完整实现。此外,您可以通过自己动手做来学到更多。最终,您可以设置QLineEdit的自动完成器,只需识别使用“QCompleter::setCompletionPrefix(const QString &prefix);”函数写入的最后一个单词即可,其中前缀只是您想要完成的最后一个单词。 - Sebastian Lange
如何为QLineEdit设置实现?您可能需要阅读QLineEdit的源代码(http://code.woboq.org/qt5/qtbase/src/widgets/widgets/qlineedit.cpp.html),然后从那里开始。 - Kuba hasn't forgotten Monica
3个回答

8

我认为你需要重写QLineEdit中的几个方法。以下是我的代码版本:

mclineedit.h

#ifndef MCLINEEDIT_H
#define MCLINEEDIT_H

#include <QLineEdit>

class MCLineEdit : public QLineEdit
{
    Q_OBJECT
public:
    explicit MCLineEdit(QWidget *parent = 0);
    void setMultipleCompleter(QCompleter*);

protected:
    void keyPressEvent(QKeyEvent *e);

private:
    QString cursorWord(const QString& sentence) const;

private slots:
    void insertCompletion(QString);

private:
    QCompleter* c;
};

#endif // MCLINEEDIT_H

mclineedit.cpp

#include "mclineedit.h"
#include <QCompleter>
#include <QTextCursor>
#include <QAbstractItemView>
#include <QScrollBar>

MCLineEdit::MCLineEdit(QWidget *parent) :
    QLineEdit(parent)
{
}
void MCLineEdit::keyPressEvent(QKeyEvent *e)
{
    QLineEdit::keyPressEvent(e);
    if (!c)
        return;

    c->setCompletionPrefix(cursorWord(this->text()));
    if (c->completionPrefix().length() < 1)
    {
        c->popup()->hide();
        return;
    }
    QRect cr = cursorRect();
         cr.setWidth(c->popup()->sizeHintForColumn(0)
                     + c->popup()->verticalScrollBar()->sizeHint().width());
    c->complete(cr);

}
QString MCLineEdit::cursorWord(const QString &sentence) const
{
    return sentence.mid(sentence.left(cursorPosition()).lastIndexOf(" ") + 1,
                        cursorPosition() -
                        sentence.left(cursorPosition()).lastIndexOf(" ") - 1);
}

void MCLineEdit::insertCompletion(QString arg)
{
    setText(text().replace(text().left(cursorPosition()).lastIndexOf(" ") + 1,
                           cursorPosition() -
                           text().left(cursorPosition()).lastIndexOf(" ") - 1,
                           arg));
}

void MCLineEdit::setMultipleCompleter(QCompleter* completer)
{
    c = completer;
    c->setWidget(this);
    connect(c, SIGNAL(activated(QString)),
            this, SLOT(insertCompletion(QString)));
}

想要了解更多,请访问 http://qt-project.org/doc/qt-4.8/tools-customcompleter.html (使用QTextEdit)。


3
我这里找到了一个解决方案。
#ifndef COMPLETER_H
#define COMPLETER_H
#include <QCompleter>
#include <QString>
#include <QStringList>
#include <QLineEdit>

class Completer:public QCompleter
{
Q_OBJECT
public:
    explicit Completer(QStringList stringList, QObject *parent=0);
    virtual QString pathFromIndex(const QModelIndex &index)const;
    virtual QStringList splitPath(const QString&)const;

public slots:
    void onLineEditTextChanged();
private:
    mutable int cursorPos_;
};


class ExpressionLineEdit: public QLineEdit
{
    Q_OBJECT
    public:
        explicit ExpressionLineEdit(QWidget *parent=0);
private:
    QStringList stringList;
    Completer *completer_;
};
#endif // COMPLETER_H

#include <completer.h>
#include <QDebug>
Completer::Completer(QStringList stringList, QObject *parent)
    : QCompleter(stringList,parent)
    , cursorPos_(-1)
{

}

ExpressionLineEdit::ExpressionLineEdit(QWidget* parent)
    : QLineEdit(parent)
{
stringList << "minRoute" << "minPitch" << "minSpacing";
completer_ = new Completer(stringList, this);
setCompleter(completer_);

    QObject::connect(this, SIGNAL(textChanged(const QString&)),
             completer_, SLOT(onLineEditTextChanged()));

    QObject::connect(this, SIGNAL(cursorPositionChanged(int, int)),
            completer_, SLOT(onLineEditTextChanged()));
}

QString Completer::pathFromIndex(const QModelIndex &index) const
{
    QString newStr = index.data(Qt::EditRole).toString();
    ExpressionLineEdit *lineEdit = qobject_cast<ExpressionLineEdit*>(parent());
    QString str = lineEdit->text();
    int prevSpacePos = str.mid(0, lineEdit->cursorPosition()).lastIndexOf(' ');

    int curPos = lineEdit->cursorPosition();
    int nextSpacePos = str.indexOf(' ', curPos);
    if (nextSpacePos == -1) {
        nextSpacePos = str.size();
}

QString part1 = str.mid(0, prevSpacePos + 1);
QString pre = str.mid(prevSpacePos + 1, curPos - prevSpacePos - 1);
QString post = str.mid(curPos, nextSpacePos - curPos);
QString part2 = str.mid(nextSpacePos);

    cursorPos_ = curPos + newStr.size() - pre.size();
    return part1 + newStr + part2;
}

void Completer::onLineEditTextChanged()
{
    qDebug() << "Completer::onLineEditTextChanged()" << cursorPos_;
    if (cursorPos_ != -1) {
        ExpressionLineEdit *lineEdit = qobject_cast<ExpressionLineEdit*>(parent());
        lineEdit->setCursorPosition(cursorPos_);
        cursorPos_ = -1;
    }
}

QStringList Completer::splitPath(const QString &path) const
{
     cursorPos_ = -1;
     ExpressionLineEdit *lineEdit = qobject_cast<ExpressionLineEdit*>(parent());
     QString text = lineEdit->text();
     QStringList stringList;
     QString str;
     int index = text.mid(0,lineEdit->cursorPosition()).lastIndexOf(' ');
     str = text.mid(index, lineEdit->cursorPosition()-index);
     str.trimmed();
     str.replace(" ", "");
     stringList << str;
     return stringList;
}

3
我需要像这样的东西!但在我的测试中,我注意到了一些问题。有些东西可以被删除(例如,QString post 实际上并没有被使用,可以删除)。此外,从我的测试来看,你的实现中似乎存在一个 bug,如果你输入一系列单词,然后回到中间某个位置输入一个单词,如果你使用箭头键上下移动自动完成列表,第一个高亮显示的单词将被放在正确的位置,但之后光标会跳到末尾(不管是哪个 onLineEditTextChanged() 插槽),然后它开始用自动更正列表中的单词替换最后一个单词。
我创建了自己的实现,其中一个主要区别是我的实现需要一个 delimiter 字符,并且该字符用于代替始终使用空格的情况,我修复了上述描述的 bug。你会发现我的实现非常简单: DelimitedCompleter.hpp
#ifndef DELIMITEDCOMPLETER_HPP
#define DELIMITEDCOMPLETER_HPP


#include <QCompleter>


class QString;
class QStringList;


/**
 * class DelimitedCompleter
 *
 * QCompleter that supports completing multiple words in a QLineEdit, completed words are separated
 * by delimiter.
 */
class DelimitedCompleter : public QCompleter {
    Q_OBJECT

    public:
        DelimitedCompleter(QLineEdit *parent, char delimiter);
        DelimitedCompleter(QLineEdit *parent, char delimiter, QAbstractItemModel *model);
        DelimitedCompleter(QLineEdit *parent, char delimiter, const QStringList &list);
        QString pathFromIndex(const QModelIndex &index) const;
        QStringList splitPath(const QString &path) const;

    private:
        char delimiter;
        mutable int cursor_pos = -1;

        void connectSignals();

    private slots:
        void onActivated(const QString &text);
        void onCursorPositionChanged(int old_pos, int new_pos);
};


#endif

DelimitedCompleter.cpp

#include "DelimitedCompleter.hpp"

#include <QDebug>
#include <QLineEdit>
#include <QStringList>


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                              DELIMITEDCOMPLETER PUBLIC METHODS                                *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


DelimitedCompleter::DelimitedCompleter(QLineEdit *parent, char delimiter)
        : QCompleter(parent), delimiter(delimiter) {
    parent->setCompleter(this);
    connectSignals();
}


DelimitedCompleter::DelimitedCompleter(QLineEdit *parent, char delimiter, QAbstractItemModel *model)
        : QCompleter(model, parent), delimiter(delimiter) {
    parent->setCompleter(this);
    connectSignals();
}


DelimitedCompleter::DelimitedCompleter(QLineEdit *parent, char delimiter, const QStringList &list)
        : QCompleter(list, parent), delimiter(delimiter) {
    parent->setCompleter(this);
    connectSignals();
}


QString DelimitedCompleter::pathFromIndex(const QModelIndex &index) const {
    QString auto_string = index.data(Qt::EditRole).toString();
    QLineEdit *line_edit = qobject_cast<QLineEdit*>(parent());
    QString str = line_edit->text();

    // If cursor position was saved, restore it, else save it
    if(cursor_pos != -1) line_edit->setCursorPosition(cursor_pos);
    else cursor_pos = line_edit->cursorPosition();

    // Get current prosition
    int cur_index = line_edit->cursorPosition();

    /**
     * NOTE
     *
     * prev_delimiter_index should actually point at final white space AFTER the delimiter.
     */

    // Get index of last delimiter before current position
    int prev_delimiter_index = str.mid(0, cur_index).lastIndexOf(delimiter);
    while(str.at(prev_delimiter_index + 1).isSpace()) prev_delimiter_index++;

    // Get index of first delimiter after current position (or EOL if no delimiter after cursor)
    int next_delimiter_index = str.indexOf(delimiter, cur_index);
    if(next_delimiter_index == -1) {
        next_delimiter_index = str.size();
    }

    // Get part of string that occurs before cursor
    QString part1 = str.mid(0, prev_delimiter_index + 1);

    // Get string value from before auto finished string is selected
    QString pre = str.mid(prev_delimiter_index + 1, cur_index - prev_delimiter_index - 1);

    // Get part of string that occurs AFTER cursor
    QString part2 = str.mid(next_delimiter_index);

    return part1 + auto_string + part2;
}


QStringList DelimitedCompleter::splitPath(const QString &path) const {
    QLineEdit *line_edit = qobject_cast<QLineEdit*>(parent());

    QStringList string_list;
    int index = path.mid(0,line_edit->cursorPosition()).lastIndexOf(delimiter) + 1;
    QString str = path.mid(index, line_edit->cursorPosition()-index).trimmed();
    string_list << str;
    return string_list;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                              DELIMITEDCOMPLETER PRIVATE METHODS                               *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


void DelimitedCompleter::connectSignals() {
    connect(this, SIGNAL(activated(const QString &)), this, SLOT(onActivated(const QString &)));

    connect(qobject_cast<QLineEdit*>(parent()), SIGNAL(cursorPositionChanged(int, int)),
        this, SLOT(onCursorPositionChanged(int, int)));
}


void DelimitedCompleter::onActivated(const QString &text) {
    cursor_pos = -1;
}


void DelimitedCompleter::onCursorPositionChanged(int old_pos, int new_pos) {
    // If old_pos == cursor_pos then we are cycling through autocomplete list
    // If not cycling through autocomplete list, then make sure cursor_pos is reset to -1
    if(old_pos != cursor_pos) cursor_pos = -1;
}

我自从最初发布以来进行了几次更改,我认为我的实现基本上没有错误,并且现在非常可用,如果有人尝试我的代码并发现错误,请告诉我! - Tory
1
我喜欢你的解决方案,因为我不必更改我的QLineEdit,但我必须更改第54行为while (prev_delimiter_index < str.size() - 1 && str.at(prev_delimiter_index + 1).isSpace())以避免越界崩溃。使用Qt 5.12.2。 - jaba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接