egui 关闭确认对话框实战:用 close_requested 与 CancelClose 拦截窗口退出

发布时间:2026/9/10 12:27:38

egui 关闭确认对话框实战:用 close_requested 与 CancelClose 拦截窗口退出 egui 关闭确认对话框实战用 close_requested 与 CancelClose 拦截窗口退出【免费下载链接】eguiegui: an easy-to-use immediate mode GUI in Rust that runs on both web and native项目地址: https://gitcode.com/GitHub_Trending/eg/egui导读在使用 Rust egui/eframe 开发桌面应用时常常需要在用户点击窗口关闭按钮后弹出确认框避免误关导致未保存数据丢失。本篇文章以仓库中 examples/confirm_exit 官方示例为主体完整讲解如何监听视口viewport的关闭请求、通过ViewportCommand::CancelClose取消关闭并弹窗询问、再通过ViewportCommand::Close真正退出这一完整流程并深入到 eframe 与 egui-winit 源码说明这套机制在底层是如何被处理的。读完本文你将能独立实现关闭前确认未保存工作提醒等常见交互。示例概览与运行方式该示例位于 examples/confirm_exit核心文件为 src/main.rs。它实现了一个 320×240 的小窗口标题为 Confirm exit窗口中只有一行文字 Try to close the window当你点击窗口右上角的关闭按钮时不会立刻退出而是弹出 Do you want to quit? 对话框提供 No 与 Yes 两个按钮。运行方式非常简单在仓库根目录执行cargo run -p confirm_exit示例的 Cargo.toml 显示它依赖工作区中的eframe启用默认特性并额外启用了__screenshot特性用于 CI 中通过EFRAME_SCREENSHOT_TO环境变量导出截图仓库中的 screenshot.png 即由此生成。核心机制一次关闭被拆成两半要理解这个示例关键是明白 egui 对关闭窗口的处理方式关闭请求close request与真正关闭close是分离的两件事。用户点击窗口关闭按钮时窗口系统发出关闭请求egui 将这一请求记录为视口信息中的close_requested标志应用可以在每一帧读取它应用可以选择取消这次关闭发送ViewportCommand::CancelClose也可以选择放行什么都不做或发送ViewportCommand::Close只有未被取消的关闭请求才会真正导致窗口销毁、程序退出。在 crates/egui/src/data/input/viewport_info.rs 中close_requested()的实现如下/// This viewport has been told to close. /// /// If this is the root viewport, the application will exit /// after this frame unless you send a /// [crate::ViewportCommand::CancelClose] command. /// /// If this is not the root viewport, /// it is up to the user to hide this viewport the next frame. pub fn close_requested(self) - bool { self.events.contains(ViewportEvent::Close) }注意注释中强调的两点对于根视口即主窗口应用会在该帧结束后退出除非发送了CancelClose对于子视口额外的原生窗口则需要用户自行决定是否隐藏它。对应地crates/egui/src/viewport.rs 中定义了两个方向相反的视口命令pub enum ViewportCommand { /// Request this viewport to be closed. /// /// For the root viewport, this usually results in the application shutting down. /// For other viewports, the [crate::ViewportInfo::close_requested] flag will be set. Close, /// Cancel the closing that was signaled by [crate::ViewportInfo::close_requested]. CancelClose, // ... }ViewportCommand是 egui 输出给后端eframe/winit的命令应用通过Context::send_viewport_cmd发送见 crates/egui/src/viewport.rs 的说明。逐步拆解示例代码1. 应用状态与入口#[derive(Default)] struct MyApp { show_confirmation_dialog: bool, allowed_to_close: bool, }示例用两个布尔值管理状态show_confirmation_dialog控制确认弹窗是否可见allowed_to_close记录用户是否已经明确同意关闭即点击过 Yes。main函数中通过eframe::NativeOptions设置初始窗口大小并调用eframe::run_native启动应用fn main() - eframe::Result { env_logger::init(); // Log to stderr (if you run with RUST_LOGdebug). let options eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; eframe::run_native( Confirm exit, options, Box::new(|_cc| Ok(Box::MyApp::default())), ) }2. 在每一帧检查关闭请求ui回调中在绘制完主面板后立即检查用户是否请求关闭if ui.input(|i| i.viewport().close_requested()) { if self.allowed_to_close { // do nothing - we will close } else { ui.send_viewport_cmd(egui::ViewportCommand::CancelClose); self.show_confirmation_dialog true; } }这里的逻辑是一旦检测到close_requested()为真说明用户点了关闭按钮如果allowed_to_close为真说明此前已确认过什么都不做关闭请求会被放行程序正常退出否则通过send_viewport_cmd(egui::ViewportCommand::CancelClose)取消本次关闭并置show_confirmation_dialog true弹出确认对话框。ui.input(...)是 egui 中安全读取当前帧输入的方式其中i.viewport()返回 ViewportInfoclose_requested()即为上文所述的关闭标志。3. 弹出确认对话框if self.show_confirmation_dialog { egui::Window::new(Do you want to quit?) .collapsible(false) .resizable(false) .show(ui.ctx(), |ui| { ui.horizontal(|ui| { if ui.button(No).clicked() { self.show_confirmation_dialog false; self.allowed_to_close false; } if ui.button(Yes).clicked() { self.show_confirmation_dialog false; self.allowed_to_close true; ui.send_viewport_cmd(egui::ViewportCommand::Close); } }); }); }点击No关闭对话框show_confirmation_dialog false并重置allowed_to_close false让下次关闭仍然需要确认点击Yes关闭对话框、置allowed_to_close true然后主动发送ViewportCommand::Close再次发起关闭请求。由于此时allowed_to_close已经为真下一帧检查到关闭请求时会放行程序随之退出。之所以点击 Yes 后要重新发送ViewportCommand::Close是因为用户第一次点关闭按钮时发出的请求已经被CancelClose取消了必须再发一次才能让窗口真正关闭。这正是该示例最精妙也最容易被忽略的一点。源码级原理eframe 如何处理关闭请求理解了应用层代码我们再深入 eframe 与 egui-winit 看这套机制如何落地。eframe 的 update 流程在 crates/eframe/src/native/epi_integration.rs 的update方法中eframe 先读取本次输入的关闭标志再运行 egui UI最后检查输出中是否包含取消命令let close_requested raw_input.viewport().close_requested(); // ... let full_output self.egui_ctx.run_ui(raw_input, |ui| { /* app 的 logic 与 ui */ }); if is_root_viewport close_requested { let canceled full_output.viewport_output[ViewportId::ROOT] .commands .contains(egui::ViewportCommand::CancelClose); self.handle_close_request(canceled); }handle_close_request的实现在同一文件的 L403-L409fn handle_close_request(mut self, canceled: bool) { if canceled { log::debug!(Closing of root viewport canceled with ViewportCommand::CancelClose); } else { log::debug!(Closing root viewport (ViewportCommand::CancelClose was not sent)); self.close true; } }可以看到决定窗口是否关闭的唯一依据就是本帧输出到根视口的命令里是否包含ViewportCommand::CancelClose。如果包含关闭被取消否则设置内部标志close true驱动主循环退出。也就是说即使示例代码不发送CancelClose只要应用在收到关闭请求的那一帧什么都不做窗口照样会关闭——取消关闭是主动动作必须显式发送命令。egui-winit 侧的事件来源关闭请求最初来自窗口系统。在 crates/egui-winit/src/lib.rs 附近winit 的WindowEvent::CloseRequested被转换为 egui 的视口事件最终体现为ViewportInfo中的ViewportEvent::Close从而被close_requested()读取。而ViewportCommand::Close与CancelClose这两个命令在 crates/egui-winit/src/lib.rs 的处理也值得注意ViewportCommand::Close { info.events.push(egui::ViewportEvent::Close); } ViewportCommand::CancelClose { // Need to be handled elsewhere }Close会把关闭事件重新推入视口事件列表再次触发close_requested而CancelClose在此处无需处理——它由上一节介绍的 eframeupdate流程在更高层看到并决定是否放行。这正是示例中点击 Yes 后再次发送 Close能被正常响应、且allowed_to_close标志能在下一帧生效的原因。与旧 API 的对比在 crates/eframe/CHANGELOG.md 中记录了本机制的演进App::on_close_eventhas been replaced withctx.input(|i| i.viewport().close_requested())andctx.send_viewport_cmd(ViewportCommand::CancelClose).同时crates/eframe/src/epi.rs 中App::on_exit的文档也明确提示If you need to abort an exit checkctx.input(|i| i.viewport().close_requested())and respond withegui::ViewportCommand::CancelClose.也就是说on_exit是程序确定退出前的最后一个钩子通常用于保存状态、释放资源而关闭拦截要放在每一帧的 UI 回调中完成两者职责不同可以组合使用用close_requestedCancelClose拦截并询问确认退出后由on_exit做收尾保存。实战扩展从确认退出到未保存工作提醒示例展示的是最简形态你可以在此基础上演化出更实用的场景1. 未保存数据检测。把allowed_to_close换成是否有未保存修改的判断例如if ui.input(|i| i.viewport().close_requested()) { if self.document.is_saved() { // 放行 } else { ui.send_viewport_cmd(egui::ViewportCommand::CancelClose); self.show_save_dialog true; } }2. 多视口应用。对于通过Context::show_viewport创建的子视口关闭请求同样体现在其close_requested标志上由于子视口关闭不会自动退出整个程序处理方式可以是确认后隐藏子视口见 crates/egui/src/context.rs 中关于子视口关闭标志的说明。3. 键盘快捷键触发关闭。也可以主动向视口发送ViewportCommand::Close来模拟关闭请求如响应CtrlW走完全相同的确认流程实现方式与示例中 Yes 按钮的代码一致。注意事项与平台差异必须在发出关闭请求的同一帧内决定是否取消egui 是立即模式immediate modeCancelClose的判定以该帧输出为准因此不能异步延时后才决定如果弹出的是模态确认框通常要保证在用户做出选择前后续每一帧都检查并CancelClose直到用户点击按钮。allowed_to_close标志的生命周期示例在用户点 Yes 后先置标志再发Close命令依赖下一帧的检查来放行。若你的确认流程跨越多帧例如确认框异步等待需要确保每次收到close_requested且尚未确认时都发送CancelClose否则窗口会提前退出。Web 端行为本示例针对桌面原生窗口eframe native。Web 端浏览器标签页的关闭由浏览器接管egui 无法可靠拦截close_requested机制主要适用于原生视口场景。调试日志示例启用了env_logger用RUST_LOGdebug cargo run -p confirm_exit运行时可以在 stderr 中看到 eframe 输出的 Closing of root viewport canceled ... / Closing root viewport ... 日志crates/eframe/src/native/epi_integration.rs有助于排查关闭流程是否按预期工作。小结confirm_exit示例虽然只有几十行代码却完整演示了 egui 视口关闭机制的三个关键环节读取关闭请求close_requested()→ 取消关闭ViewportCommand::CancelClose→ 确认后重新发起ViewportCommand::Close。从源码看eframe 在每一帧结束后根据根视口命令中是否含CancelClose决定是否真正退出epi_integration.rs这既是该示例正确运行的基础也是你在自己应用中实现关闭拦截、未保存提醒等功能的底层依据。相关完整源码与运行说明可继续查看 examples/confirm_exit以及 crates/eframe/src/epi.rs 中Apptrait 的接口文档。【免费下载链接】eguiegui: an easy-to-use immediate mode GUI in Rust that runs on both web and native项目地址: https://gitcode.com/GitHub_Trending/eg/egui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/10 12:27:38

