资讯动态

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

发布时间:2026/10/21 21:21:16 来源:尧图企业网站定制
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)

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