有超链接。单击时,我希望在外部浏览器中打开一个链接。
网上引用的常用方法好像是:
final Hyperlink hyperlink = new Hyperlink("http://www.google.com");
hyperlink.setOnAction(t -> {
application.getHostServices().showDocument(hyperlink.getText());
});
但是我没有对 Application
的引用。链接是从 Dialog 打开的,而 Dialog 是从 Controller 打开的,而 Controller 通过 fxml 文件打开,因此获取对 Application 对象的引用会非常痛苦。
有人知道这样做的简单方法吗?
干杯
最佳答案
解决方案 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());
}
}
解决方案 2:使用 Controller 工厂将主机服务推送到 Controller 。
这是上面的一个更简洁的版本。您无需获取 Controller 并调用方法来初始化它们,而是通过 controllerFactory
配置它们的创建。并通过将 HostServices
对象传递给 Controller 的构造函数来创建 Controller ,如果它有合适的构造函数的话:
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 时使用 Controller 工厂:
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();
}
并定义您的 Controller 以将 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());
任何你需要的地方。这里的问题之一是您为需要访问主机服务的所有 Controller 引入了对应用程序类型的依赖。
解决方案 4 定义一个单例 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 使用依赖注入(inject)框架。这可能不适用于您当前的用例,但可能会让您了解这些(相对简单的)框架有多么强大。
例如,如果您使用 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 .
关于JavaFx 8 : open a link in a browser without reference to Application,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33094981/