双目视觉测量全流程:从相机标定到工件变形量计算

简介:本资源是一套面向本科毕业设计、课程设计及工业视觉项目开发的双目视觉测量完整实现方案,聚焦工件三维形变量化分析这一典型机器视觉应用。项目融合OpenCV图像处理、MATLAB相机标定工具箱与C高性能计算,系统完成图像预处理、特征点提取、…

2026/9/10 12:22:37

CVAT安装教程:3步在Docker里跑起视觉标注平台

CVAT安装教程:3步在Docker里跑起视觉标注平台 【免费下载链接】cvat Computer Vision Annotation Tool (CVAT) is a leading platform for building high-quality visual datasets for vision AI. It offers open-source, cloud, and enterprise products, as well …

2026/9/10 13:32:47

TVBoxOSC无线投屏教程:5步把手机内容送上电视大屏

TVBoxOSC无线投屏教程:5步把手机内容送上电视大屏 【免费下载链接】TVBoxOSC TVBoxOSC - 一个基于第三方项目的代码库,用于电视盒子的控制和管理。 项目地址: https://gitcode.com/GitHub_Trending/tv/TVBoxOSC 家庭聚会上想用手机给全家展示旅行…

2026/9/10 13:27:47

SEO内容优化与关键词研究实战指南

1. 内容优化与SEO效果提升的核心逻辑内容优化是SEO工作中最具长期价值的环节。我从业八年发现一个规律:那些靠技术手段快速提升排名的网站,往往在算法更新后迅速跌落;而持续产出优质内容的站点,排名却能稳定上升。这背后的本质是搜…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/10 11:16:38

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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