2014-07-11 26 views
6

È possibile avvolgere un foglio di stile intero in una stringa e applicarlo a un determinato nodo? Il caso di utilizzo sarebbe quello di aggiungere un comportamento specifico (non modificabile) per PseudoClass. So che posso usare pane.getStylesheets().add(getClass().getResource("mycss.css").toExternalForm());, ma vorrei sapere se c'è un modo per includerlo direttamente nella fonte; qualcosa in linea:In JavaFX 8 posso fornire un foglio di stile da una stringa?

pane.getStylesheets().add(
    ".button:ok { -fx-background-color: green; }\n"+ 
    ".button:ko { -fx-background-color: red; }"); 

risposta

8

Ho trovato un modo per farlo definendo un nuovo collegamento URL:

private String css; 

public void initialize() { 
    ... 
    // to be done only once. 
    URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory()); 
    ... 
} 

private void updateCss(Node node) { 
    // can be done multiple times. 
    css = createCSS(); 
    node.getStylesheets().setAll("internal:"+System.nanoTime()+"stylesheet.css"); 
} 

private class StringURLConnection extends URLConnection { 
    public StringURLConnection(URL url){ 
     super(url); 
    } 

    @Override public void connect() throws IOException {} 

    @Override public InputStream getInputStream() throws IOException { 
     return new StringBufferInputStream(css); 
    } 
} 

private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory { 
    URLStreamHandler streamHandler = new URLStreamHandler(){ 
     @Override protected URLConnection openConnection(URL url) throws IOException { 
      if (url.toString().toLowerCase().endsWith(".css")) { 
       return new StringURLConnection(url); 
      } 
      throw new FileNotFoundException(); 
     } 
    }; 
    @Override public URLStreamHandler createURLStreamHandler(String protocol) { 
     if ("internal".equals(protocol)) { 
      return streamHandler; 
     } 
     return null; 
    } 
} 

Ovviamente protocollo "interno" può essere qualsiasi (non scontrarsi) stringa ben formato e (in questo semplice esempio) percorsofile è assolutamente ignorato.

Io uso questo per impostare il file .css globale, quindi non ho bisogno di ricordare più stringhe. Sembra che lo Stream sia aperto solo una volta, ma non so se questo è vero in tutti i casi.

Sentitevi liberi di complicare il codice in base alle esigenze;)

credito per questo metodo va a Jasper Potts (see this example)

+0

Se si sta scrivendo codice a livello di framework e non si vuole utilizzare l'unica sostituzione possibile del factory di flusso di flusso url, vedere la mia risposta di seguito per un metodo più leggero che non sovrascrive il factory statico. Se ottieni un'eccezione cercando di impostare lo stream factory, allora qualche altro codice che hai già fatto e dovrai usare la semantica interna del "caricatore di servizio" che ho trovato nell'origine della classe URL. Ciò ti consente di registrare un gestore senza creare uno stream factory. – Ajax

+0

Suppongo che il poster originale che ha selezionato la propria risposta probabilmente non lo cambierà alla soluzione che ho lasciato qui sotto, ma per chiunque stia scrivendo una libreria o un framework, davvero, davvero, davvero non dovrebbe sovrascrivere il gestore di flusso di URL predefinito ; al contrario, è necessario utilizzare il framework del provider di servizi utilizzato dalla classe URL stessa. Leggi la documentazione dell'URL e vedrai da solo che non devi (e non dovrebbe mai) sovrascrivere il gestore di flusso predefinito. – Ajax

2

Ho esaminato la documentazione e non vedo un modo integrato per farlo. getStylesheets è l'unico metodo relativo al foglio di stile in Parent e accetta solo "string URL che collegano ai fogli di stile", non i fogli di stile stessi. Restituisce un generico ObservableList, quindi il suo valore di ritorno non ha metodi speciali per tipi diversi; solo un generico add. Ciò è coerente con getResource restituendo un URL e toExternalForm() semplicemente restituendo una versione String di tale oggetto URL.

