PyQt+QML 实现个性化消息通知栏源码分享

发布时间:2026/10/20 11:03:17

PyQt+QML 实现个性化消息通知栏源码分享 PyQtQML 实现个性化消息通知栏源码分享一、效果展示二、源码分享1、工程结构2、库安装3、main.py4、main.qml三、实现原理1、整体架构2、数据模型设计3、消息类型与配色映射4、Canvas 绘制箭头丝带5、倒计时与自动移除机制6、交互逻辑7、小结一、效果展示二、源码分享1、工程结构2、库安装3、main.pyimportsysimportosfromPyQt6.QtCoreimportQUrlfromPyQt6.QtGuiimportQGuiApplicationfromPyQt6.QtQmlimportQQmlApplicationEnginedefmain():appQGuiApplication(sys.argv)engineQQmlApplicationEngine()qml_pathos.path.join(os.path.dirname(os.path.abspath(__file__)),main.qml)engine.load(QUrl.fromLocalFile(qml_path))ifnotengine.rootObjects():print(错误无法加载 QML 文件,filesys.stderr)sys.exit(-1)sys.exit(app.exec())if__name____main__:main()4、main.qmlimportQtQuickimportQtQuick.WindowimportQtQuick.ControlsimportQtQuick.Layouts ApplicationWindow{id:root visible:true width:800height:600title:消息提示栏color:#f1f5f9QtObject{id:messageType readonlypropertyintinfo:0readonlypropertyintwarning:1readonlypropertyinterror:2}ListModel{id:messagesModel Component.onCompleted:{append({id:1,text:这是一条信息提示,type:messageType.info,remaining:5000,duration:5000})append({id:2,text:这是一条警告提示,type:messageType.warning,remaining:5000,duration:5000})append({id:3,text:这是一条故障提示,type:messageType.error,remaining:5000,duration:5000})}}propertyintmsgCounter:3function getColor(type){switch(type){casemessageType.info:return#3b82f6casemessageType.warning:return#f59e0bcasemessageType.error:return#ef4444default:return#3b82f6}}function getProgressColor(type){switch(type){casemessageType.info:return#bfdbfecasemessageType.warning:return#fde68acasemessageType.error:return#fecacadefault:return#bfdbfe}}function getIcon(type){switch(type){casemessageType.info:returncasemessageType.warning:return⚡casemessageType.error:return❌default:return}}function addMessage(text,type,durationMs5000){msgCountermessagesModel.append({id:msgCounter,text:text,type:type,remaining:durationMs,duration:durationMs})}function closeMessage(msgId){for(var i0;imessagesModel.count;i){if(messagesModel.get(i).idmsgId){messagesModel.remove(i)break}}}ColumnLayout{anchors.fill:parent spacing:0Item{Layout.preferredHeight:50Layout.fillWidth:true}Rectangle{Layout.preferredHeight:60Layout.fillWidth:true color:transparentListView{id:messagesListView anchors.fill:parent anchors.margins:8clip:true model:messagesModel orientation:ListView.Horizontal spacing:-10//尖角重叠效果 layoutDirection:Qt.LeftToRight//添加消息后自动滚动到最右边 onCountChanged:{Qt.callLater(function(){positionViewAtEnd()})}//右侧留白补偿最后一个箭头的12px延伸 footer:Item{width:12height:messagesListView.height}delegate:Item{id:messageItem width:160height:messagesListView.heightpropertyintmsgId:model.idpropertystring msgText:model.textpropertyintmsgType:model.typepropertyreal progress:model.duration0? model.remaining/model.duration:0Canvas{id:ribbonCanvas width:parent.width12height:parent.height onPaint:{var ctxgetContext(2d)ctx.clearRect(0,0,width,height)ctx.beginPath()ctx.moveTo(12,0)ctx.lineTo(width-12,0)ctx.lineTo(width,height/2)ctx.lineTo(width-12,height)ctx.lineTo(12,height)ctx.lineTo(24,height/2)ctx.closePath()ctx.fillStylegetColor(msgType)ctx.fill()ctx.strokeStyle#ffffff30ctx.lineWidth1ctx.stroke()}Rectangle{x:24y:parent.height-height-3width:(parent.width-48)*messageItem.progress height:2color:getProgressColor(msgType)}}RowLayout{anchors.fill:parent anchors.leftMargin:28anchors.rightMargin:28spacing:4Text{text:getIcon(msgType)font.pixelSize:14Layout.alignment:Qt.AlignVCenter}Text{text:msgText color:#fffffffont.pixelSize:12elide:Text.ElideRight Layout.fillWidth:true Layout.alignment:Qt.AlignVCenter wrapMode:Text.WordWrap}}MouseArea{anchors.fill:parent cursorShape:Qt.PointingHandCursor onClicked:{closeMessage(msgId)}}}}}Item{Layout.fillWidth:true Layout.fillHeight:true Column{anchors.centerIn:parent spacing:20Row{anchors.horizontalCenter:parent.horizontalCenter spacing:16Button{width:150height:50text: 添加提示palette.button:#3b82f6palette.buttonText:#050505onClicked:{addMessage(这是一条信息提示,messageType.info)}}Button{width:150height:50text:⚡ 添加警告palette.button:#f59e0bpalette.buttonText:#050505onClicked:{addMessage(这是一条警告提示,messageType.warning)}}Button{width:150height:50text:❌ 添加故障palette.button:#ef4444palette.buttonText:#050505onClicked:{addMessage(这是一条故障提示,messageType.error)}}}}}}Timer{id:countdownTimer interval:100repeat:true running:true triggeredOnStart:true onTriggered:{var toRemove[]for(var i0;imessagesModel.count;i){var msgmessagesModel.get(i)var newRemainingmsg.remaining-100if(newRemaining0){toRemove.push(i)}else{messagesModel.setProperty(i,remaining,newRemaining)}}for(var jtoRemove.length-1;j0;j--){messagesModel.remove(toRemove[j])}}}}三、实现原理1、整体架构本项目采用PyQt6 QML混合架构Python 负责应用生命周期与 QML 引擎加载QML 负责界面渲染与交互逻辑。两者通过QQmlApplicationEngine桥接Python 侧只做最少的引导工作所有界面逻辑都写在 QML 中便于快速迭代和热调试。main.py 入口创建 QGuiApplication创建 QQmlApplicationEngine加载 main.qmlApplicationWindow 初始化ListModel 预置 3 条消息Timer 驱动倒计时Canvas 绘制箭头丝带点击关闭 / 按钮添加2、数据模型设计消息列表使用 QML 内置的ListModel作为数据源每条消息包含 5 个字段字段类型作用idint消息唯一标识用于定位删除textstring消息显示文本typeint消息类型info/warning/errorremainingint剩余存活时间毫秒durationint总存活时间毫秒remaining与duration的比值即为进度条的实时进度由Timer每 100ms 递减更新实现平滑的倒计时效果。3、消息类型与配色映射通过QtObject定义三个只读常量作为枚举配合三个映射函数实现「类型 → 视觉样式」的转换getColor(type)返回主色丝带填充色info 蓝色、warning 橙色、error 红色getProgressColor(type)返回进度条浅色与主色同色系但更淡getIcon(type)返回对应 emoji 图标 / ⚡ / ❌。这种「常量 映射函数」的设计避免了在 delegate 中写大量if/else新增类型时只需扩展QtObject和三个函数即可。4、Canvas 绘制箭头丝带每个消息项的核心视觉是「箭头丝带」使用 QMLCanvas2D 上下文绘制画布宽度比消息项宽 12px为右侧箭头尖角留出延伸空间beginPath()后依次连线左上角 → 右上角 → 右侧中点箭头尖→ 右下角 → 左下角 → 左侧中点内凹尖角→ 闭合用getColor(msgType)填充再用半透明白色描边形成立体感进度条作为Rectangle叠加在画布底部宽度按progress比例动态计算。左侧内凹 12px、右侧外凸 12px 的设计配合ListView的spacing: -10让相邻消息产生「箭头咬合」的重叠效果。5、倒计时与自动移除机制核心逻辑由Timer驱动Timer { id: countdownTimer interval: 100 repeat: true running: true triggeredOnStart: true onTriggered: { var toRemove [] for (var i 0; i messagesModel.count; i) { var msg messagesModel.get(i) var newRemaining msg.remaining - 100 if (newRemaining 0) { toRemove.push(i) } else { messagesModel.setProperty(i, remaining, newRemaining) } } for (var j toRemove.length - 1; j 0; j--) { messagesModel.remove(toRemove[j]) } } }关键点先收集后删除遍历时先记录待删除下标再倒序删除避免正序删除导致索引错位倒序删除从后往前remove保证前面的索引不受影响进度条联动remaining递减后delegate 中progress remaining / duration自动重算进度条随之缩短。6、交互逻辑点击消息关闭delegate 内MouseArea捕获点击调用closeMessage(msgId)按 id 从ListModel中移除按钮添加消息三个按钮分别调用addMessage(text, type)内部自增msgCounter生成新 id并追加到ListModel自动滚动到最新ListView监听onCountChanged用Qt.callLater延迟调用positionViewAtEnd()确保新消息出现后自动滚到最右侧。7、小结整个通知栏的核心设计可以概括为三点数据驱动视图ListModel作为唯一数据源delegate 只负责渲染增删改都通过模型操作Canvas 定制视觉用 2D 绘制实现普通控件难以做到的箭头丝带造型Timer 统一驱动所有消息共享一个 100ms 定时器批量递减、批量清理性能开销极小。r)
延伸阅读

更多相关文章

2026/10/20 11:03:17

声纹识别Web身份认证实战:从MFCC特征提取到前后端落地

简介:基于声纹识别的Web身份认证系统,面向需要为企业或业务系统提供安全、便捷登录方案的技术人员,也适合信息安全、人工智能方向的学生作为课设参考。方案以人声生物特征代替传统口令,用户无需记忆复杂密码,且不涉及人…

2026/10/20 10:58:07

2026排课调课在教培系统后台怎么设置

排课是教培机构教务最头疼的事。老师时间冲突、教室被占、班级学员时间对不上,Excel拉来拉去还是撞课。2026年了,后台排课系统能按空闲智能排课、自动检测冲突,调课在课节里改、靠排课和上课提醒触达家长,这篇讲清楚怎么在后台把排…

2026/10/20 10:58:07

C++数据结构实战:从链表到哈希表,掌握选型与实现核心

数据结构是一门非常容易被低估的课。很多同学觉得它不就是一堆链表、树、图吗,背一背就完事,结果一到手写代码就卡住,要么指针把你绕晕,要么递归根本看不出返回值在干嘛。也有朋友工作两三年后回来补课,才发现以前写的…

2026/10/20 12:08:31

SSM+Vue电商网站开发实战:从数据库设计到前后端分离部署

1. 项目概述与需求拆解1.1 这是个什么项目看到这个标题,我猜不少人第一反应是:这就是个典型的毕业设计选题。确实,SSM Vue的组合在近年来已经成了高校软件工程类项目的事实标配,但真正把它做好、做完整,并不是把框架搭…

2026/10/20 12:08:31

面向对象六大原则:从SOLID到迪米特法则的代码重构指南

1. 从烂代码的痛说起接手过别人项目的人,大多经历过这种崩溃瞬间:一个类几千行,改一个需求要顺藤摸瓜牵动十几个文件,新增一个支付方式恨不得把原来的逻辑全推翻重写。这时候你会听到一个词——面向对象六大原则。说实在的&#x…

2026/10/20 12:08:31

Qt词法分析器实战:带GUI的可调试编译原理教学工具

简介:本资源是一份面向计算机专业本科生、编译原理初学者及Java GUI实践者的词法分析器教学项目,聚焦编译原理核心环节——词法分析的完整实现与可视化验证。项目采用Java语言开发,基于Swing构建交互式图形界面,支持在MyEclipse环…

2026/10/20 12:03:30

VeRL框架PPO微调详解:use_pf_ppo参数的作用与调参实践

做过大模型对齐训练的朋友应该都清楚,强化学习微调(RL Finetuning)和传统的 SFT 完全不是一回事。SFT 只要算 loss、回传梯度就行,但 RL 要把生成、奖励计算、优势估计、策略更新整个闭环串起来,任何一个环节出问题&am…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/20 1:05:37

单片机C运行时从启动到多任务:中断安全与共享资源冲突

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

GCC编译四步详解:预处理、编译、汇编、链接实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

FIW线是什么?FEP注塑线与普通氟塑料线的区别及应用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