Nik Novák Nik Novák - 1 month ago 16
CSS Question

JavaFX Tooltip - border color bug

I have a problem with Tooltips in JavaFX. For illustration the core of this problem I created new JavaFX project through IntelliJ Idea. Root pane of that project is HBox with

alignment="CENTER"
and that HBox contains four buttons (text: Button A-D). Every button has set Tooltip with text: Sample tooltip A-B. Through fxml file I am also loading cascading styles saved in file named styles.css.

Problem description: I launch application and when I hover with mouse on the first button, it's tooltip looks exactly like CSS file describes. When I am hovering on the second button, right border of tooltip is very slightly brighter. But when I hover with mouse on third or fourth button, right red border of tooltip is slightly visible, rather we can see mix of it's background (yellow) and border color. None of others borders is doing that, only the right border. It's very strange behavioral.

What I tried: I tried change font family, font size and padding.

My question: What causes this issue? Have you got any solution for that problem? And have you got this trouble too?

Screenshot shows issue that I described

Screenshot shows issue that I described

*Mouse cursor is hovering on Button C

Main.java

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}


public static void main(String[] args) {
launch(args);
}
}


Controller.java

package sample;

public class Controller {
}


sample.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<HBox alignment="CENTER" spacing="10.0" stylesheets="@styles.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Button mnemonicParsing="false" text="Button A">
<tooltip>
<Tooltip text="Sample tooltip A" />
</tooltip>
</Button>
<Button mnemonicParsing="false" text="Button B">
<tooltip>
<Tooltip text="Sample tooltip B" />
</tooltip>
</Button>
<Button mnemonicParsing="false" text="Button C">
<tooltip>
<Tooltip text="Sample tooltip C" />
</tooltip>
</Button>
<Button mnemonicParsing="false" text="Button D">
<tooltip>
<Tooltip text="Sample tooltip D" />
</tooltip>
</Button>
</children>
</HBox>


styles.css

.tooltip{
-fx-text-fill: black;
-fx-background-radius: 0px;
-fx-background-color: yellow;
-fx-effect: null;
-fx-border-width: 1px;
-fx-border-color: red;}


PS: Sorry for my english. If it's bug of JavaFX, it's very acute to resolve where is problem!

Answer

I've managed to reproduce your issues, so I've been trying to find out the reason for this weird/buggy behaviour.

First of all, I thought it had something to do with the length of the text, being that "C" has different length than "A".

For debugging purposes, I've created the scene in code.

@Override
public void start(Stage primaryStage) throws IOException {
    Button bA=new Button("Button A");
    Tooltip tA=new Tooltip("Sample tooltip A");
    tA.widthProperty().addListener((obs,b,b1)->System.out.println("A: "+tA.getWidth()));
    bA.setTooltip(tA);

    Button bB=new Button("Button B");
    Tooltip tB=new Tooltip("Sample tooltip B");
    tB.widthProperty().addListener((obs,b,b1)->System.out.println("B: "+tB.getWidth()));
    bB.setTooltip(tB);

    Button bC=new Button("Button C");
    Tooltip tC=new Tooltip("Sample tooltip C");
    tC.widthProperty().addListener((obs,b,b1)->System.out.println("C: "+tC.getWidth()));
    bC.setTooltip(tC);

    Button bD=new Button("Button D");
    Tooltip tD=new Tooltip("Sample tooltip D");
    tD.widthProperty().addListener((obs,b,b1)->System.out.println("D: "+tD.getWidth()));
    bD.setTooltip(tD);

    HBox root =new HBox(10,bA,bB,bC,bD);
    Scene scene = new Scene(root, 300, 250);
    scene.getStylesheets().add(getClass().getResource("root.css").toExternalForm());
    primaryStage.setScene(scene);
    primaryStage.show();

}

Where I've added a listener to changes in the width of the tooltips.

And this is the output:

A: 18.0
A: 0.0
A: 95.16523742675781
A: 95.0
A: 95.16523742675781
A: 95.0

B: 18.0
B: 0.0
B: 94.43310546875
B: 94.0
B: 94.43310546875
B: 94.0

C: 18.0
C: 0.0
C: 94.9012680053711
C: 94.0
C: 94.9012680053711
C: 94.0

D: 18.0
D: 0.0
D: 95.73799133300781
D: 95.0
D: 95.73799133300781
D: 95.0

Do you notice that every tooltip get its real width, and then it's truncated?

This means that tooltip for 'A' is truncated just a few, from 95.17 to 95, and there's no visible indication of this, but 'C' is truncated from 94.90 to 94, almost 1 pixel, so that's why the right border is nearly not visible!

I can't tell the reason why this behaviour. But we can try to solve it.

One simple solution could be providing enough space for the text. In your FXML file this will solve the problem:

<Tooltip prefWidth="100" text="Sample tooltip A" />
...
<Tooltip prefWidth="100" text="Sample tooltip C" />
...

A more exact solution would be setting the required width for every tooltip: If we round up the length of the text and add the 20px padding, the tooltip will be just one pixel wider and the borders will be visible for every button tooltip.

This is how you can do it (padding and border hardcoded for the sake of simplicity):

private final double PADDING = 9d;
private final double BORDER  = 1d;

private void setTooltipWidth(Tooltip tooltip){
    if(tooltip==null || 
        tooltip.getScene()==null || 
        tooltip.getScene().getRoot()==null){
        return;
    }
    Text text=(Text)tooltip.getScene().getRoot().lookup(".text");
    if(text==null || text.getLayoutBounds().getWidth()==0){
        return;
    }
    double width=2d*(PADDING+BORDER)+Math.ceil(text.getLayoutBounds().getWidth());
    tooltip.setPrefWidth(width);
}

And now, for every button:

tC.widthProperty().addListener((obs,b,b1)->setTooltipWidth(tC));

If we print again the width changes, this will show now on button "C":

C: 18.0
C: 0.0
C: 94.9012680053711
C: 94.0
C: 94.9012680053711
C: 95.0

And the tooltip will be displayed correctly:

Tooltip