textbackgroundjavafxtextflow

JavaFX: setting background color for Text controls


I'm using a TextFlow and some Text items to show a styled text, but i cant find a way to set a simple background color for the Text items.

I can set the fill color and font but it does not have a java method or css property that sets its background color.


Solution

  • Based on this solution, this is a quick implementation of a method to provide background coloring for all the Text nodes within a FlowPane, using CSS and the ability to set a series of paint values separated by commas (as much as Text items) and insets for each one of them:

    private FlowPane flow;
    private Scene scene;
    
    @Override
    public void start(Stage primaryStage) {
        Text text0 = new Text("These are several ");
        Text text1 = new Text("Text Nodes ");
        Text text2 = new Text("wrapped in ");
        Text text3 = new Text("a FlowPane");
        text0.setFill(Color.WHEAT);
        text0.setFont(new Font("Times New Roman", 20));
        text1.setFill(Color.WHITE);
        text1.setFont(new Font("Verdana", 32));
        text2.setFill(Color.WHITESMOKE);
        text2.setFont(new Font("Arial", 24));
        text3.setFill(Color.WHITESMOKE);
        text3.setFont(new Font("Arial", 18));
    
        flow = new FlowPane(text0, text1, text2, text3);
        scene = new Scene(flow, 300, 200);
    
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    
        setBackgroundColors();
        flow.needsLayoutProperty().addListener((obs,d,d1)->setBackgroundColors());        
    }
    
    private void setBackgroundColors(){
        final Bounds out = flow.getBoundsInLocal();
        final StringBuilder sbColors = new StringBuilder();
        final StringBuilder sbInsets = new StringBuilder();
        AtomicInteger cont = new AtomicInteger();
        flow.getChildrenUnmodifiable().forEach(n->{
            sbColors.append("hsb(")
                    .append((((double)cont.get())/((double)flow.getChildren().size()))*360d)
                    .append(", 60%, 90%)");
            Bounds b = ((Text)n).getBoundsInParent();
            sbInsets.append(b.getMinY()).append(" ");
            sbInsets.append(Math.min(scene.getWidth(),out.getMaxX())-b.getMaxX()).append(" ");
            sbInsets.append(Math.min(scene.getHeight(),out.getMaxY())-b.getMaxY()).append(" ");
            sbInsets.append(b.getMinX());
            if(cont.getAndIncrement()<flow.getChildren().size()-1){
                sbColors.append(", ");
                sbInsets.append(", ");
            }
        });
        flow.setStyle("-fx-background-color: "+sbColors.toString()+"; -fx-background-insets: "+sbInsets.toString()+";");
    }
    

    This will lead to this:

    Flow1

    and after resizing the scene:

    Flow2

    EDIT

    Based on the OP request of using a TextFlow layout instead of a FlowPane, since Text nodes can be spanned over several lines within a TextFlow, the given solution will no longer be valid, as the bounding box of each text node will overlap others.

    As a workaround, we can split the Text nodes in single word Text nodes, while keeping the same background color for those in the same original phrase.

    I won't go into the splitting logic, but I will add a list of indices, where each index maps the text node with its index of background color.

    private FlowPane flow;
    private Scene scene;
    
    private final List<Integer> indices=Arrays.asList(0,0,0,1,1,2,2,3,3);
    
    @Override
    public void start(Stage primaryStage) {
        List<Text> text0 = Arrays.asList(new Text("These "), new Text("are "), new Text("several "));
        List<Text> text1 = Arrays.asList(new Text("Text "), new Text("Nodes "));
        List<Text> text2 = Arrays.asList(new Text("wrapped "), new Text("in "));
        List<Text> text3 = Arrays.asList(new Text("a "), new Text("FlowPane"));
        text0.forEach(t->t.setFill(Color.WHEAT));
        text0.forEach(t->t.setFont(new Font("Times New Roman", 20)));
        text1.forEach(t->t.setFill(Color.WHITE));
        text1.forEach(t->t.setFont(new Font("Verdana", 32)));
        text2.forEach(t->t.setFill(Color.WHITESMOKE));
        text2.forEach(t->t.setFont(new Font("Arial", 24)));
        text3.forEach(t->t.setFill(Color.WHITESMOKE));
        text3.forEach(t->t.setFont(new Font("Arial", 18)));
    
        flow = new FlowPane();
        flow.getChildren().addAll(text0);
        flow.getChildren().addAll(text1);
        flow.getChildren().addAll(text2);
        flow.getChildren().addAll(text3);
        scene = new Scene(flow, 300, 200);
    
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    
        setBackgroundColors();
        flow.needsLayoutProperty().addListener((obs,d,d1)->setBackgroundColors());        
    }
    
    private void setBackgroundColors(){
        final Bounds out = flow.getBoundsInLocal();
        final StringBuilder sbColors = new StringBuilder();
        final StringBuilder sbInsets = new StringBuilder();
        AtomicInteger cont = new AtomicInteger();
        flow.getChildrenUnmodifiable().forEach(n->{
            sbColors.append("hsb(")
                    .append((double)indices.get(cont.get())/(double)(indices.get(flow.getChildren().size()-1)+1)*360d)
                    .append(", 60%, 90%)");
            Bounds b = ((Text)n).getBoundsInParent();
            sbInsets.append(b.getMinY()).append(" ");
            sbInsets.append(Math.min(scene.getWidth(),out.getMaxX())-b.getMaxX()-1).append(" ");
            sbInsets.append(Math.min(scene.getHeight(),out.getMaxY())-b.getMaxY()).append(" ");
            sbInsets.append(b.getMinX());
            if(cont.getAndIncrement()<flow.getChildren().size()-1){
                sbColors.append(", ");
                sbInsets.append(", ");
            }
        });
        flow.setStyle("-fx-background-color: "+sbColors.toString()+"; -fx-background-insets: "+sbInsets.toString()+";");
    }
    

    This FlowPane now behaves as a TextFlow:

    Flow3