资讯详情

Qt无线连接与软键盘实现:WiFi扫描、连接及输入方案详解

📅 2026/10/30 19:04:48 | 华诺云谱 👁 阅读
Qt无线连接与软键盘实现:WiFi扫描、连接及输入方案详解
简介面向Linux平台需要添加WiFi图形管理能力的嵌入式或桌面开发者这份资源以Qt框架完成无线网络界面通过系统wpa_supplicant服务实现附近WiFi的搜索、列表显示与加密连接兼容WEP、WPA2等常见加密方式。密码输入处集成了软键盘并带有拼音输入法支持可减少实体键盘依赖适合触屏设备使用。压缩包共21个文件大小2.94MB主要包含5个cpp源文件、4个h头文件、3个ui界面文件、2个qrc资源文件和若干背景图片源码、界面布局、图标资源及工程配置分离清晰便于直接载入Qt工程查看运行。功能上支持一键刷新扫描最多同时显示20个WiFi热点连接对话框可自动弹出软键盘交互链路完整目前已有1011人浏览学习。对于不熟悉wpa_supplicant调用机制的开发者这份工程提供了从界面到系统调用的完整示例可在此基础上修改认证逻辑或UI也可作为Qt访问底层无线服务的参考模板。1. 用Qt做WiFi搜索连接与软键盘先回答这四个问题再动手如果你在工控触摸屏或自助终端上做过设置界面大概率逃不掉这个需求用户要像手机一样搜索周边WiFi、点一下连接、再在弹出的软键盘里输无线密码。标题里的“Qt无线连接 WIFI搜索显示连接 可弹出软键盘”是嵌入式Qt里最常见的“设置页三件套”。可真正动手会发现一个反直觉的事实Qt自己根本不提供无线扫描和连接接口QNetwork只管收发数据包。WiFi扫描靠系统的NetworkManager或Windows的Wlan APIQt只负责把结果画成界面软键盘也不是自带功能得接入Qt Virtual Keyboard或自绘键盘窗口。动手前先想清楚四个问题目标系统是Linux还是Windows、触摸屏分辨率多大、程序有没有root/管理员权限、要不要保存连接密码。这四个答案决定后面所有选型。2. 扫描周边WiFiQProcess调nmcli的最小实现与Windows Wlan API预案2.1 Qt没有无线管理接口为什么要走系统命令Qt的Network模块提供QNetworkInterface、QNetworkConfiguration这类类但它们是“看配置”用的不提供扫描热点、发起关联握手、保存密码这些真正的无线管理能力。Windows上可以调Wlan APILinux上有两条路NetworkManager的DBus接口和nmcli命令行。DBus是正路子协议稳定、信息量大但写起来很啰嗦——要引入QtDBus处理DBus信号、成员、路径一个小接口动辄几百行。nmcli是NetworkManager官方命令行工具把DBus操作全都包好了QProcess启动nmcli、读标准输出、按文本解析对“快速做成一个设置页”是最省事的路径。常见做法是以nmcli为主力把DBus留作需要实时信号比如系统网络变化提示的补充。我一般会把扫描、连接、查状态分别封装成独立的小类避免一个类里塞满QProcess回调。QProcess本身是异步的每次只发一个命令、用finished信号收结果比用阻塞式调用来得稳。Qt自带的命令行工具确实不少但WiFi扫描这件事不在Qt的职权范围内落到系统命令或系统API上才是正解。2.2 nmcli扫描命令的封装与带信号强度的列表解析最小可跑的扫描命令长这样nmcli -t -f SSID,SIGNAL,SECURITY dev wifi list-tterse模式不带表头、用冒号分隔字段-f只输出SSID、信号强度、加密方式三列。完整扫描结果通常有十几行一次跑下来不到一秒。注意有些网卡驱动扫描结果带乱码或空SSID初始版本先只解析非空SSID。下面是QProcess封装的最小代码#include QProcess #include QStringList #include QDebug void WifiScanner::startScan() { if (m_proc m_proc-state() QProcess::Running) { m_proc-terminate(); // 上一次还没跑完先停掉 m_proc-waitForFinished(200); } m_proc new QProcess(this); connect(m_proc, QProcess::finished, this, [this](int exitCode, QProcess::ExitStatus status) { if (exitCode ! 0 || status ! QProcess::NormalExit) { qWarning() m_proc-readAllStandardError(); return; } QByteArray out m_proc-readAllStandardOutput(); parseScanResult(QString::fromUtf8(out)); // 统一用UTF-8进业务层 }); // 关键用参数列表不要拼shell字符串 m_proc-start(nmcli, QStringList() -t -f SSID,SIGNAL,SECURITY dev wifi list); }解析函数void WifiScanner::parseScanResult(const QString raw) { m_networks.clear(); const QStringList lines raw.split(\n, Qt::SkipEmptyParts); for (const QString line : lines) { QStringList parts line.split(:); if (parts.size() 3) continue; QString ssid parts[0]; if (ssid.isEmpty()) continue; // 隐藏SSID或未解析到的先跳过 int signal parts[1].toInt(); // 0~100 QString security parts[2]; // 同一个SSID可能被多个热点重复上报按SSIDsecurity去重 bool exists std::any_of(m_networks.cbegin(), m_networks.cend(), [](const WifiInfo w) { return w.ssid ssid; }); if (!exists) { m_networks.append({ssid, signal, security}); } } emit scanFinished(); }用QProcess::start而不是QProcess::execute因为execute是阻塞式的扫描期间界面会卡死。finished回调里先读stderr再读stdout两个管道是独立的先读完一个再读另一个避免缓冲区互锁。如果想区分2.4G和5G把-f里加一个CHAN字段信道大于36的一般是5G这对后续按频段排序很有用。2.3 Windows触摸一体机的netsh/Wlan API后备方案如果目标设备是Windows触摸一体机或Windows板卡nmcli就没了。备选方案有两个。netsh容易上手netsh wlan show networks modebssid输出按“SSID 1 : xxx”分组下面带信号、加密等信息。netsh输出定位于人类可读解析时按SSID行和“信号 : xx%”配对。连接用netsh wlan connect name配置文件 ssidxxx但前提是先配置好WLAN配置文件对临时机器很不友好。量产更稳的是Wlan API链接wlanapi.lib在pro里写LIBS -lwlanapi。核心流程是WlanOpenHandle获取句柄WlanGetNetworkBssList拿到WLAN_BSS_LIST每个entry里都有dot11Ssid和信号强度连接用WlanConnect配合WLAN_CONNECTION_PARAMETERS指定SSID和密码。代码量比nmcli方案大不少但Windows下不需要额外服务依赖。如果只是给演示样机做一版netsh够用要量产直接上Wlan API编码和正则的坑会让你怀疑人生。2.4 隐藏SSID、扫描限频和DBus方案的取舍隐藏SSID的网络在扫描列表里默认不出现nmcli也扫不到。要做完整得在连接时直接传SSID让网卡定向关联或者让用户手动输入SSID再连。我一般不做隐藏网络扫描而是把“手动添加网络”做成一个输入框走连接命令的SSID直连逻辑。还有一个容易踩的点NetworkManager对扫描有限频连续两次scan太近第二次可能拿到上次的缓存结果甚至直接返回空。所以扫描按钮要做冷却两次扫描间隔至少5秒。如果单靠DBus的RequestScan触发网络级扫描这个限频是绕不开的。DBus方案本身没什么问题只是对产品来说QProcess读文本的调试成本远低于QtDBus的类型签名调试。除非你的产品需要被动感知系统网络变化比如断网弹提醒否则先用nmcli别一开始就上DBus。3. 显示WiFi列表并触发连接QListWidget、去重排序与异步状态机3.1 用QListWidget承载扫描结果并区分信号强度扫描结果拿到后显示层有个简单的选择QListWidget或QTableView。设置页一般只需要“点一项去连接”QListWidget就够。想显示更多字段时可以用QTableView但普通WiFi选择页展示太多列反而难看。我倾向QListWidget加自定义委托既保留简单性又能在item里画信号图标。每个item用Qt::UserRole存SSID和信号值显示文本只放SSID名称。信号强度不要靠Unicode字符“▂▄▆█”表示不同字体的显示宽度不一致在触摸屏上经常错位。更稳的做法是画一个四格信号条QPixmap存成iconvoid WifiPage::refreshList(const QVectorWifiInfo networks) { ui-listWidget-clear(); for (const WifiInfo w : networks) { QListWidgetItem *item new QListWidgetItem(ui-listWidget); item-setText(w.ssid); item-setData(Qt::UserRole, w.ssid); item-setData(Qt::UserRole 1, w.signal); item-setIcon(signalIconForLevel(w.signal)); // 自定义绘制或资源图 ui-listWidget-addItem(item); } }刷新时不要每次都全清全插。clear再addItem在数据量大时会闪触摸屏设备性能一般闪烁感更明显。正确做法是先对比新旧列表的SSID集合只增删差异项数据量小时用QSignalBlocker包住插入操作也能缓解。排序上按signal降序排列但把“已连接”的网络置顶。判断已连接用nmcli -t -f IN-USE,SSID,SIGNAL dev wifi listIN-USE字段为*的就是当前网。3.2 中文SSID和特殊字符在列表里的编码处理中文SSID是这功能第一个拦路虎。nmcli在绝大多数Linux发行版里输出UTF-8但Qt程序跑在LC_ALLC或locale缺失的环境时直接用QString::fromLocal8Bit会把它按ASCII/ANSI解释中文全变问号。反过来Windows的netsh输出是当前代码页GBK常见必须用QString::fromLocal8Bit。// Linux/nmcli场景 QString ssid QString::fromUtf8(lineBytes); // Windows/netsh场景 QString ssid QString::fromLocal8Bit(lineBytes);最坑的现场是显示正常但连不上显示层用UTF-8的QString连接层却把它用toLocal8Bit转回本地编码拼装命令本地locale是C时中文被转成问号。正确处理是显示层统一UTF-8 QString连接层直接把QString传给QProcess参数列表让QProcess按系统locale编码后再发给nmcli。想让nmcli固定吃UTF-8可以在命令前加LC_ALLC.UTF-8 nmcli ...但精简版系统未必有C.UTF-8 locale。程序启动时先setlocale(LC_ALL, )能减少一半这种玄学问题。3.3 发起连接的参数化命令与结果反馈连接命令的核心只有一行nmcli dev wifi connect SSID password 密码但代码里最安全的写法是参数列表不拼字符串void WifiConnector::connectTo(const QString ssid, const QString password) { QStringList args; args dev wifi connect ssid; if (!password.isEmpty()) { args password password; } m_proc-start(nmcli, args); }QProcess传入包含空格的密码不需要也不能再加引号。password为空说明可能是开放网络省略password参数即可。connect命令是同步等待NetworkManager完成关联的网络不好可能等十秒左右界面上必须给出“正在连接”的等待状态。nmcli返回“Device wlan0 successfully activated”表示成功非0退出码则表示失败stderr里带“Error: Connection activation failed”要把stderr原文显示出来别只给个“连接失败”。已保存过的网络不需要再传密码nmcli dev wifi connect SSID会自动用系统凭据但旧配置过期时反而会失败。这种场景要先用nmcli connection delete SSID删掉旧配置再连新密码否则界面会一直报密码错误。连接期间把列表和按钮都disabled防止用户在等待时又点别的WiFi触摸屏上重复点击引发的连锁bug很常见。连接返回后还有IP获取的等待问题。nmcli的connect返回时DHCP可能还没拿到地址立刻查IP会拿到空。常见做法是等待两秒后执行nmcli -g IP4.ADDRESS dev show wlan0循环轮询三次、间隔1秒拿到形如192.168.1.23/24的地址再更新界面。这个延后策略能省掉不少“连上了但界面说没连上”的假故障。4. 触摸屏弹出软键盘Qt Virtual Keyboard接入与自绘键盘兜底4.1 三种软键盘方案对比虚拟键盘模块、系统键盘、自绘控件触摸屏上没有物理键盘软键盘是刚需。三种主流方案边界非常清楚方案接入成本手感/定制性平台依赖典型场景Qt Virtual Keyboard低pro加模块高可换布局换皮肤模块要随Qt安装交叉编译要带上量产设备、纯Qt界面系统键盘matchbox-keyboard、onboard、TabTip极低QProcess启动低外观和Qt界面割裂各平台各自有快速原型、演示机自绘QWidget键盘中自己排键位最高完全可控无特殊布局、九宫格、小键盘Qt Virtual Keyboard对绝大多数场景是最优解焦点在QLineEdit上时自动弹出收起跟随焦点支持布局切换。系统键盘的问题在于它是独立窗口层级由窗口管理器决定和Qt界面互相遮挡或抢焦点在轻量级窗口管理器上经常出现有些嵌入式板子甚至直接跑Qt without WM系统键盘根本浮不出来。自绘键盘最稳但最费工要处理焦点事件、按键回填、shift状态、大小写锁定还得维护一个QWERTY布局。4.2 Qt Virtual Keyboard的最小接入配置先确认一个容易翻车的点Qt 5.15.2 msvc2019_64发行包默认不含Virtual Keyboard模块Ubuntu上安装Qt时要勾选或事后用apt装libqt5virtualkeyboard5。嵌入式交叉编译镜像里也得带上这个模块。pro文件里加一行QT core gui widgets virtualkeyboardmain.cpp里在创建QApplication前设置输入法模块#include QApplication #include QDebug int main(int argc, char *argv[]) { // 必须在QApplication构造前设置 qputenv(QT_IM_MODULE, QByteArray(qtvirtualkeyboard)); QApplication app(argc, argv); // 触摸屏场景下启用触摸事件合成 app.setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, true); // 业务代码... return app.exec(); }设置完这两处凡是QLineEdit获得焦点软键盘自动浮出。默认布局是英文要出中文键盘pro里加CONFIG lang-cn或在qmake配置开启。WiFi密码输入场景一般不需要中文候选框英文布局就够了。如果界面里还有搜索框要给用户输中文再用中文布局。qtvirtualkeyboard依赖Qt Quick和QML运行时。Widget工程里加QT virtualkeyboard会自动拉入Qt Quick相关模块。如果之前禁用了quick模块这里会报错。跑嵌入式板子时要确认模块编进了镜像里否则运行时QLineEdit一点就崩溃这是高频翻车点。排查方法很直接看Qt安装目录有没有virtualkeyboard的QML目录没有就回去补装。4.3 密码输入框的弹出克制与自绘兜底实现有了Virtual Keyboard下一步是“克制”很多设置页不止一个输入框用户只想点WiFi密码框时弹键盘结果点其他编辑框也弹出全键盘体验立刻变差。做法是给QLineEdit安装事件过滤器只在WiFi密码框的FocusIn里允许输入法启用bool WifiPage::eventFilter(QObject *obj, QEvent *e) { if (obj ui-pwdEdit e-type() QEvent::FocusIn) { // 交给输入法模块去弹Virtual Keyboard ui-pwdEdit-setAttribute(Qt::WA_InputMethodEnabled, true); } return QWidget::eventFilter(obj, e); }如果目标平台实在没有qtvirtualkeyboard自绘键盘是最后的保险。骨架如下class SoftKeyboard : public QWidget { Q_OBJECT public: explicit SoftKeyboard(QWidget *parent nullptr) : QWidget(parent, Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint) { // Qt::Tool 让键盘不抢焦点且能独立显示在顶层 QGridLayout *lay new QGridLayout(this); const QStringList keys {1,2,3,4,5,6,7,8,9,0, q,w,e,r,t,y,u,i,o,p, a,s,d,f,g,h,j,k,l, Shift,z,x,c,v,b,n,m,Backspace}; for (const QString k : keys) { QPushButton *btn new QPushButton(k, this); lay-addWidget(btn); } setFocusPolicy(Qt::NoFocus); } };自绘键盘要在按钮点击时把字符回填给当前焦点控件在构造函数里连接clicked信号并获取QApplication::focusWidget()。设置Qt::Tool时它不抢焦点主窗口的输入框仍是焦点持有者这样“点键盘按键”不会把焦点夺走。窗口位置固定屏幕底部第一版不要做跟随输入框的浮动先跑通再优化。Windows场景下还有一种省力方案用QProcess启动TabTip.exe触摸键盘路径在C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exeFocusIn时startDetached启动、FocusOut时收起但外观和Qt界面割裂一般只用于演示机。5. 避坑nmcli权限、密码特殊字符与软键盘遮挡的五个现场5.1 扫描结果一直为空先关WiFi开关再谈权限现象nmcli dev wifi list命令在终端手工执行正常Qt程序里QProcess跑同样参数输出为空或只有“No Wi-Fi device found”。原因两种情况最多。一是WiFi射频被关掉了nmcli radio wifi off状态下扫描命令不报错但输出空列表。二是NetworkManager服务异常或者程序跑了但硬件被rfkill锁住。nmcli扫描不需要root但硬件状态元数据由NetworkManager维护它服务异常时命令直接返回空。解决先查nmcli general status确认NetworkManager running再用nmcli radio wifi查看射频状态off则执行nmcli radio wifi on。排查顺序建议终端手工跑同一条命令确认命令本身没问题再看PATH里能不能找到nmcli最后看rfkill锁。嵌入式板子偶发rfkill状态锁定要写一条“WiFi被RF-Kill阻塞”的日志不要傻傻显示空白列表。5.2 SSID带空格和冒号导致解析错位现象列表里SSID显示成两截或连接时拿到的SSID缺了一半。原因nmcli -t模式用冒号分隔字段但SSID本身可以包含冒号。部分版本会自动转义字段内冒号为\:部分版本不转义直接split(:)时字段错位。空格不分割字段但如果解析时按空格二次拆分也会拆断。解决不直接split(:)先用line.replace(\\:, %COLON%)把转义冒号替换成占位符再按冒号切分切完把占位符还原成冒号。SSID为空时行首会多一个冒号parts数量会变多按大小过滤不够要按“第一个非空字段为SSID、第二个字段可转int为信号强度、第三为加密方式”来校验。这个坑在SSID叫“ABC:Guest”时必现没有例外。5.3 密码含$和空格被Shell解释掉现象连接命令返回“Error: passphrase must be 8..63 characters”但密码长度明明够或连接失败提示密码错误。原因QProcess::start带参数列表时不会经过Shell这是正确做法。但图省事把整个命令拼成一个大字符串再用QProcess::start系统会交给sh解释$、空格、引号全被处理一遍。WiFi密码里出现$、!、很常见一旦被Shell吃掉就得到错误密码。解决永远用QProcess的参数列表形式。字符串拼接这条路一旦走了转义是无底洞。开发期可以临时把密码明文打日志对照nmcli的报错确认传进去的值是完整的再用完即删。老代码改造时重点检查所有写start(nmcli dev wifi connect...)的地方。5.4 软键盘不弹或被窗口挡住现象Virtual Keyboard在桌面Linux上弹得出来到嵌入式触摸屏上就不弹了或者弹出后键盘浮在屏幕中间把密码框挡得严严实实。原因多数嵌入式板子的Qt镜像是精简过的没有打qtvirtualkeyboard模块或者窗口系统不含Wayland合成器。弹出的键盘默认靠主窗口定位全屏应用下主窗口没给键盘预留位置就会被盖住。解决先确认模块是否真实存在。qmlimportscanner能列出实际QML模块输入qmlimportscanner -rootPath 你的应用目录看有没有VirtualKeyboard。然后在main里设置qputenv(QT_VIRTUALKEYBOARD_HINT, desktop)或直接指定布局参数。遮挡问题主要靠布局规避密码输入框放在屏幕上半部分键盘在底部弹出时自然不遮。5.5 msvc2019_64的dependent路径报错现象Windows上用VS2022打开Qt工程编译一开始就报:-1: error: dependent ..\..\..\..\..\..\qt\5.15.2\msvc2019_64\include\qtwid... does not exist这类错误整个工程没法编译。原因这不是代码问题是.pro里QT模块名写错或内置了错误的Qt版本路径。qmake生成Makefile时把QT 的模块名映射到安装路径模块名不存在时生成一个不存在的dependent路径编译一步都走不下去。解决先检查.pro文件的QT 行widgets模块就是widgets不是qtwidgets。再确认Qt Kit选的是5.15.2 msvc2019_64而不是MinGW或32位版本。改完后删除构建目录里所有Makefile重新qmake必要时先“Run qmake”再“Rebuild All”。这种错在拷贝别人工程时特别多本质是缓存问题我的习惯是直接清掉shadow build目录去猜路径。Windows下切换编译器时还要注意依赖库也得分版本5.15.2 msvc2019_64和MinGW混用会让链接错误多到怀疑人生。6. 把连接做得再稳一点自动重连、DHCP等待与开机自启到了这个阶段扫描、显示、连接、软键盘都通了但现场连续跑三天后你会有几个额外需求设备重启后能不能自动连上已保存的WiFi信号变差时界面和状态能不能跟上客户环境是园区网连上WiFi还要过Portal认证此时界面怎么引导这三件事决定了功能从“能演示”走向“能交付”。自动重连NetworkManager本身有autoconnect机制nmcli con mod SSID connection.autoconnect yes可以开启但状态反馈到Qt界面还是要自己轮询。我一般把连接成功的SSID和密码写进QSettings程序启动时先nmcli con up SSID失败再用保存的密码走一遍连接流程。轮询用QTimer每3秒跑一次nmcli -t -f GENERAL.STATE,IP4.ADDRESS dev show wlan0状态变化时发信号更新标题栏图标。别用500ms刷新高频轮询会把自己带进坑里NetworkManager也会限频。DHCP等待之前提过连接返回后立刻查IP是空的。正确顺序是“连接成功 → 等状态变成connected → 再等1~2秒查IP4.ADDRESS → 界面显示IP”。IP一直拿不到多数是网段不对、Portal拦截或DHCP池满界面要明确提示“IP获取失败”而不是让客户以为连上了就万事大吉。这部分逻辑不复杂但一定要在真机上抓一份日志确认再写死。开机自启工控设备用systemd托管Qt程序但NetworkManager开机后0~5秒才完成扫描程序启动太早会扫到空的WiFi列表。systemd unit里加Afternetwork-manager.service程序里做启动后3秒延时的延迟扫描就能规避。Debian精简镜像还要确认nmcli命令在服务进程的PATH里可执行路径要写完整。最后说一个我的习惯做这套功能时先到开发板的Shell里把每条命令执行一遍、记录输出格式再回Qt代码里照抄解析。nmcli的版本差异坑过我很多次不同发行版对“已连接”标记的位置都有变化。曾经有同事直接照抄另一套代码的解析逻辑上线两天搜不到热点一查是版本号不同输出格式全变了。经过那一次之后我的自查清单里永远有一条在目标机器上先运行nmcli --version和完整命令输出确认字段结构后再写解析。这也是最有效的后悔药省下的大把排查时间远远超过写解析那几分钟。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