当前位置: 首页 > 工具软件 > JxBrowser > 使用案例 >

jxbrowser保存文件打开的上次目录,支持单文件,多文件,多窗口

宗政松
2023-12-01

需求描述:

jxbrowser将Web浏览器组件集成到Java Swing / AWT / JavaFX应用程序中,因为公司项目需求是需要将网页内容放到JavaFx中。因为常用的浏览器在打开文件弹窗时都会记录上次打开的目录,下次打开时直接可以访问到想要的目录文件,增加了用户体验度。
而内嵌到javaFx后,jxbrowser没有记录文件访问目录,每次都会打开计算机根目录。
市面上关于jxbrowser的技术文档相当少,所以只能参考官方提供的参考文档和API.

注:本文使用的jxbrowser的版本是7.19,不同版本之间会有差异。

解决思路

根据jxbrowser提供的文档,jxbrowser可以监听文件触发文件框,那么我们就可以深入触发机制,把触发时的指向目录进行更改,前提是我们要保存每次访问的文件目录。

但是jxbrowser默认情况下,所有弹出窗口都被禁止,也就意味着打开新页面后,原有页面的监听会失效,所以需要对新弹出的窗口再次监听。

思路实现

jxbrowser的javaFX实现

import com.teamdev.jxbrowser.browser.Browser;
import com.teamdev.jxbrowser.browser.callback.*;
import com.teamdev.jxbrowser.browser.callback.OpenFolderCallback;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.view.javafx.BrowserView;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import static com.teamdev.jxbrowser.engine.RenderingMode.HARDWARE_ACCELERATED;

/**
 * This example demonstrates how to initialize Chromium, create a browser instance
 * (equivalent of the Chromium tab), embed a JavaFX BrowserView component into JavaFX
 * scene to display content of the loaded web page, load the required web page.
 */
public final class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        //jxbriwser需要进行注册后才能使用
        System.setProperty("jxbrowser.license.key", "注册码****");
        // Initialize Chromium.
        Engine engine = Engine.newInstance(HARDWARE_ACCELERATED);
        Browser browser = engine.newBrowser();
        // Load the required web page.
        //放入浏览器网页地址
        browser.navigation().loadUrl("http://localhost:8080/");
        // Create and embed JavaFX BrowserView component to display web content.
        BrowserView view = BrowserView.newInstance(browser);
        Scene scene = new Scene(new BorderPane(view), 1920, 1080);
        primaryStage.setTitle("***");
        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.setMaximized(true);
       
        //单文件监听
        browser.set(OpenFilesCallback.class, (params, tell) -> {
            DefaultOpenFilesCallback d = new DefaultOpenFilesCallback(view);
            d.on(params, tell);
        });
        //多文件监听
        browser.set(OpenFileCallback.class, (params, tell) -> {
            DefaultOpenFileCallback d = new DefaultOpenFileCallback(view);
            d.on(params, tell);
        });
        //新弹窗监听
        browser.set(OpenPopupCallback.class, (params) -> {
          //  Browser popup = params.popupBrowser();
            DefaultOpenPopupCallback defaultOpenPopupCallback = new DefaultOpenPopupCallback();
            defaultOpenPopupCallback.on(params);
            return DefaultOpenPopupCallback.Response.proceed();
        });
        // Shutdown Chromium and release allocated resources.
        primaryStage.setOnCloseRequest(event -> engine.close());
    }
}

因为我们更改jxbrowser的触发文件框的目录,又因为jxbrowser提供的jar包里面的方法不能更改,所以我们需要重新实现这些方法。

  1. html中单文件触发文件框方法重新实现
    DefaultOpenFileCallback触发文件框方法重新实现,实现方式是把每次打开文件的目录保存到本地的cache.txt中,每次打开触发文件框时都会读取cache.txt中的目录地址。此方法只能监听前端单文件上传实现。
    代码实现:

