JavaFX: setting background color for Text controls
Asked Answered
E

2

7

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.

Epi answered 5/4, 2015 at 2:22 Comment(0)
I
9

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

Infant answered 5/4, 2015 at 11:14 Comment(7)
Nice workaround thanks! But this gets very slow when the input text is coming gradually. In my case its the output from a MUD which is essentially telnet protocol with ANSI colors.Epi
Well, it's a workaround... How many Text nodes do you have?Tar
Not exactly what I want, since you used a FlowPane which will not automatically break long lines, and when I change it to a TextFlow which will do that, the colors get messed up.Epi
well you could count on thousands of nodes maybe XD I changed my mind anyway, I will use a WebView and add html nodes to it gradually. its not so fast as it uses javascript and has to be done in JavaFX thread, but still better than TextFlow with tons of Text objects.Epi
Yes, it doesn't work with TextFlow, since each Text node can be spanned in several lines, and its bounding box overlaps others. Using FlowPane you could split your Text nodes into smaller ones (keeping the same background color for them). Anyway, this will be too slow for a big number of nodes.Tar
Have a look at my edit. It's a workaround to make FlowPane behave as TextFlow. You just need to provide an (easy) splitting logic. It will be even slower for big number of nodes, but it's closer to what you asked for.Tar
Indeed, thats what I wished for! But I should be careful what I wish for :) Thank you for your efforts sir!Epi
R
4

There is no background for Text objects. You'd either have to group it with a shape (rectangle, ellipse, etc) and set the color of that shape, or you could put the objects inside a StackPane and set the background color of the StackPane.

Riggle answered 5/4, 2015 at 2:38 Comment(5)
no im fine with the TextFlow, the problem is the Text objects.Epi
The question asks how to set the background of the Text objects, not the TextFlow object.Canfield
Oops. Yes you are right. I misread the question. My bad! I updated and fixed my answer to answer the correct question this time!Fervent
that didn't work as expected, the TextFlow treats StackPanes as individual objects not like Text objects that will get wraped automatically by TextFlowEpi
In my app I just needed to highlight individual short words, so StackPane was the simplest solution. I found though that the TextFlow would add extra line spacing after lines with highlighted words. After much fiddling, I found that doing setLayoutY(10) on the Text object did not affect the position of the text, but did prevent the extra line spacing. I have no idea why :)Buckeen

© 2022 - 2024 — McMap. All rights reserved.