当前位置: 首页 > news >正文

Qt网络开发之Qt内嵌浏览器(其二)基于WebEngine实现(QML版)

前言

上一节在QWidget中用传统web引擎模块实现了一版浏览器。于是我心想又用QML来实现一遍,看效果怎样。于是我用ai帮忙写了段代码,纯qml的,顺便记录一下学习过程和最终代码。

一、原理模块和设计思路

这里我们用到了qml的QtWebEngine模块,基础组件是WebEngineView,相当于一个网页页面。给WebEngineView正常设置一个url,就能打开网址了。如果是新增弹页,则需要监听onNewWindowRequested信号处理,然后再新增页面。不然的话,qml默认是不处理新增弹页的哦。

1.1 整体架构
Header(ToolBar) - 导航控制
[←] [→] [↻] [地址栏_______] [Go] [+] [×]
TabBar- 标签页切换
WebView Container- 网页显示区 (多个 WebEngineView,只有一个 visible=true) │ │

1.2 三大核心模块
数据层 ListModel 存储每个标签的 URL、标题
控制层 TabBar + 自定义函数 标签切换、新建、关闭
视图层 Repeater + WebEngineView 网页渲染和显示

1.3 数据流设计

二、代码演示

2.1 完整代码

importQtQuick2.15importQtQuick.Window2.15importQtQuick.Controls2.15importQtWebEngine1.15importQtQuick.Layouts1.15ApplicationWindow{id:root visible:truewidth:1024height:768title:webViewContainer.currentTitle+" - Qt Browser"// ==================== 顶部工具栏 ====================header:ToolBar{RowLayout{anchors.fill:parent spacing:5anchors.margins:5// 导航按钮Button{text:"←"enabled:currentView&&currentView.canGoBack onClicked:currentView.goBack()}Button{text:"→"enabled:currentView&&currentView.canGoForward onClicked:currentView.goForward()}Button{text:"↻"enabled:currentView onClicked:currentView.reload()}// 地址栏TextField{id:urlField Layout.fillWidth:trueplaceholderText:"输入网址..."onAccepted:loadUrl(text)}// Go 按钮Button{text:"Go"enabled:urlField.text.trim().length>0onClicked:loadUrl(urlField.text)}Item{Layout.preferredWidth:10;}// 标签管理Button{text:"+";onClicked:addNewTab("about:blank")}Button{text:"×"enabled:tabModel.count>0onClicked:closeCurrentTab()}}}// ==================== 数据模型 ====================ListModel{id:tabModel ListElement{url:"https://www.baidu.com";title:"百度"}}// ==================== 主内容区 ====================ColumnLayout{anchors.fill:parent spacing:0// 标签栏TabBar{id:tabBar Layout.fillWidth:trueheight:35currentIndex:activeTabIndex onCurrentIndexChanged:{activeTabIndex=currentIndexupdateCurrentView()}Repeater{model:tabModel delegate:TabButton{text:model.title||"新标签页"width:150contentItem:RowLayout{Label{text:parent.parent.text elide:Text.ElideRight Layout.fillWidth:true}Button{text:"×"flat:truepadding:0Layout.preferredWidth:20Layout.preferredHeight:20background:Rectangle{color:parent.hovered?"#ddd":"transparent"radius:4}onClicked:{closeTab(index)mouse.accepted=true}}}onClicked:tabBar.currentIndex=index}}}// 网页容器Item{id:webViewContainer Layout.fillWidth:trueLayout.fillHeight:trueproperty string currentTitle:""Repeater{id:webViewRepeater model:tabModel delegate:Item{anchors.fill:parent visible:index===activeTabIndex WebEngineView{id:webView anchors.fill:parent url:model.url// URL 变化 - 同步地址栏和模型onUrlChanged:{tabModel.setProperty(index,"url",url.toString())if(index===activeTabIndex){urlField.text=url.toString()}}// 标题变化 - 更新标签和窗口标题onTitleChanged:{tabModel.setProperty(index,"title",title)if(index===activeTabIndex){webViewContainer.currentTitle=title}}// 拦截新窗口请求 - 在新标签打开onNewWindowRequested:function(request){addNewTab(request.requestedUrl.toString())request.reject()}// 加载状态onLoadingChanged:{if(loadRequest.status===WebEngineView.LoadFailedStatus){console.log("加载失败:",loadRequest.errorString)}}}}}}}// ==================== 全局属性 ====================propertyintactiveTabIndex:0property var currentView:null// ==================== 核心函数 ====================// 更新当前视图引用functionupdateCurrentView(){if(webViewRepeater.count>0&&activeTabIndex<webViewRepeater.count){var item=webViewRepeater.itemAt(activeTabIndex)if(item&&item.children.length>0){currentView=item.children[0]urlField.text=currentView.url.toString()webViewContainer.currentTitle=currentView.title}}}// 加载网址functionloadUrl(text){if(!currentView){updateCurrentView()if(!currentView)return}var cleanText=text.trim()if(!cleanText.startsWith("http://")&&!cleanText.startsWith("https://")&&!cleanText.startsWith("qrc:/")){cleanText="https://"+cleanText}currentView.url=cleanText urlField.text=cleanText currentView.forceActiveFocus()}// 新建标签functionaddNewTab(urlStr){tabModel.append({"url":urlStr,"title":"加载中..."})tabBar.currentIndex=tabModel.count-1}// 关闭标签functioncloseTab(index){if(tabModel.count<=1){tabModel.setProperty(0,"url","about:blank")tabModel.setProperty(0,"title","新标签页")return}tabModel.remove(index)if(index===tabBar.currentIndex&&index>=tabModel.count){tabBar.currentIndex=tabModel.count-1}elseif(index<tabBar.currentIndex){tabBar.currentIndex--}}functioncloseCurrentTab(){closeTab(tabBar.currentIndex)}// ==================== 初始化 ====================Component.onCompleted:{updateCurrentView()}}

2.2 代码要点说明

代码段作用关键点
ListModel存储标签数据数据驱动 UI 更新
visible: index === activeTabIndex控制页面显示代替 StackLayout
webViewRepeater.itemAt()获取页面对象解决 currentItem 为空
onNewWindowRequested拦截弹窗request.reject() 必须调用
onUrlChanged / onTitleChanged双向同步保持数据一致性

三、效果展示

3.1 功能清单

功能状态说明
多标签管理支持新建、切换、关闭
地址栏同步标签切换/网页加载自动更新
导航控制前进、后退、刷新
弹窗拦截新窗口请求转为新标签
自动协议补全输入域名自动加 https://
标题显示窗口标题随网页变化

3.2 界面预览

3.3 使用流程
启动 → 默认打开百度首页
输入网址 → 地址栏输入后按 Enter 或点 Go
新建标签 → 点 + 按钮
切换标签 → 点击标签栏按钮
关闭标签 → 点标签上的 × 或工具栏 ×
点击外链 → 自动在新标签打开

四、总结

4.1 收获
QML 数据绑定 - 深刻理解了 Model-View 模式在 QML 中的实现
Repeater 用法 - 学会了如何正确访问 Repeater 生成的子项
WebEngine 集成 - 掌握了网页加载、弹窗拦截等核心功能
调试技巧 - console.log() 是 QML 调试的好朋友

4.2 可扩展方向
历史记录(用 ListElement 存储)
书签管理(持久化到文件)
下载管理(WebEngineDownloadItem)
隐私模式(WebEngineProfile 配置)
广告拦截(WebEngineUrlRequestInterceptor)

http://www.jsqmd.com/news/524596/

相关文章:

  • 钉钉小程序map组件全解析:从基础配置到高级功能(含v-bind使用技巧)
  • 如何用扩散模型实现多聚焦图像融合?FusionDiff论文实战解析(附代码)
  • 2026年 三菱PLC模块推荐榜:CCLink I/O模块专业解析,工业自动化核心组件实力厂家深度测评 - 品牌企业推荐师(官方)
  • ARM架构下Device与Normal内存类型实战解析:如何避免踩坑?
  • 普源精电DHO系列示波器选购指南:从学生党到工程师的完整对比
  • OpenClaw 自动化策略与金融工具应用指南
  • BLE协议栈LL层实战:手把手解析广播包与数据包结构(附Wireshark抓包分析)
  • 设计素材同步太慢?2026适合设计团队的 5 款企业网盘深度实测与选型指南
  • OpenAI插件实战:用Python Flask快速搭建一个天气查询插件(含完整API代码)
  • 动平衡材料实力品牌榜:平衡泥品牌/平衡泥公司/平衡泥厂家/动平衡泥/平衡泥厂商/平衡泥工厂/高比重平衡胶泥/平衡土/选择指南 - 优质品牌商家
  • 别再死记硬背了!用Python字典思维轻松玩转MMDetection配置文件
  • AI写教材新方法!低查重秘诀,让你的教材生成更高效!
  • 虾皮订单数据高效导出技巧与实战指南
  • Kettle实战100篇 第11篇 JavaScript脚本中日志级别与调试技巧
  • Doris性能调优必看:FE查询优化器与BE执行引擎的7个黄金配合法则
  • 分享一个基于MCU实现智能陪伴时钟的项目
  • 提示内容用户体验升级:架构师用7步让用户“主动配合”
  • 避开这些坑!VRPTW建模中5个常见CPLEX报错解决方案
  • 20252201 吕厚德
  • 当波束成形遇上导向矢量失配:特征子空间投影法如何成为你的‘纠偏’利器?
  • 为什么关闭Git的SSL验证是下策?安全工程师教你正确处理证书错误
  • 华为OD机试双机位C卷-虚拟文件系统(C/C++/Py/Java/Js/Go)
  • 干货来了:千笔·降AIGC助手,开源免费降重首选!
  • HY-Motion 1.0保姆级教程:日志分析+性能监控+错误定位全链路
  • 2026年 辐射空调系统厂家推荐排行榜,大平层/别墅/豪宅/办公室/商场/酒店/医院/实验室/数据中心辐射空调,毛细管辐射空调系统专业定制 - 品牌企业推荐师(官方)
  • StoneL QX2VCK03HDM 阀门位置开关:双通道反馈与工业物联网(IIoT)集成应用
  • 代码归 Git,文档归哪里?研发团队协作云存储选型的 5 个关键真相
  • 【全网最全】Neles EN33A05DM 限位开关:从底层架构到工业 4.0 集成的深度技术解析
  • 2026航空航天节能半自动清洗机优质推荐榜:全自动超声波清洗机、医用清洗机、医用清洗机、半自动超声波清洗机、单槽超声波清洗机选择指南 - 优质品牌商家
  • 海康VisionMaster实战笔记:从零搭建字符识别与TCP通信方案