Tuttavia, c'è una cosa che si può provare: a data URI. Invece di passare un URI generato a un foglio di stile, passare un URI di dati il ​​cui contenuto è tale foglio di stile. Non so se l'API avrebbe accettato quel tipo di URI, però, dato che il CSS Reference Guide collegato nella documentazione getStylesheets s’dice URL foglio

Uno stile può essere un URL assoluto o un URL relativo.

Provare un URI di dati davvero semplice per vedere se funziona. Puoi crearne uno usando this online tool. Se Java non accetta un dato URI, allora non vi resta che avvolgere la stringa CSS contenenti con qualche chiamata di metodo che converte una stringa in un URI di dati, qualcosa di simile:

pane.getStylesheets().add(new DataURI(
    ".button:ok { -fx-background-color: green; }\n"+ 
    ".button:ko { -fx-background-color: red; }").toString()); 

La classe DataURI è ipotetica. Se JavaFX accetta un URI di dati generato manualmente, sarà necessario trovare una libreria che fornisca la classe DataURI; Sono sicuro che esista da qualche parte.

C'è anche un modo per specificare CSS in linea per un certo Node come String, che è quasi quello che stai cercando. E 'menzionato nel CSS Reference Guide:

stili CSS possono provenire da fogli di stile o stili inline. I fogli di stile vengono caricati dagli URL specificati nella variabile stylesheets dell'oggetto Scene. Se il grafico scena contiene un controllo, viene caricato un foglio di stile agente utente predefinito. Gli stili in linea sono specificati tramite l'API del nodo setStyle. Gli stili incorporati sono analoghi all'attributo style="…" di un elemento HTML.

Tuttavia, suona come non supporta selettori del CSS, ma solo regole - in modo piuttosto che dire .red { color: red; }, si sarebbe solo in grado di scrivere color: red;, e si applicherebbe a tutti i figli di quel Node. Questo non suona come quello che vuoi. Quindi un URI di dati è la tua unica speranza.


EDIT: Mentre questa è un'idea intelligente (non sapevo di URI di dati prima) non funziona. Ho lo stesso requisito, quindi ho provato.Essa non genera un'eccezione, ma c'è un avvertimento nei registri e gli stili non vengono applicate:

Ho usato questo stile:

.root{ 
    -fx-font-family: "Muli"; 
    -fx-font-weight: lighter; 
    -fx-font-size: 35pt; 
    -fx-padding: 0; 
    -fx-spacing: 0; 
} 

E utilizzando lo strumento fornito generato i seguenti dati URI:

data:text/css;charset=utf-8,.root%7B%0D%0A%20%20%20%20-fx-font-family%3A%20%22Muli%22%3B%0D%0A%20%20%20%20-fx-font-weight%3A%20lighter%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-padding%3A%200%3B%0D%0A%20%20%20%20-fx-spacing%3A%200%3B%0D%0A%7D 

Applicando alla mia scena:

scene.getStylesheets().add("data:text/css;charset=utf-8,.root%7B%0D%0A%20%20%20%20-fx-font-family%3A%20%22Muli%22%3B%0D%0A%20%20%20%20-fx-font-weight%3A%20lighter%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-padding%3A%200%3B%0D%0A%20%20%20%20-fx-spacing%3A%200%3B%0D%0A%7D"); 

Risultati a (pardon il mio francese, AVERTISSEMENT = ATTENZIONE):

janv. 07, 2015 12:02:03 PM com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged 
AVERTISSEMENT: Resource "data:text/css;charset=utf-8,%23header%7B%0D%0A%20%20%20%20-fx-background-color%3A%23002D27%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-text-fill%3A%20%23fff%3B%0D%0A%7D" not found. 

così tristemente JavaFX sembra non essere a conoscenza di URI dati.

+0

Ciao ragazzi. La mia risposta di seguito si avvale del provider di servizi di gestione degli stream url per aggiungere il supporto per gli urls 'css:/blah' che mappano a ... quello che vuoi, senza dover sovrascrivere la fabbrica di flusso predefinita. – Ajax

