1. 先聊聊为什么还要重新认识QCompleterQCompleter这名字在Qt圈子里不算冷门但大部分人对它的认知停留在“给QLineEdit加个下拉建议”的阶段构造一个QStringListModel塞几条字符串setCompleter一挂完事。等真正进了项目需求一复杂就发现这玩意儿没那么乖——明明model里有数据下拉却不对过滤规则跟预期不符弹窗高度忽大忽小和QComboBox配合时各种奇怪行为接踵而至。我的态度很明确QCompleter不是只能做“静态字典联想”的小工具它是一套完整的“输入补全引擎”。只要把它的模式机制、数据模型接口、过滤语义摸透它可以服务从代码编辑器、搜索框、表格单元格内联提示到复杂业务表单联动的一大堆场景。这篇就按我实际项目里逐步趟出来的路径把QCompleter的进阶用法拆开讲不讲空泛理论直接给结论、给代码、给坑。适合谁看两类人。一类是已经用过QCompleter但被它的怪毛病卡过几次的Qt开发者另一类是想拿它做自定义输入联想、动态数据补全、多列联动提示的进阶玩家。如果你只是“setStringList三件套”的初级用户看完也会对内部行为有全新的认知。2. 核心机制拆解模式、匹配、数据源三者如何协同2.1 CompletionMode选错后面全白做QCompleter有三种补全模式很多新手只见过默认的Popup剩下两个当它不存在这是大坑。Popup模式输入时在输入框下方弹出一个列表视图用户用上下键选择按回车或点击后选中项替换当前输入。Inline模式不弹列表直接在输入框内自动补全缺失的部分补全内容以“选中态”显示在后面用户回车确认、继续输入则自动放弃。UnfilteredPopup模式弹窗始终显示model里的全部项输入内容不做前缀过滤。这种模式很少用但某些场景——比如你想让用户既能看到全量候选又能随手键入过滤——就非常合适。选型要根据交互目标决定。以搜索对话框为例用户期望可见候选项随输入不断刷新Popup就是正宗选择。但如果做的是“URL地址栏自动补全”浏览器里那种灰显补齐效果Inline模式更贴近预期。我见过有人把UnfilteredPopup用在“总是显示所有历史记录但允许输入筛选”的混合需求上结果发现筛选和显示是割裂的后来改成Popup加自定义过滤才顺了。模式之间的行为差异Core里由completionMode属性决定但真正的过滤逻辑在QCompleterPrivate的filter逻辑里想深入可以从源码跟踪。2.2 Popup高度、宽度这些“小”设置实际体验差别巨大setMaxVisibleItems是控制弹窗可见行数的关键默认值是7。这个值直接影响可用性太大了小分辨率下弹窗会顶出屏幕太小了候选密集场景用户要反复翻。另一个会坑人的点是弹窗宽度。QCompleter的popup宽度默认跟随输入框但如果你setModel的是QStringListModelpopup列宽不会自动适配最长字符串。解决办法是setPopup的时候塞一个QListView进去然后设置尺寸策略QListView *listView new QListView; listView-setUniformItemSizes(true); listView-setSelectionBehavior(QAbstractItemView::SelectRows); completer-setPopup(listView);setUniformItemSizes(true)是经典优化项。当候选列表几百上千条时QListView默认要反复询问每个item的尺寸开启后按统一尺寸绘制渲染性能立竿见影。我实测过几千条数据不开这个弹窗滚动明显卡顿开了之后流畅一个等级。弹窗宽度的逻辑Qt文档里说得不算细。实际行为是如果QCompleter的widget是QLineEditpopup会默认拉伸到和QLineEdit一样宽。想让popup比输入框宽或者窄就得在自定义popup上做文章。QListView *popupView qobject_castQListView *(completer-popup()); popupView-setMinimumWidth(400);一个常见误区是修改popup的minimumWidth没用因为completer内部可能在showEvent里做了宽度重置动态调宽度还得自己拦截showEvent或在事件过滤器里处理。这个我在后面“实战坑位”里详细讲。2.3 匹配规则不是只有前缀匹配FilterMode才是真正决定语义的地方Qt 5.2以后QCompleter引入了setFilterMode这个接口不显眼但它是进阶老玩家和新手的分水岭。默认filter是Qt::MatchStartsWith也就是“前缀匹配”。中文输入场景下前缀匹配往往不够用。你想实现“包含匹配”——用户输入“北京”能补全“北京市朝阳区”前缀匹配就做不到。这时要设置completer-setFilterMode(Qt::MatchContains);更精准的做法是用正则匹配completer-setFilterMode(Qt::MatchRegularExpression);只用MatchContains有个边缘问题大小写敏感度还受caseSensitivity约束。比如用户想匹配“Qt”而输入“qt”不设caseSensitivity时大小写不敏感没问题但某些业务里用户名、ID匹配必须严格区分大小写就得completer-setCaseSensitivity(Qt::CaseSensitive);还有个细节QCompleter过滤的是completionPrefix而这个prefix来自输入框的当前文本。但注意QCompleter默认对文本的截取策略是“从光标位置向后取整段”不是把光标前的最后一个词拎出来。假如输入“hello worl|d”prefix是整串“hello world”而不是“hello worl”。这种情况要在复杂输入场景里拆词匹配全靠重写splitPath。2.4 completer是一个“匹配引擎”不是“存储容器”这是理解QCompleter最重要的心智模型。QCompleter自己基本不存数据它持有一个model指针所有候选数据、显示文本、匹配列都从这个model取。你传入QStringList它内部会包一个QStringListModel你传入QStandardItemModel它就直接按QAbstractItemModel的规则去取值。因此决定补全行为的关键环节有三处数据从哪来model选型取哪一列/哪一行setCompletionColumn、completionRole匹配哪段文本setCompletionPrefix、splitPath、filter。这三个环节搞清楚QCompleter基本就被你拿捏了。3. 数据模型进阶从字符串列表到多列联动的完整方法论3.1 QStringListModel够用但别过度依赖很多教程示例都用QStringListModel因为它构造简单、零心智负担。但在真实项目里它有两个明显短板没有业务字段。你只能存“显示字符串”存不了ID、类别、附加数据无法对数据进行分组或分类排序。如果业务只是给历史记录补全QStringListModel没毛病。一旦需要“显示中文名补全拼音码或ID”它就完全不够了。3.2 自定义QAbstractListModel的标准姿势当一条候选记录有多个字段时标准做法是自己实现一个QAbstractListModel。关键点是数据角色和completionRole齐头并进。举个例子一个用户搜索框要支持按姓名、工号、手机号任意输入补全但显示列表里同时展示姓名、工号、手机号三列。QLineEdit的补全弹窗默认是个QListView只能显示一行文本这种情况下还想要多列展示就必须重写popup为QTableView并设置对应列。这个我们稍后演示。先看模型侧最精简的结构class UserListModel : public QAbstractListModel { Q_OBJECT public: struct UserInfo { QString name; QString uid; QString phone; }; int rowCount(const QModelIndex parent QModelIndex()) const override { return parent.isValid() ? 0 : users.size(); } QVariant data(const QModelIndex index, int role) const override { if (!index.isValid() || index.row() users.size()) return QVariant(); const UserInfo u users.at(index.row()); if (role Qt::DisplayRole) { return u.name; // 列表默认显示的文本 } if (role Qt::UserRole) { return u.uid; } if (role Qt::UserRole 1) { return u.phone; } return QVariant(); } };配合completer设置completer-setModel(model); completer-setCompletionRole(Qt::DisplayRole); // 参与过滤的默认是显示角色 completer-setCompletionColumn(0);这里有个隐藏行为如果completionRole是Qt::DisplayRoleQCompleter过滤时取的是data(index, Qt::DisplayRole)返回的字符串。你可以在data()里对着role做区分比如DisplayRole返回“姓名 空格 工号”格式的字符串过滤和显示同时兼顾。3.3 用QStandardItemModel做多列补全弹窗多列展示的典型需求是“商品搜索”输入关键字弹窗里显示商品名、分类、价格三列用户选中后把商品名填回输入框。这种场景QListView是搞不定的必须换QTableView作为popup。做法分两步。第一步让completer使用QStandardItemModel并且指定参与匹配的列第二步替换completer默认的popup。QStandardItemModel *model new QStandardItemModel(this); // 每个候选商品一行三列 for (const auto item : goodsList) { QListQStandardItem * rowItems; rowItems new QStandardItem(item.name); rowItems new QStandardItem(item.category); rowItems new QStandardItem(QString::number(item.price)); model-appendRow(rowItems); } completer-setModel(model); completer-setCompletionColumn(0); // 用第0列做过滤匹配 completer-setCompletionRole(Qt::DisplayRole);但这一步还不够。QCompleter默认取“当前选中行的第0列文本”作为补全结果填回输入框。如果商品表第0列是名称那填回的就是名称OK。但价格、分类怎么显示这时把popup换掉QTableView *tableView new QTableView; tableView-setSelectionBehavior(QAbstractItemView::SelectRows); tableView-setSelectionMode(QAbstractItemView::SingleSelection); tableView-setEditTriggers(QAbstractItemView::NoEditTriggers); tableView-setShowGrid(false); tableView-horizontalHeader()-setSectionResizeMode(QHeaderView::Stretch); tableView-setMinimumWidth(500); completer-setPopup(tableView);注意一个细节QCompleter内部的popup是QAbstractItemView的子类它靠QAbstractItemView::currentIndex来跟踪当前高亮项。换成QTableView之后默认的键盘上下键导航、回车确认逻辑还保留只要SelectionMode和SelectionBehavior设置对基本可以直接用。3.4 动态更新模型数据变了completer怎么感知这是实战里踩得最狠的坑之一也是论坛里重复出现的问题。场景用户输入关键字后台异步拉取建议词结果回来之后直接model-clear()再插入新行然后发现completer弹窗里的数据要么不刷新、要么弹出空白。原因是什么QCompleter监听的是model的dataChanged、rowsInserted、rowsRemoved等信号。如果你是用同一个model实例正常增删行completer应该能感知到。但清空后重新插入sequence是model-clear(); // 此时model的dataChanged等信号已发QCompleter收到了空状态 for (...) { model-appendRow(...); // 逐条通知 }大部分情况是好的。真正常见的问题出在“替换了整个model”completer-setModel(newModel); // 老model被set进去如果completer原先用的model和newModel是两个实例你切换后旧model的数据变化信号completer不再关心自然不刷新。遇到这种情况要么保留同一个model实例做数据更新要么切换完成后主动重新设置补全前缀completer-setCompletionPrefix(currentText);这个动作会强制completer重新过滤一次。很多“不刷新”问题一个setCompletionPrefix就治好了。3.5 由model所有权引发的内存风波QCompleter对model的所有权策略在不同Qt版本里有不一致的历史。早期Qt版本中如果通过构造传入model指针completer会认为model归自己所有但通过setModel传入的所有权默认不转移。到了Qt 5.15以后行为做了调整文档明确说setModel传入的model也不会被删除所有权归调用方。实际项目里最稳妥的态度是对传入completer的model统一用显式父对象管理。比如new Model(this)让父QObject管理生命周期避免completer和外部代码互相甩锅导致悬垂指针。4. 过滤逻辑的深度定制splitPath、filter、completionRole组合拳4.1 自定义splitPath实现拆词输入匹配QCompleter默认会把输入框的整个文本作为前缀。对“标签搜索”这类场景用户输入“Acl, Qt”时你希望补全的是光标后那个词“Qt”而不是整段“Acl, Qt”。实现方式是通过重写splitPath和pathFromIndex两个虚函数。class TagCompleter : public QCompleter { Q_OBJECT public: using QCompleter::QCompleter; protected: QStringList splitPath(const QString path) const override { // 只取最后一个逗号后的部分参与匹配 QString lastToken path.section(,, -1).trimmed(); return QStringList() lastToken; } QString pathFromIndex(const QModelIndex index) const override { // 选中后保留前面的内容只把最后一段替换成选中项 QString current completer()-completionPrefix(); QString prefixPart current.section(,, 0, -2); QString newText index.data(Qt::DisplayRole).toString(); if (prefixPart.isEmpty()) return newText; return prefixPart , newText; } };要注意的是completionPrefix在某些版本是可读写属性可以直接取。pathFromIndex里如果只是return model数据会丢前缀造成选中后把整段输入都覆盖成一条候选这是初学者常见的“输入框内容被截断”问题的根源。4.2 completionRole到底影响什么completionRole表示“从model的哪一列哪个角色取值参与匹配”。默认值是Qt::EditRole。对QStandardItemModel来说EditRole和DisplayRole返回的基本一样但自定义model里两者可以刻意区分——DisplayRole给用户看展示文本EditRole给completer做匹配文本。例如商品列表DisplayRole显示“商品名规格”EditRole返回“商品名”那输入“商品名”可以匹配输入带括号的规格字符串就不匹配。这就是一个很实用的场景。实际项目里把匹配键值和展示文本分离使用能解决“按照拼音码补全中文”的经典需求EditRole返回拼音或首字母串DisplayRole返回中文名。用户输入“bj”能提示“北京”输入“北京”也能提示“北京”。实现就是在data()里对不同角色返回不同内容completionRole设置为Qt::EditRolepopup显示时依然拿到DisplayRole。这种做法的好处是不需要维护两份model或自定义filter类原生机制就支持。4.3 实现“包含匹配”与“通配匹配”时的细节setFilterMode(Qt::MatchContains)的语义是候选字符串包含输入串即匹配。但有两个坑 一是“包含”受caseSensitivity控制对ASCII字母场景影响不大中文无大小写问题英文缩写场景要小心 二是匹配顺序Qt默认按model原始顺序返回候选不按匹配度排序。你期望“以输入开头的排前面包含的排后面”这种智能排序原生QCompleter不提供。要做只能自己维护一个排序后的QStringListModel在每次setCompletionPrefix时重新排一次。QStringList filtered allKeys; filtered.sort(); // 自定义排序前缀命中 子串命中 std::stable_sort(filtered.begin(), filtered.end(), [this](const QString a, const QString b) { int rankA rankOf(a); int rankB rankOf(b); return rankA rankB; }); model-setStringList(filtered);这种“重排后刷新模型”的做法再加上setCompletionPrefix强制刷新就能得到近似搜索引擎的体验。5. 弹窗行为的掌控焦点的流动、键盘事件和显示位置5.1 弹窗出来之后到底谁获得焦点QCompleter弹窗默认行为按下键盘上下键时QLineEdit保持焦点popup内部的高亮索引跟随键盘移动。这个交互逻辑是Qt内置的也基本符合主流补全交互。真正让人头疼的是“用户用鼠标点击popup里的项”和“用户回车选中”这两个路径。鼠标点击时点击事件会先落在popup上然后completer会activated信号QLineEdit收到结果。如果你在activated里判断“获得焦点的控件”这时候焦点还在popup上这在某些业务里会造成误判。键盘回车的路径则相反焦点始终在QLineEdit上回车后Qt内部的event filter捕获回车让popup选中当前行。所以如果业务要区分“键盘确认”和“鼠标点击”需要在activated里判断sender是否为空或对比currentIndex没法只看焦点。5.2 popup位置和大小的最后一道闸门QCompleter弹窗的位置计算Qt自己已经做了基准处理默认在widget下方弹出并在必要时翻转方向。但复杂布局下比如QLineEdit在可滚动容器内弹窗可能在滚动时残留或者定位偏差。我处理这种问题时习惯全局安装事件过滤器监听QEvent::Show和移动事件重新校正popup位置。bool MyFilter::eventFilter(QObject *obj, QEvent *ev) { if (obj completer-popup() ev-type() QEvent::Show) { popupView-move(lineEdit-mapToGlobal(QPoint(0, lineEdit-height() 2))); } return false; }这个手法虽然有点“土”但能解决大多数第三方样式表、布局嵌套导致的弹窗错位。另一个常见的弹窗问题popup宽度被QCompleter内部宽度计算强制拉回输入框宽度。你setMinimumWidth可能生效但一旦当前model的列宽策略改变又会弹回去。更稳定的做法是给自定义popup写个子类重写showEventvoid MyPopup::showEvent(QShowEvent *e) { QTableView::showEvent(e); setFixedWidth(qMax(widthForContent(), hostWidth())); }5.3 popup里放自定义控件图片、按钮、富文本默认QListView渲染的只是简单的text。想在补全项里显示图标或者几行富文本信息QListView自带功能很弱。进阶做法是把popup换成QListView自定义Delegate。Delegate里重写paint和sizeHint可以画出任意复杂的候选卡片左侧头像、中间名称描述、右侧状态标签。QCompleter对Delegate的配合度很高因为它内部只关心currentIndex和activated信号绘制完全由view的delegate接管。这里有个容易踩的坑completer的popup如果使用自定义delegatesizeHint不能随便返回固定高度。如果model中每行差异较大比如名称长短不同要按内容动态计算高度否则popup的行高错乱视觉很糟糕。6. 与QComboBox深度集成可编辑下拉的正确打开方式6.1 可编辑QComboBox为什么补全总是不听话让QComboBox可编辑、再加QCompleter实现“既能下拉选又能输入提示”操作如下QComboBox *combo new QComboBox; combo-setEditable(true); combo-setInsertPolicy(QComboBox::NoInsert); QCompleter *completer new QCompleter(combo); completer-setModel(combo-model()); combo-setCompleter(completer);但很快会撞上一个现象输入时下拉列表能过滤了但下拉框本身的QComboBox下拉列表也还在——界面上有两个弹层交互非常割裂。原因setCompleter之后QComboBox的可编辑输入框会收到补全弹窗同时QComboBox原本的view还保留着自己的下拉弹窗。要二选一。如果目标是“完全使用QCompleter的补全体验”就把QComboBox自身的下拉箭头去掉或禁用弹出combo-setMaxVisibleItems(0); // 这不好使更靠谱的做法是用隐藏下拉按钮样式或者干脆不用QComboBox改用QLineEdit加QToolButton做“搜索框按钮”组合。从产品交互角度看补全输入用QLineEditQCompleter灵活得多QComboBox更适合“纯选择型”下拉。6.2 数据联动一个输入框两个completer动态切换有些表单里一个输入框会根据另一个字段不同调用不同数据源的补全。这时候不要创建无数个QCompleter实用做法是复用同一个QCompleter动态setModel。void onTypeChanged(int type) { if (type TypeUser) { completer-setModel(userModel); completer-setCompletionColumn(0); } else if (type TypeDept) { completer-setModel(deptModel); completer-setCompletionColumn(1); } completer-setCompletionPrefix(lineEdit-text()); }切换model后setCompletionPrefix强制刷新一下弹窗数据马上跟过来。这个方案我用了好几年稳定而且代码量最小。反面教材是有人给每个类型new一个completer再在切换时setCompleter换装结果旧completer持有旧widget连接导致内存和信号堆积。动态刷新还有一个关键点如果数据是异步拉取的必须保证响应顺序。用户先输入“abc”发出请求A再输入“abd”发出请求B如果A的响应晚于B返回了旧的建议列表直接用会把B的结果覆盖掉。解决办法是每个请求绑定请求序号返回时判断序号是否为最新不是就丢弃。这种“竞态处理”做不好搜索框的联想功能会出现非常难复现的抖动问题。7. 实战中绕不开的坑位与排查清单7.1 选中补全后光标位置“跳回开头”用户输入“Qt 智能提示”补全选中的是“智能提示”结果文本变成了“Qt 智能提示”但光标跳到了整个字符串最前面。这个问题的根源通常是pathFromIndex返回的字符串没包含光标前的部分或者splitPath拆词后构造最终文本时故意只返回了选中词。解决思路正常情况下QCompleter内部逻辑会用completionPrefix替换选中项并保留prefix前面的部分。如果你的pathFromIndex返回了完整字符串需要同时考虑光标前内容如果返回不完整就会丢文本。排查这一步时建议在pathFromIndex里打印当前输入框text()和completionPrefix()逐一确认。7.2 弹窗不显示或一闪而过常见情况是setCompleter后输入框任何输入都没反应popup根本不出现。另一个情况是弹窗闪了一下就消失用户根本来不及点选。不显示可能原因有completer的model为空、setCompletionPrefix未生效、或者QCompleter的widget没设置对。注意QCompleter内部用setWidget指定关联控件你在构造传入lineEdit后才有资格弹窗。一闪而过大概率是lineEdit在textChanged里手动setText或者你自己清空了输入框内容把completer的过滤前缀也带崩了。实际操作中我一遇到闪没的案例第一件事就是检查是不是在textChanged或editingFinished里对同一输入框做了“防抖”处理触发了一次额外的clear或setText。输入框文本被外部重置QCompleter内部的数据同步断掉弹窗立刻关闭。这是特别隐蔽的坑。7.3 数据更新不刷新、候选还停留在旧状态在前面模型章节提过setCompletionPrefix可以强制刷新。这里再补充另一个原因setModel时如果模型已经有很多行而completer的completionColumn、completionRole和你预期的不一致过滤会命中空集。例如model更新后新增了列completionColumn仍然指向旧列那一列恰好是空数据弹窗就会空白。排查思路固定三步自己把model的数据遍历打印一遍确认行和列内容确认completionColumn指向的列不为空手动调一次setCompletionPrefix(), 再调setCompletionPrefix(lineEdit-text())看弹窗是否恢复。三步排查下来绝大部分“数据不刷新”都能定位。7.4 和输入法、粘贴操作互相干扰中文输入法下QCompleter的弹窗在拼音组词过程中会频繁触发textChanged有时候拼音还没上屏弹窗已经在跟着“字母”跑了。这是Qt输入法和QCompleter协作不够智能的典型表现。我的做法是监听QInputMethod事件或重写QLineEdit::inputMethodEvent在inputMethodComposing为true时暂时禁用completer的弹出void PinyinLineEdit::inputMethodEvent(QInputMethodEvent *event) { bool composing !event-preeditString().isEmpty(); if (composing completer()) completer()-popup()-hide(); QLineEdit::inputMethodEvent(event); }粘贴操作也有类似问题用户粘贴一段包含空格或换行的字符串QCompleter会按整段文本做过滤结果弹窗里什么都没有。贴合业务的做法是在粘贴后手动截断一下输入框内容或重新设置completionPrefix为最后一个有效词。8. 完整案例一个带权重的代码片段补全输入框前面讲了不少分散的点这里用一个能落地的完整案例把知识点串起来。需求很简单做一个简易IDE里的“代码片段补全输入框”。用户输入片段名或缩写时popup里展示片段名称、语言、说明三列数据并按使用频率权重排序选中后把片段模板插入编辑器。这个案例覆盖了自定义模型、多列popup、动态排序、包装编辑器四个技术点。第一步数据模型。用一个QStandardItemModel每行四列片段名、语言、说明、使用次数。前四列是数据其中片段名参与匹配。第二步自定义排序。QCompleter原生不支持按权重排序。我在每次setCompletionPrefix触发时从原始数据里筛出匹配的候选按weight降序重建一个临时的QStandardItemModel再setModel给completer。第三步自定义popup使用QTableView设置SelectionBehavior为SelectRows禁止编辑开启行选中。再给每列设置合适的宽度比例。第四步接入编辑器。以QPlainTextEdit为例它本身不是QLineEdit系列但QCompleter的setWidget支持任意QWidget。做法是给QPlainTextEdit加一个事件过滤器捕获按键事件当用户按下触发快捷键比如CtrlSpace时把当前光标前的单词作为completionPrefix然后调用completer-complete()手动弹出bool EditorFilter::eventFilter(QObject *obj, QEvent *event) { if (obj editor event-type() QEvent::KeyPress) { QKeyEvent *keyEvent static_castQKeyEvent *(event); if (keyEvent-key() Qt::Key_Space keyEvent-modifiers() Qt::ControlModifier) { QString word currentWordUnderCursor(); completer-setCompletionPrefix(word); completer-complete(); return true; } } return QObject::eventFilter(obj, event); }这里的高质量体验点在于每次弹出前用当前光标前的单词重置completionPrefix并且把popup的弹出位置定位在光标下方而不是简单的输入框正下方。QCompleter的complete()默认对齐widget底部当widget是QPlainTextEdit时需要手动移动popupQPoint cursorPos editor-cursorRect().bottomLeft(); QPoint globalPos editor-viewport()-mapToGlobal(cursorPos); completer-popup()-move(globalPos QPoint(0, 2));第五步选中处理。连接completer的activated信号根据当前行的第0列取出片段名再从原始数据池查模板插入编辑器。因为popup是QTableViewactivated带一个QModelIndex可以直接通过index.row()去你自己的原始数据数组取模板。这样一个完整的代码片段补全能力用QCompleter加少量自定义代码就能实现。整个过程没有引入第三方组件跨平台行为也一致后续要加“语言过滤”“最近使用优先”等逻辑也只是改模型和排序架构上非常干净。9. 一些项目沉淀下来的个人看法QCompleter给我的感觉像一把好刀新手拿它切菜都嫌钝用熟了才发现刀刃早就开好了只是很多人没去找磨刀石。它最核心的价值在于“输入补全”这个交互本身是高频、高频、极高频的用户触点。一个方案合理的补全输入能大幅减少用户敲键盘的数量也能帮用户把模糊记忆转化成准确输入。在业务系统、IDE、搜索工具、表单填写的每一个角落它都有发挥空间。但也正因为高频它的一点瑕疵都会被放大。弹窗位置偏了1个像素、数据刷新慢了一帧、匹配语义差了一个字母用户都能立刻感知。这也是我写这篇文章的初衷把QCompleter从“会用”推向“用对”。以我个人的实践经验来说刚接触QCompleter时最容易犯的错误是把它当成一个封闭控件拼命找“配置项”去解决“模型问题”。等到想通“QCompleter匹配引擎数据模型弹窗视图”三元组之后很多问题就不再是问题了。你有任何自定义输入联想需求先想清楚这三元组各自要承担什么职责再动手写代码基本不会走弯路。最后一个小建议如果你所在团队有“输入联想”类需求哪怕暂时只做最基础的静态列表也要预留自定义模型和自定义popup的空间。等真实业务冲过来时不用推翻重来只要往模型里加列、往popup里加视图就能平稳接住。