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.

Farzad Bekran
  • 489
  • 1
  • 6
  • 13

2 Answers2

8

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

Community
  • 1
  • 1
José Pereda
  • 44,311
  • 7
  • 104
  • 132
  • 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. – Farzad Bekran Apr 05 '15 at 11:39
  • Well, it's a workaround... How many `Text` nodes do you have? – José Pereda Apr 05 '15 at 11:43
  • 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. – Farzad Bekran Apr 05 '15 at 11:52
  • 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. – Farzad Bekran Apr 05 '15 at 11:53
  • 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. – José Pereda Apr 05 '15 at 12:20
  • 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. – José Pereda Apr 05 '15 at 12:59
  • Indeed, thats what I wished for! But I should be careful what I wish for :) Thank you for your efforts sir! – Farzad Bekran Apr 05 '15 at 15:35
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.

Ra'kiir
  • 59
  • 1
  • 9
  • no im fine with the TextFlow, the problem is the [Text](https://docs.oracle.com/javafx/2/api/javafx/scene/text/Text.html) objects. – Farzad Bekran Apr 05 '15 at 02:45
  • The question asks how to set the background of the `Text` objects, not the `TextFlow` object. – James_D Apr 05 '15 at 02:46
  • Oops. Yes you are right. I misread the question. My bad! I updated and fixed my answer to answer the correct question this time! – Ra'kiir Apr 05 '15 at 02:57
  • that didn't work as expected, the TextFlow treats StackPanes as individual objects not like Text objects that will get wraped automatically by TextFlow – Farzad Bekran Apr 05 '15 at 03:10
  • 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 :) – John Christofolakos Mar 18 '16 at 14:46