2

Ecco il mio CSS classe Updater in base alla risposta del ZioBytre (+1 funziona molto bene) .

Questa è una classe autonoma che può essere facilmente copiata in un progetto e utilizzata così com'è.

Si ha una dipendenza dalla classe Commons IO IOUtils per restituire un Stream sulla base di un String. Ma questo potrebbe facilmente essere sottolineato o sostituito da un'altra libreria, se necessario.

Uso questa classe in un progetto in cui il CSS è modificabile dinamicamente all'interno dell'applicazione, sul lato server, e trasferito ai client JavaFX. Può essere utilizzato in qualsiasi scenario in cui la stringa CSS non proviene da un file o URL ma da un'altra origine (app del server, database, input dell'utente ...)

Ha un metodo per associare una proprietà stringa in modo che le modifiche ai CSS verranno applicate automaticamente non appena si verificano.

/** 
* Class that handles the update of the CSS on the scene or any parent. 
* 
* Since in JavaFX, stylesheets can only be loaded from files or URLs, it implements a handler to create a magic "internal:stylesheet.css" url for our css string 
* see : https://github.com/fxexperience/code/blob/master/FXExperienceTools/src/com/fxexperience/tools/caspianstyler/CaspianStylerMainFrame.java 
* and : http://stackoverflow.com/questions/24704515/in-javafx-8-can-i-provide-a-stylesheet-from-a-string 
*/ 
public class FXCSSUpdater { 

    // URL Handler to create magic "internal:stylesheet.css" url for our css string 
    { 
     URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory()); 
    } 

    private String css; 

    private Scene scene; 

    public FXCSSUpdater(Scene scene) { 
     this.scene = scene; 
    } 

    public void bindCss(StringProperty cssProperty){ 
     cssProperty.addListener(e -> { 
      this.css = cssProperty.get(); 
      Platform.runLater(()->{ 
       scene.getStylesheets().clear(); 
       scene.getStylesheets().add("internal:stylesheet.css"); 
      }); 
     }); 
    } 

    public void applyCssToParent(Parent parent){ 
     parent.getStylesheets().clear(); 
     scene.getStylesheets().add("internal:stylesheet.css"); 
    } 

    /** 
    * URLConnection implementation that returns the css string property, as a stream, in the getInputStream method. 
    */ 
    private class StringURLConnection extends URLConnection { 
     public StringURLConnection(URL url){ 
      super(url); 
     } 

     @Override 
     public void connect() throws IOException {} 

     @Override public InputStream getInputStream() throws IOException { 
      return IOUtils.toInputStream(css); 
     } 
    } 

    /** 
    * URL Handler to create magic "internal:stylesheet.css" url for our css string 
    */ 
    private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory { 

     URLStreamHandler streamHandler = new URLStreamHandler(){ 
      @Override 
      protected URLConnection openConnection(URL url) throws IOException { 
       if (url.toString().toLowerCase().endsWith(".css")) { 
        return new StringURLConnection(url); 
       } 
       throw new FileNotFoundException(); 
      } 
     }; 

     @Override 
     public URLStreamHandler createURLStreamHandler(String protocol) { 
      if ("internal".equals(protocol)) { 
       return streamHandler; 
      } 
      return null; 
     } 
    } 
} 

Usage:

StringProperty cssProp = new SimpleStringProperty(".root {-fx-background-color : red}"); 
FXCSSUpdater updater = new FXCSSUpdater(scene); 
updater.bindCss(cssProp); 

//new style will be applied to the scene automatically 
cssProp.set(".root {-fx-background-color : green}"); 

//manually apply css to another node 
cssUpdater.applyCssToParent(((Parent)popover.getSkin().getNode())); 
+0

Grazie per avermi rinunciato al lavoro di ZioByte - che era incompleto. – javadba

+0

