You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JFXPanel dispose后重新加载无法显示,Platform.runLater代码不执行

问题分析与解决方案

问题根源

首次打开正常但退出再打开后Platform.runLater代码不执行,核心原因有两点:

  1. SwingPanel的factory默认仅在组件首次挂载时执行一次,页面退出后再次进入时,Compose可能复用旧的JFXPanel实例,未触发新的初始化逻辑。
  2. minimalViewer通过无依赖的remember保存,页面退出后状态未被清空,旧引用干扰新组件的创建流程。

修改后的代码

package ui.pdfpreview

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import com.geolo.desktop.common.utils.LogUtils
import eu.patrickgeiger.fxpdf.util.PDF
import eu.patrickgeiger.fxpdf.viewer.MinimalViewer
import javafx.application.Platform
import javafx.embed.swing.JFXPanel
import javafx.scene.Scene
import javafx.scene.layout.StackPane
import java.io.File

@Composable
fun PdfPreview(
    state: PdfPreviewState,
    modifier: Modifier = Modifier,
    onCreated: (MinimalViewer) -> Unit = {},
    onDispose: (MinimalViewer) -> Unit = {},
) {
    // 让状态依赖文件路径,页面重建或文件变更时重置状态
    var minimalViewer by remember(state.filePath) { mutableStateOf<MinimalViewer?>(null) }
    var jfxPanel by remember(state.filePath) { mutableStateOf<JFXPanel?>(null) }
    val currentOnDispose by rememberUpdatedState(onDispose)

    minimalViewer?.let { viewer ->
        DisposableEffect(viewer) {
            onDispose {
                LogUtils.i("linlian pdfpreview onDispose minimalViewer=$viewer ")
                viewer.pdf.closeDocument()
                currentOnDispose(viewer)
                // 清空引用并清理旧场景
                minimalViewer = null
                jfxPanel?.scene = null
            }
        }
    }

    SwingPanel(
        factory = {
            JFXPanel().also { panel ->
                jfxPanel = panel
                Platform.runLater {
                    val rootViewer = MinimalViewer.MinimalViewerBuilder()
                        .setPDF(PDF(File(state.filePath)))
                        .build()
                    minimalViewer = rootViewer
                    val root = StackPane().apply {
                        children.add(rootViewer)
                    }
                    val scene = Scene(root)
                    onCreated.invoke(rootViewer)
                    panel.scene = scene
                }
            }
        },
        modifier = modifier,
        // 绑定key,确保文件变化或页面重建时重新创建Swing组件
        key = state.filePath
    ) { jfxPanel ->
        LogUtils.i("linlian pdfpreview update minimalViewer=$minimalViewer ")
        LogUtils.i("linlian pdfpreview update jfxpannel=$jfxPanel ")
    }
}

class PdfPreviewState(
    val filePath: String
)

关键修改点说明

  1. 状态依赖绑定:给remember的minimalViewer和jfxPanel添加state.filePath作为依赖,确保页面重新进入或文件路径变化时,状态自动重置为null,触发全新初始化流程。
  2. SwingPanel强制重建:给SwingPanel添加key = state.filePath,当key变化时,Compose会销毁旧的Swing组件并重新调用factory创建新实例,避免复用旧的JFXPanel。
  3. 资源彻底清理:在DisposableEffect的销毁逻辑中,不仅关闭PDF文档,还清空minimalViewer引用,并将JFXPanel的scene设为null,彻底释放旧资源,避免干扰新组件。

额外注意事项

确保JavaFX运行时在应用启动时已初始化,可在应用入口添加:

fun main() {
    // 提前初始化JavaFX线程环境
    JFXPanel()
    // 启动Compose应用
    application {
        // 你的应用代码
    }
}

内容的提问来源于stack exchange,提问作者lynn8570

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 01:54:56