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 в будущем.