Qualcuno lo ha davvero fatto funzionare? – Zephyr

+0

Ciao ragazzi. Guarda la mia risposta qui sotto per qualcosa che uso in produzione, senza sovrascrivere il gestore di flusso URL predefinito (che può potenzialmente rovinare altre dipendenze). – Ajax

1

Per chi sta scrivendo codice di livello quadro che non vuole usare l'unico e solo override della statica fabbrica flusso url globale, si può invece legare in il framework di "service loader" interno nella classe URL stessa.

Per fare ciò, è necessario creare una classe denominata Handler extends URLStreamHandler e aggiornare la proprietà di sistema java.protocol.handler.pkgs in modo che punti al pacchetto di quella classe, escluso il suffisso del pacchetto finale. Quindi, com.fu.css imposterà la proprietà su com.fu, quindi tutte le richieste css:my/path verranno indirizzate a questo gestore.

Incollerò la classe che sto usando di seguito; perdona le strane collezioni e le interfacce dei fornitori; puoi indovinare cosa fanno e sostituirli con programmi di utilità standard senza troppi problemi.

package xapi.jre.ui.css; 

import xapi.collect.X_Collect; 
import xapi.collect.api.CollectionOptions; 
import xapi.collect.api.StringTo; 
import xapi.fu.Out1; 
import xapi.io.X_IO; 

import java.io.IOException; 
import java.io.InputStream; 
import java.net.URL; 
import java.net.URLConnection; 
import java.net.URLStreamHandler; 
import java.nio.charset.Charset; 

/** 
* I abhor the name of this class, 
* but it must be called "Handler" in order for java.net.URL to be able to find us. 
* 
* It sucks, but it's not our api, and it's the only way to get dynamic stylesheets in JavaFx, 
* short of overriding the url stream handler directly (and this can only be done once in a single 
* JVM, and as framework-level code, it is unacceptable to prevent clients from choosing to 
* override the stream handler themselves). 
* 
* Created by James X. Nelson (james @wetheinter.net) on 8/21/16. 
*/ 
public class Handler extends URLStreamHandler { 

    private static final StringTo<Out1<String>> dynamicFiles; 
    static { 
     // Ensure that we are registered as a url protocol handler for css:/path css files. 
     String was = System.getProperty("java.protocol.handler.pkgs", ""); 
     System.setProperty("java.protocol.handler.pkgs", Handler.class.getPackage().getName().replace(".css", "") + 
      (was.isEmpty() ? "" : "|" + was)); 
     dynamicFiles = X_Collect.newStringMap(Out1.class, 
      CollectionOptions.asConcurrent(true) 
       .mutable(true) 
       .insertionOrdered(false) 
      .build()); 
    } 

    public static void registerStyleSheet(String path, Out1<String> contents) { 
     dynamicFiles.put(path, contents); 
    } 

    @Override 
    protected URLConnection openConnection(URL u) throws IOException { 
     final String path = u.getPath(); 
     final Out1<String> file = dynamicFiles.get(path); 
     return new StringURLConnection(u, file); 
    } 

    private static class StringURLConnection extends URLConnection { 
     private final Out1<String> contents; 

     public StringURLConnection(URL url, Out1<String> contents){ 
      super(url); 
      this.contents = contents; 
     } 

     @Override 
     public void connect() throws IOException {} 

     @Override public InputStream getInputStream() throws IOException { 
      return X_IO.toStream(contents.out1(), Charset.defaultCharset().name()); 
     } 
    } 
} 

Ora, qualsiasi codice può chiamare Handler.registerStylesheet ("il mio/percorso",() -> "* {-fx-css: bla}") ;, e si può usare questo foglio di stile qualsiasi luogo tramite " css: mio/percorso".

Nota che sto guardando solo la parte del percorso dell'URL; Intendo sfruttare i parametri di query per aumentare ulteriormente il dinamismo (utilizzando una fabbrica di CSS che accetta una mappa di parametri), ma ciò esula dallo scopo di questa domanda.