JavaFx 8:在浏览器中打开链接而不引用应用程序

5gfr0r5j  于 2023-05-27  发布在  Java
关注(0)|答案(4)|浏览(208)

有一个超链接。当点击我想在外部浏览器中打开一个链接。
网络上引用的常用方法似乎是:

final Hyperlink hyperlink = new Hyperlink("http://www.google.com");
hyperlink.setOnAction(t -> {
    application.getHostServices().showDocument(hyperlink.getText());
});

但是我没有Application的引用。链接是从Dialog打开的,Dialog是从Controller打开的,Controller是通过fxml文件打开的,因此获取对Application对象的引用将是非常痛苦的。
有人知道一个简单的方法来做到这一点吗?
干杯

4bbkushb

4bbkushb1#

**解决方案1:**通过应用向下传递对HostServices的引用。

这可能与您预期的“相当痛苦”的方法相似。但基本上你会做这样的事情:

public void start(Stage primaryStage) throws Exception {

    FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
    Parent root = loader.load();
    MainController controller = loader.getController();
    controller.setHostServices(getHostServices());
    primaryStage.setScene(new Scene(root));
    primaryStage.show();

}

然后在MainController中:

public class MainController {

    private HostServices hostServices ;

    public HostServices getHostServices() {
        return hostServices ;
    }

    public void setHostServices(HostServices hostServices) {
        this.hostServices = hostServices ;
    }

    @FXML
    private void showDialog() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("dialog.fxml"));
        Parent dialogRoot = loader.load();
        DialogController dialogController = loader.getController();
        dialogController.setHostServices(hostServices);
        Stage dialog = new Stage();
        dialog.setScene(new Scene(dialogRoot));
        dialog.show();
    }
}

DialogController看起来像:

public class DialogController {

    @FXML
    private Hyperlink hyperlink ;

    private HostServices hostServices ;

    public HostServices getHostServices() {
        return hostServices ;
    }

    public void setHostServices(HostServices hostServices) {
        this.hostServices = hostServices ;
    }

    @FXML
    private void openURL() {
        hostServices.openDocument(hyperlink.getText());
    }
}

**方案二:**使用控制器工厂将主机服务推送到控制器。

这是上面的一个更干净的版本。不需要获取控制器并调用方法来初始化它们,而是通过controllerFactory配置它们的创建,并通过将HostServices对象传递给控制器的构造函数来创建控制器,如果它有合适的构造函数:

public class HostServicesControllerFactory implements Callback<Class<?>,Object> {

    private final HostServices hostServices ;

    public HostServicesControllerFactory(HostServices hostServices) {
        this.hostServices = hostServices ;
    }

    @Override
    public Object call(Class<?> type) {
        try {
            for (Constructor<?> c : type.getConstructors()) {
                if (c.getParameterCount() == 1 && c.getParameterTypes()[0] == HostServices.class) {
                    return c.newInstance(hostServices) ;
                }
            }
            return type.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

现在,在加载FXML时使用控制器工厂:

public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
    loader.setControllerFactory(new HostServicesControllerFactory(getHostServices()));
    Parent root = loader.load();
    primaryStage.setScene(new Scene(root));
    primaryStage.show();
}

并定义你的控制器接受HostServices作为构造函数参数:

public class MainController {

    private final HostServices hostServices ;

    public MainController(HostServices hostServices) {
        this.hostServices = hostServices ;
    }

    @FXML
    private void showDialog() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("dialog.fxml"));
        loader.setControllerFactory(new HostServicesControllerFactory(hostServices));
        Parent dialogRoot = loader.load();
        Stage dialog = new Stage();
        dialog.setScene(new Scene(dialogRoot));
        dialog.show();
    }    
}

当然

public class DialogController {

    @FXML
    private Hyperlink hyperlink ;

    private final HostServices hostServices ;

    public DialogController(HostServices hostServices) {
        this.hostServices = hostServices ;
    }

    @FXML
    private void openURL() {
        hostServices.openDocument(hyperlink.getText());
    }
}

**解决方案3:**这是一个非常丑陋的解决方案,我强烈建议不要使用它。 我只是想包括它,这样我就可以表达出来,而不会冒犯其他人。将主机服务存储在静态字段中。

public class MainApp extends Application {

    private static HostServices hostServices ;

    public static HostServices getHostServices() {
        return hostServices ;
    }

