桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载本篇围绕 MooToolnext-composeKotlin Multiplatform Compose Desktop仓库中的 DIFF-236 变更记录展开主题是图片工具页在采屏/框选过程中被切走时如何彻底关闭全屏区域截图 overlay 并复位busy状态。读完本文你将掌握 Compose 桌面应用中「工具页离开即清理dismiss-on-leave」生命周期机制的完整落地方式包括 overlay 单例的可中断设计、会话状态集中清理函数、协程取消与单元验证的全链路配合。问题背景全屏截图 overlay 为什么会「跨页残留」DIFF-236 对应的问题定义见 236-image-region-capture-on-leave.md。图片工具的 F23 功能区域截图依赖全屏ScreenRegionPicker点击截图后程序先通过Robot抓取全部屏幕画面再为每台显示器弹出一个置顶无边框窗口让用户拖拽框选区域。这里有一个容易被忽视的前提ScreenRegionPicker是一个基于 Java Swing/AWT 的独立顶层窗口对象object ScreenRegionPicker 若干JWindow它游离在 Compose 的 UI 组合树之外。当用户在采屏或框选过程中切到其他工具页时ImageScreen会被 Compose 卸载但 overlay 窗口并不会随之自动销毁——它会悬浮在所有窗口之上继续占据鼠标与键盘焦点同时会话里的busy标志仍为true导致返回图片页后界面显示「处理中」、按钮不可用。同类问题在 F22 屏幕取色ScreenColorPicker上已先出现并修复对应变更记录 235-color-screen-picker-on-leave.md其模式与 DIFF-236 完全同构overlay 单例提供dismissActive()、会话清理函数补状态复位、页面onDispose取消在途协程。这一整套「切页清理」机制本身源自 232-tool-modal-overlays-on-leave.md 建立的模态遮罩清理模式。ScreenRegionPicker.dismissActive()可被外部中断的 overlay 单例核心修复点在 ScreenRegionPicker.kt。该对象维护了一个Volatile的回调引用并在每次show()时注册、每次关闭时清空object ScreenRegionPicker { Volatile private var dismissActivePicker: (() - Unit)? null /** 离开工具页或取消流程时关闭区域截图层避免遮罩跨页残留。 */ fun dismissActive() { val dismiss dismissActivePicker dismissActivePicker null dismiss?.invoke() } // ... }show()内部的closeAnd(action)是唯一的收口关闭路径它用closed布尔量保证幂等多次触发只执行一次依次移除全局KeyEventDispatcher、dispose()所有 overlay 窗口然后才执行回调ScreenRegionPicker.kt#L51-L60。而注册外部退出句柄的时机就在创建 overlay 窗口之前KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keyDispatcher) dismissActivePicker { closeAnd(onCancel) } // L100因此dismissActive()与用户按 Esc、右键取消走的是同一条closeAnd(onCancel)路径——这正是变更记录中「等同 Esc 取消」的实现依据。onCancel回调由调用方ImageScreen的capture()协程传入会负责复位session.busy、写入「截图已取消」提示。show()的完整交互面也值得了解ScreenRegionPicker.kt#L32-L206非 EDT 线程调用时先经SwingUtilities.invokeLater切到事件分发线程按GraphicsEnvironment遍历screenDevices为每块屏幕的bounds建一个RegionOverlayWindowJWindowisAlwaysOnTop true多屏框选由多个窗口协同承载全局KeyEventDispatcher处理Esc取消与Enter确认当前选区鼠标交互支持新建选区、拖拽、八向缩放手柄、移动、以及选区旁绘制的「✓ / ×」命令按钮。ImageSession.dismissModalOverlays()集中复位会话瞬时状态第二个修复点在 ToolModalOverlays.kt。该文件为每个工具的会话类型定义了dismissModalOverlays()扩展函数是「切页清理」的统一出口。DIFF-236 将ImageSession的版本扩充为internal fun ImageSession.dismissModalOverlays() { historyOpen false compressOpen false watermarkOpen false svgOpen false renameOpen false saveOpen false deleteOpen false busy false // DIFF-236 新增复位采屏忙标志 ScreenRegionPicker.dismissActive() // DIFF-236 新增关闭区域截图 overlay }见 ToolModalOverlays.kt#L83-L93其中busy是ImageSession的核心瞬时状态之一定义在 SessionManager.kt#L1179-L1186。对照同文件的ColorSession.dismissModalOverlays()picking falseScreenColorPicker.dismissActive()ToolModalOverlays.kt#L69-L75可以看出两个 overlay 类功能被刻意保持对称状态复位与 overlay 关闭必须同时发生任何一环缺失都会造成「按钮禁用但遮罩已消失」或「遮罩消失但状态仍为忙」的错位。值得注意的分寸是清理只针对瞬时 UI 状态各对话框开关、busy、promptValue之类会话中的持久数据如currentName、selectedNames、lastOutputs等不在清理范围内。这与文件头部注释声明的原则一致——对照 Electron 版会话状态jsonSessionState等在切工具时保留数据、仅遮罩随页面挂载/卸载的策略ToolModalOverlays.kt#L6-L9。ImageScreen 的 onDispose 清理取消协程 复位状态第三个修复点是页面侧的挂载点。ImageScreen.kt 在组合函数体顶部注册了离开回调val session remember { container.sessionManager.imageSession() } var imageWorkJob by remember { mutableStateOfJob?(null) } fun startImageWork(job: Job?) { imageWorkJob?.cancel() imageWorkJob job } DismissModalOverlaysOnDispose(container, ToolId.Image) { imageWorkJob?.cancel() // 取消进行中的截图协程 imageWorkJob null session.dismissModalOverlays() }见 ImageScreen.kt#L126-L137DismissModalOverlaysOnDispose定义在 ToolModalLifecycle.kt本质是一个DisposableEffect(toolId)在onDispose时检查shouldRunToolLeaveCleanup(container.sessionManager.isDetached(toolId))再执行清理。isDetached守卫解决了一个边界场景主窗口把工具页分离成独立窗口时主窗侧占位会被销毁此时不应执行清理会话仍被分离窗共享而分离窗关闭会先reattachisDetached恢复为false后清理照常执行ToolModalLifecycle.kt#L8-L38。取消协程之所以必要是因为截图流程是两阶段异步的ImageScreen.kt#L1020-L1074private fun capture(container, session, scope, onLoaded): Job? { if (session.busy) return null session.busy true // ... return scope.launch(Dispatchers.Default) { val capture runCatching { ScreenColorSampler.captureAllScreens() } if (!isActive) { session.busy false; returnlaunch } // 阶段一后被取消 withContext(Dispatchers.Swing) { if (!isActive) { ... returnwithContext } // 阶段二前再次检查 capture.onSuccess { image - ScreenRegionPicker.show(image, container::t, onPicked { ... session.busy false ... }, onCancel { session.busy false; ... } ) } // ... } } }ScreenColorSampler.captureAllScreens()ScreenColorSampler.kt#L55-L75在Dispatchers.Default上执行Robot.createScreenCapture是整条链路中最耗时的一步且期间会检查 macOS 屏幕录制权限缺权限时弹系统提示并引导设置。协程中两处isActive检查确保若用户在采屏阶段就切页协程被取消后不会把已截好的画面送进ScreenRegionPicker.show()若 overlay 已弹出则由dismissModalOverlays()里的ScreenRegionPicker.dismissActive()走onCancel收口。两层防护互为兜底。验证方式与可复现步骤变更记录指定的验证手段有两条单元测试ToolModalOverlaysTest.kt 中的ToolModalOverlaysTest.host_and_image_dismissModalOverlays_clears_flags。该用例构造ImageSession置compressOpen true、deleteOpen true、busy true调用dismissModalOverlays()后断言三者均为false——busy断言正是 DIFF-236 的增量验证点ToolModalOverlaysTest.kt#L80-L100。同文件的color_dismissModalOverlays_clears_picking_and_dialogs则持续守住 DIFF-235 的对称行为。离线运行 desktopTest./gradlew :composeApp:desktopTest --offline在 next-compose 目录下执行--offline表明该测试目标不依赖外部网络资源。小结Swing 顶层窗口融入 Compose 生命周期的通用模式DIFF-236 的修复虽然只有三行行为变更但它固化了一个可复用的模式适用于任何「Compose 页面内嵌 AWT/Swing 顶层窗口」的桌面工具环节责任关键实现overlay 单例暴露幂等的dismissActive()与 Esc/取消共用同一关闭路径ScreenRegionPicker.dismissActivePicker回调槽 closeAnd()幂等收口会话清理函数集中复位该工具的瞬时 UI 状态并调用 overlay 关闭ImageSession.dismissModalOverlays()busy falsedismissActive()页面离开钩子onDispose时取消在途协程并触发会话清理且受isDetached守卫DismissModalOverlaysOnDispose/OnToolLeaveUnlessDetachedDisposableEffect(toolId)回归验证纯 JVM 单测覆盖状态复位desktopTest离线可跑ToolModalOverlaysTest对应busy断言由于 overlay 生命周期完全由 Compose 组合树驱动「切页残留」这类跨框架边界的泄漏问题就被收敛到了单一可测试的清理出口上。若你正在为自己的 Compose Desktop 项目设计截图、取色或全屏选择类功能这套「回调槽 会话清理 离开钩子 协程取消」的组合可以直接参照。赞分享桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载相关推荐MooTool Compose Desktop 屏幕取色实现剖析AWT Robot 冻结截图替代 Electron desktopCapturerMooTool Compose Desktop 屏幕取色实现剖析AWT Robot 冻结截图替代 Electron desktopCapturer 本文基于桌面应用开发工具AutoHotkey屏幕截图工具实现区域截图与编辑功能AutoHotkey屏幕截图工具实现区域截图与编辑功能 在日常办公和开发过程中屏幕截图是一项高频需求。无论是保存重要信息、分享操作步骤还是调试界面问题一个RPAGUI 自动化桌面应用MooTool Compose 桌面版 P5 工具工作区壳local-tool-shell样式对齐实现解析DIFF-091MooTool Compose 桌面版 P5 工具工作区壳local tool shell样式对齐实现解析DIFF 091 导读 本文解析 MooToo桌面应用开发工具上一篇RAG检索实战从切分到精排的5个关键环节下一篇One-API 部署实战指南从 Docker 单容器到多节点生产的 4 条路径创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?