JavaFx 8: откройте ссылку в браузере без ссылки на приложение
есть гиперссылка. При нажатии я хочу, чтобы ссылка была открыта во внешнем браузере.
обычный метод, цитируемый в интернете, кажется:
final Hyperlink hyperlink = new Hyperlink("http://www.google.com");
hyperlink.setOnAction(t -> {
    application.getHostServices().showDocument(hyperlink.getText());
});
однако у меня нет ссылки на Application.  Ссылка открывается из диалогового окна, которое открывается с контроллера, который открывается через файл fxml, поэтому получение ссылки на объект приложения будет довольно болезненным.
кто-нибудь знает простой способ сделать это?
Ура
3 ответов
Решение 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: использовать заводской контроллер, чтобы подтолкнуть хост-служб контроллеры.
это более чистая версия выше. Вместо того, чтобы получать контроллеры и вызывать метод для их инициализации, вы настраиваете их создание через 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 as параметр конструктора:
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 определить синглтон HostServicesProvider. Это лучше, чем решение 3, но все же не является хорошим решением imo.
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 используйте структуру инъекции зависимостей. Это, вероятно, не относится к текущий вариант использования, но может дать вам представление о том, насколько мощными могут быть эти (относительно простые) фреймворки.
например, если вы используете форсаже.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 является здесь.
Если вы хотите открыть url-адрес при нажатии на кнопку внутри вашего приложения, и вы используете файл контроллера fxml, то вы можете сделать следующее...
сначала в главном файле запуска приложения получите указатель на объект HostServices и добавьте его на свой этап, например...
stage.getProperties().put("hostServices", this.getHostServices());
затем в файле контроллера fxml получите объект hostServices из объекта stage, а затем выполните метод 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;
}
другой способ-использовать java.ОУ.Столе
попробовать (непроверено):
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).
существует открытая проблема поддержка рабочего стола в JavaFX в будущем.
