QML实现输入框Completer自动补全

QtWidgets 的 QLineEdit 输入框可以设置 QCompleter 来实现自动补全,用法如下:

    QStringList words;
    words<<"123456"<<"234567"<<"345678"<<"456789";

    QCompleter *completer = new QCompleter(words, this);
    //默认从头开始匹配,MatchContains设置为任意位置匹配
    completer->setFilterMode(Qt::MatchContains);
    ui->lineEdit->setCompleter(completer);

但是 Qt5 QML 并没有提供相关的功能,需要自己实现。对于列表弹框,一般用 Popup + ListView 即可,而过滤部分可以用 QSortFilterProxyModel。QSortFilterProxyModel 作为 ListView 的 model,输入内容变更后重置过滤条件,ListView 呈现的内容也就是过滤之后的了。

实现效果:

代码链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qml/QmlCompleter

主要参考: https://github.com/Konstilio/QML-TextField-Completer

如果只是输入单个词,那比较简单:选中提示项后关闭弹框,替换当前文本值。如果要输入多个词,需要定义分隔符,然后记录当前光标所在词的前后位置,这样才能只替换当前词。

主要代码:

import QtQuick 2.12
import QtQuick.Controls 2.12
import Completer 1.0

//带提示选项的编辑框
//龚建波 2022-01-17
//参考: https://github.com/Konstilio/QML-TextField-Completer
TextField {
    id: control

    //选项高度
    property int itemHeight: 30
    //只选单个还是可以多次选,由符号分格多个词
    property bool single: true
    //提示的关键字列表
    property alias keywords: completer_proxy.keywords
    implicitHeight: 30
    implicitWidth: 200
    color: "black"
    selectByMouse: true
    selectedTextColor: "white"
    selectionColor: "blue"
    font{
        family: "Microsoft YaHei"
        pixelSize: 14
    }
    background: Rectangle{
        color: "white"
        border.color: "black"
    }

    Menu {
        id: completer_menu
        padding: 1
        width: control.width
        height: (completer_view.count > 10
                 ? 10
                 : completer_view.count) * control.itemHeight + 2
        closePolicy:Popup.CloseOnPressOutsideParent|
                    Popup.CloseOnReleaseOutsideParent|
                    Popup.CloseOnEscape
        contentItem: completer_view
        ListView {
            id: completer_view
            spacing: 0
            model: completer_proxy.filterModel
            interactive: false
            delegate: MenuItem {
                id: menu_item
                text: model.display
                width: completer_menu.width - 2
                height: control.itemHeight
                font: control.font
                property bool highlight: model.index === completer_view.currentIndex
                background: Rectangle {
                    color : (menu_item.pressed ? "green" : (menu_item.highlight ? "lightgreen" : "white"))
                }
                onTriggered: {
                    control.complete(text);
                }
            }
        }
        background: Rectangle {
            border.width: 1
            border.color: "black"
        }
    }

    CompleterProxy {
        id: completer_proxy
    }

    Keys.enabled: true
    Keys.onUpPressed: {
        if(completer_menu.visible){
            completer_view.decrementCurrentIndex();
        }
    }
    Keys.onDownPressed: {
        if(completer_menu.visible){
            completer_view.incrementCurrentIndex();
        }
    }
    Keys.onReturnPressed: {
        if(completer_menu.visible){
            control.selectItem();
        }
    }
    Keys.onEnterPressed: {
        if(completer_menu.visible){
            control.selectItem();
        }
    }

    onPressed: {
        control.textChange()
    }

    onTextEdited: {
        control.textChange()
    }

    //文本编辑后,重置sortfilter的过滤条件
    function textChange()
    {
        var filter_text = getWord(text, cursorPosition);
        completer_proxy.filterModel.setFilterWildcard(filter_text + '*');

        if(completer_menu){
            if(completer_view.count === 0 || filter_text.length < 1){
                completer_menu.close();
            }else{
                completer_menu.popup(control, 0, control.height + 1);
                control.forceActiveFocus();
            }
        }
    }

    //选中当前提示选项
    function selectItem()
    {
        var index = completer_proxy.filterModel.index(completer_view.currentIndex, 0);
        control.complete(completer_proxy.filterModel.data(index, 0));
    }

    //complete 设置当前文本
    //__startPos 多个词时记录当前词的起始位置
    property int __startPos: -1
    function complete(completeText)
    {
        if(control.single){
            control.text = completeText;
        }else{
            if(__startPos === -1 || __startPos >= text.length)
                return;
            var old_text = control.text;
            control.text = old_text.substring(0, __startPos) + completeText +
                    old_text.substring(cursorPosition, old_text.length) + ' ';
            // set cursor position
            control.cursorPosition = __startPos + completeText.length + 1;
            __startPos = -1;
        }

        if(completer_menu){
            completer_menu.close();
        }
    }

    //getWord 在编辑时找词的开始
    //endOfWord 多个词时用于判断词的分隔
    property string endOfWord: "~!#$%^&*()_+{}|:\"<>?,./;'[]\\-=\n "
    function getWord(text, position)
    {
        if(single){
            return text;
        }
        __startPos = -1;
        if (position > text.length)
            return "";
        var pos = position - 1;
        for (; pos >= 0; --pos)
        {
            if (endOfWord.indexOf(text.charAt(pos)) > -1)
            {
                break;
            }
        }
        pos += 1;
        if (pos >= position)
            return "";
        __startPos = pos;
        return text.substring(pos, position);
    }
}
import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Qml Completer")

    CompleteTextField {
        anchors.centerIn: parent
        width: 500
        height: 30
        keywords: ["123456", "234567", "345678", "456789"]
    }
}
#pragma once
#include <QStringListModel>
#include <QSortFilterProxyModel>

class CompleterProxy : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QSortFilterProxyModel* filterModel READ getFilterModel CONSTANT)
    Q_PROPERTY(QStringList keywords READ getKeywords WRITE setKeywords NOTIFY keywordsChanged)
public:
    explicit CompleterProxy(QObject *parent = nullptr);

    //设置给ui列表的model
    QSortFilterProxyModel *getFilterModel();

    //用于提示的关键字列表
    QStringList getKeywords() const;
    void setKeywords(const QStringList &keys);

signals:
    void keywordsChanged();

private:
    QStringListModel model;
    QSortFilterProxyModel filter;
};

CompleterProxy::CompleterProxy(QObject *parent)
    : QObject(parent)
{
    filter.setSourceModel(&model);
    //CaseInsensitive不区分大小写
    filter.setFilterCaseSensitivity(Qt::CaseInsensitive);
    filter.sort(0);
}

QSortFilterProxyModel *CompleterProxy::getFilterModel()
{
    return &filter;
}

QStringList CompleterProxy::getKeywords() const
{
    return model.stringList();
}

void CompleterProxy::setKeywords(const QStringList &keys)
{
    model.setStringList(keys);
    emit keywordsChanged();
}

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值