    public void start(Stage primaryStage) throws Exception {

        hostServices = getHostServices();

        Parent root = FXMLLoader.load(getClass().getResource("main.fxml"));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
}

那你就做吧

MainApp.getHostServices().showDocument(hyperlink.getText());

任何你需要的地方。这里的一个问题是,对于需要访问主机服务的所有控制器,您引入了对应用程序类型的依赖性。

方案4定义一个singleton HostServicesProvider。这比解决方案3好,但仍然不是一个好的解决方案。

public enum HostServicesProvider {

    INSTANCE ;

    private HostServices hostServices ;
    public void init(HostServices hostServices) {
        if (this.hostServices != null) {
            throw new IllegalStateException("Host services already initialized");
        }
        this.hostServices = hostServices ;
    }
    public HostServices getHostServices() {
        if (hostServices == null) {
            throw new IllegalStateException("Host services not initialized");
        }
        return hostServices ;
    }
}

现在你只需要

public void start(Stage primaryStage) throws Exception {
    HostServicesProvider.INSTANCE.init(getHostServices());
    // just load and show main app...
}

public class DialogController {

    @FXML
    private Hyperlink hyperlink ;

    @FXML
    private void openURL() {
        HostServicesProvider.INSTANCE.getHostServices().showDocument(hyperlink.getText());
    }
}

解决方案5使用依赖注入框架。这可能不适用于您当前的用例,但可能会给予您了解这些(相对简单的)框架有多强大。

例如,如果您使用的是afterburner.fx,则只需执行以下操作

Injector.setModelOrService(HostServices.class, getHostServices());

在应用程序中使用start()init()方法,然后

public class DialogPresenter {

    @Inject
    private HostServices hostServices ;

    @FXML
    private Hyperlink hyperlink ;

    @FXML
    private void showURL() {
        hostServices.showDocument(hyperlink.getText());
    }
}

使用Spring的一个例子是here

kyvafyod

kyvafyod2#

如果你想打开一个url当你点击一个按钮在你的应用程序,你正在使用一个fxml控制器文件,那么你可以做以下...
首先,在主应用程序启动文件中获取指向HostServices对象的指针,并将其添加到您的舞台,例如...

stage.getProperties().put("hostServices", this.getHostServices());

然后在fxml控制器文件中从stage对象获取hostServices对象,然后执行showDocument()方法。

HostServices hostServices = (HostServices)this.getStage().getProperties().get("hostServices");
hostServices.showDocument("http://stackoverflow.com/");

在我的contoller类中有一个方法,叫做getStage()...

/**
 * @return the stage from my mainAnchor1 node.
 */
public Stage getStage() {
    if(this.stage==null)
        this.stage = (Stage) this.mainAnchor1.getScene().getWindow();
    return stage;
}
oxf4rvwz

oxf4rvwz3#

另一种方法是使用java.awt.Desktop
未测试(untested):

URI uri = ...;
if (Desktop.isDesktopSupported()){
    Desktop desktop = Desktop.getDesktop();
    if (desktop.isSupported(Desktop.Action.BROWSE)){
        desktop.browse(uri);
    }
}

但是请注意,这将引入对AWT堆栈的依赖性。如果您使用完整的JRE,这可能不是问题,但如果您想要使用定制的JRE(Java SE 9和Jigsaw)或如果您想要在移动终端上运行应用程序(javafxports),则可能会成为问题。
support Desktop in JavaFX在未来有一个开放的问题。

bsxbgnwa

bsxbgnwa4#

在寻求更可持续的解决方案时,可以考虑以下任何一种临时方法:
1.让类 extendApplication,让它访问HostServices的示例。它不会是父应用程序的示例,但可能足够了。

class MyController extends Application … {
     HostServices hostServices = this.getHostServices();
 }

1.让类 * 包含 * 一个Application,它可以从中获得HostServices的示例。同样,它不会是父应用程序的示例。

class MyController extends Application … {
     HostServices hostServices = new ServiceApp().getHostServices();

     private static class ServiceApp extends Application {
         @Override
         public void start(Stage stage) throws Exception {}
     }
 }

在这个完整的example中,控制器的早期version只是扩展了Application。同一控制器的后一个version实现了一个合适的接口,以实现here概述的第一个解决方案。最近的version使用控制器工厂来提供参考,如第二个解决方案所示,参见here
从概念上讲,HostServices对于每个Application示例都应该是唯一的。这意味着singleton pattern,建议的here和第四个解决方案概述的here。实际上,HostServicesfinalsource委托给一个私有类,称为here,它引用应用程序。

相关问题