2015-10-13 6 views
8

Avere un collegamento ipertestuale. Quando si fa clic su Voglio un collegamento per essere aperto in un browser esterno.JavaFx 8: apre un collegamento in un browser senza riferimento all'applicazione

Il metodo usuale citato sul web sembra essere:

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

Tuttavia non ho un riferimento a Application. Il collegamento viene aperto da Dialog, che viene aperto da un Controller, che viene aperto tramite un file fxml, quindi ottenere un riferimento all'oggetto Application sarebbe piuttosto doloroso.

Qualcuno sa un modo semplice per farlo?

Acclamazioni

risposta

14

Soluzione 1: Passare un riferimento allo HostServices attraverso l'applicazione.

Questo è probabilmente simile all'approccio "abbastanza doloroso" che si sta anticipando. Ma in fondo si potrebbe fare qualcosa di simile:

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(); 

} 

e poi in 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(); 
    } 
} 

e naturalmente DialogController assomiglia:

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()); 
    } 
} 

Soluzione 2: Usa una fabbrica di controller per inviare il servizio host es ai controller.

Questa è una versione più pulita di quanto sopra.Invece di ottenere i controllori e chiamando un metodo per inizializzare loro, si configurare la creazione di loro tramite un controllerFactory e creare controller passando un oggetto HostServices al costruttore del controller, se si ha un costruttore adeguato:

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); 
     } 
    } 
} 

Ora utilizzare la fabbrica di controllo quando si carica il 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(); 
} 

e definire i controller di prendere HostServices come parametro del costruttore:

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(); 
    }  
} 

e naturalmente

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()); 
    } 
} 

Soluzione 3:questa è una soluzione miseramente brutto, e vi consiglio vivamente l'uso di esso. Volevo solo includerlo in modo da poterlo esprimere senza offendere qualcun altro quando lo hanno pubblicato. Archiviare i servizi host in un campo statico.

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(); 
    } 
} 

Poi basta fare

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

ovunque sia necessario. Uno dei problemi qui è che si introduce una dipendenza dal tipo di applicazione per tutti i controller che devono accedere ai servizi host.


Soluzione 4 Definire un singleton HostServicesProvider. Questo è meglio della soluzione 3, ma non è ancora una buona soluzione.

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 ; 
    } 
} 

Ora non vi resta

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

e

public class DialogController { 

    @FXML 
    private Hyperlink hyperlink ; 

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

Soluzione 5 utilizzare un quadro di iniezione di dipendenza. Questo probabilmente non si applica al tuo caso d'uso corrente, ma potrebbe darti un'idea di quanto potenti possano essere questi framework (relativamente semplici).

Ad esempio, se si utilizza afterburner.fx, basta fare

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

nell'applicazione start() o init() metodo, e quindi

public class DialogPresenter { 

    @Inject 
    private HostServices hostServices ; 

    @FXML 
    private Hyperlink hyperlink ; 

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

Un esempio utilizzando la primavera è here.

+0

la soluzione statica brutto non funziona se non chiami i getHostServices modo diverso per esempio getStaticHostServices. È la soluzione meno faticosa nel mio caso d'uso. –

+0

@WolfgangFahl Sì, probabilmente. Non sono sicuro del perché potresti provare qualcosa che consiglio vivamente di non usare comunque. –

+0

nessuna preoccupazione Sto usando un'interfaccia ora interfaccia pubblica Linker { public void browse (String url); } che nasconde l'implementazione - le cose statiche non sono necessarie in questo modo –

1

Un altro modo sarebbe quello di utilizzare java.awt.Desktop

Try (non testata):

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

noti comunque che questo introdurrà una dipendenza alla pila AWT. Questo probabilmente non è un problema se stai lavorando con il JRE completo, ma potrebbe diventare un problema se vuoi lavorare con un JRE su misura (Java SE 9 & Jigsaw) o se vuoi eseguire la tua applicazione su dispositivo mobile (javafxports).

C'è un problema aperto a support Desktop in JavaFX in futuro.

3

Se si desidera aprire un URL quando si fa clic su un pulsante all'interno della propria app e si sta utilizzando un file controller fxml, è possibile effettuare le seguenti operazioni ...

primo nel file di avvio Applicazione principale ottenere un puntatore ai HostServices oggetto e aggiungerlo al tuo stadio, come ...

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

Poi nel file di controllo fxml ottenere il hostServices oggetto dall'oggetto palco e quindi eseguire il metodo showDocument().

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

Ho un metodo nella mia classe contoller chiamato getStage() ...

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

cos'è mainAnchor1? – simpleuser

Problemi correlati