import com.teamdev.jxbrowser.browser.callback.OpenFileCallback;
import com.teamdev.jxbrowser.view.javafx.BrowserView;
import javafx.application.Platform;
import javafx.stage.FileChooser;
import java.io.*;

public class DefaultOpenFileCallback extends DefaultCallback implements OpenFileCallback {
    public DefaultOpenFileCallback(BrowserView parent) {
        super(parent);
    }

    public void on(Params params, Action tell) {
        Platform.runLater(() -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("Choose File");
            fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("All Files", "*.*"));
            File cacheFile = new File("cache.txt");
            if (cacheFile.exists()) {
                try (InputStream inputStream = new FileInputStream(cacheFile)) {
                    byte[] bytes = new byte[(int) cacheFile.length()];
                    inputStream.read(bytes);
                    File directory = new File(new String(bytes));
                    if (directory.exists()) {
                        fileChooser.setInitialDirectory(directory);
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            this.window().ifPresent((window) -> {
                FileChoosers.setExtensionFilters(fileChooser, params.filterDescription(), params.acceptableExtensions());
                File file = fileChooser.showOpenDialog(window);
                if (file != null) {
                    try (OutputStream outputStream = new FileOutputStream(cacheFile)) {
                        byte[] bytes = file.getParent().getBytes();
                        outputStream.write(bytes);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (file != null) {
                    tell.open(file.toPath());
                } else {
                    tell.cancel();
                }

            });
        });
    }
}

  1. html中多文件触发实现
    DefaultOpenFilesCallback的方法的重新实现。在此方法中与单文件实现逻辑相同
    代码如下:
import com.teamdev.jxbrowser.browser.callback.OpenFilesCallback;
import com.teamdev.jxbrowser.view.javafx.BrowserView;
import javafx.application.Platform;
import javafx.stage.FileChooser;
import java.io.*;
import java.nio.file.Path;
import java.util.List;
public class DefaultOpenFilesCallback extends DefaultCallback implements OpenFilesCallback {
    DefaultOpenFilesCallback(BrowserView parent) {
        super(parent);
    }
    public void on(Params params, Action tell) {
        Platform.runLater(() -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("All Files", "*.*"));
            File cacheFile = new File("cache.txt");
            if (cacheFile.exists()) {
                try (InputStream inputStream = new FileInputStream(cacheFile)) {
                    byte[] bytes = new byte[(int) cacheFile.length()];
                    inputStream.read(bytes);
                    File directory = new File(new String(bytes));
                    if (directory.exists()) {
                        fileChooser.setInitialDirectory(directory);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            this.window().ifPresent((window) -> {
                FileChoosers.setExtensionFilters(fileChooser, params.filterDescription(), params.acceptableExtensions());
                List<File> selectedFiles = fileChooser.showOpenMultipleDialog(window);
                if (selectedFiles != null) {
                    try (OutputStream outputStream = new FileOutputStream(cacheFile)) {
                        byte[] bytes = selectedFiles.get(selectedFiles.size() - 1).getParent().getBytes();
                        outputStream.write(bytes);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                  tell.open((Path[])selectedFiles.stream().map(File::toPath).toArray(Path[]::new));
                } else {
                    tell.cancel();
                }
            });
        });
    }
}

因为重写后,需要原有的继承类,但是jar包外不能访问jar包中final的方法与类,所以单独拿出来

  • FileChoosers类
import java.util.List;
import java.util.stream.Collectors;
import javafx.collections.ObservableList;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;

final class FileChoosers {
    private FileChoosers() {
    }

    static void setExtensionFilters(FileChooser fileChooser, String filterDescription, List<String> extensions) {
        ObservableList<ExtensionFilter> extensionFilters = fileChooser.getExtensionFilters();
        ExtensionFilter allFilesFilter;
        if (!extensions.isEmpty()) {
            allFilesFilter = new ExtensionFilter(filterDescription, withPrefix(extensions));
            extensionFilters.add(allFilesFilter);
            fileChooser.setSelectedExtensionFilter(allFilesFilter);
        }

        allFilesFilter = new ExtensionFilter("All files", new String[]{"*.*"});
        extensionFilters.add(allFilesFilter);
    }

    private static List<String> withPrefix(List<String> extensions) {
        return (List)extensions.stream().map((e) -> {
            return "*." + e;
        }).collect(Collectors.toList());
    }
}

  • DefaultCallback类的实现
import com.teamdev.jxbrowser.view.WidgetHolder;
import com.teamdev.jxbrowser.view.javafx.BrowserView;
import java.util.Optional;
import javafx.scene.Scene;
import javafx.stage.Window;

abstract class DefaultCallback extends WidgetHolder<BrowserView> {
    DefaultCallback(BrowserView parent) {
        super(parent);
    }
    protected Optional<Window> window() {
        Scene scene = ((BrowserView)this.widget()).getScene();
        return scene != null ? Optional.ofNullable(scene.getWindow()) : Optional.empty();
    }
}

到此为止,html中的文件上传都会保存上次的的访问目录,且每次都是上次打开的目录。
但是遇到了新的问题,就是html中如果有新页面打开,监听就会失效,因为我们之前写的只是监听上个页面的文件触发。

所以尝试了很久,,如果新页面要覆盖原有页面时只需要添加

        browser.set(CreatePopupCallback.class, params -> {
            browser.navigation().loadUrl(params.targetUrl());
            return CreatePopupCallback.Response.suppress();
        });

就可以实现原有的监听成功。

但是如果新页面需要单独打开,且不覆盖原有页面时,那么此时新的brower就会生成,那么我们需要做的就是实现监听新页面的触发事件。

官方提供的DefaultOpenPopupCallback并不能满足我们的业务需求,所以我们需要重写,加入文件触发的监听事件。


public final class DefaultOpenPopupCallback implements OpenPopupCallback {
    private static final int DEFAULT_POPUP_WIDTH = 800;
    private static final int DEFAULT_POPUP_HEIGHT = 600;
    @Override
    public Response on(Params paramss) {
        Browser browser = paramss.popupBrowser();
        runLater(() -> {
            BrowserView view = BrowserView.newInstance(browser);
            Stage stage = new Stage();
            StackPane root = new StackPane();
            Scene scene = new Scene(root);
            root.getChildren().add(view);
            stage.setScene(scene);
            updateBounds(stage, paramss.initialBounds());
            stage.setOnCloseRequest(event -> browser.close());
            browser.on(TitleChanged.class, event ->
                    runLater(() -> stage.setTitle(event.title()))
            );
            browser.on(BrowserClosed.class, event ->
                    runLater(stage::close)
            );
            browser.on(UpdateBoundsRequested.class, event ->
                    runLater(() -> updateBounds(stage, event.bounds()))
            );
            browser.set(OpenFilesCallback.class, (params, tell) -> {
                DefaultOpenFilesCallback d = new DefaultOpenFilesCallback(view);
                d.on(params, tell);
            });
            browser.set(OpenFileCallback.class, (params, tell) -> {
                DefaultOpenFileCallback d = new DefaultOpenFileCallback(view);
                d.on(params, tell);
            });
            stage.show();
        });
        return Response.proceed();
    }
    private static void updateBounds(Stage stage, Rect bounds) {
        if (bounds.size().isEmpty()) {
            stage.setWidth(DEFAULT_POPUP_WIDTH);
            stage.setHeight(DEFAULT_POPUP_HEIGHT);
        } else {
            stage.setX(bounds.origin().x());
            stage.setY(bounds.origin().y());
            stage.setWidth(bounds.size().width());
            stage.setHeight(bounds.size().height());
        }
    }

到此,需求解决。

参考官方文档与api地址:https://jxbrowser-support.teamdev.com/docs/guides/popups.html

 类似资料: