35

I was playing around with JavaFX's Tooltip. I realized that for me personally the delay between hovering over something and the tooltip actually appearing is too long. A look in the API reveals:

Typically, the tooltip is "activated" when the mouse moves over a Control. There is usually some delay between when the Tooltip becomes "activated" and when it is actually shown. The details (such as the amount of delay, etc) is left to the Skin implementation.

After some further investigation, I was not able to find any possibility to control the delay. The JavaFX CSS Reference has no information about delay time and a runtime-evaluation of getCssMetaData() did not help either.

I know that there is a way to control the tooltips manually via onMouseEntered(...) and onMouseExited(...), but is there really no other way? Or am I missing an obvious solution?

Turing85
  • 18,217
  • 7
  • 33
  • 58

8 Answers8

51

I use the next hack for this via Reflection

public static void hackTooltipStartTiming(Tooltip tooltip) {
    try {
        Field fieldBehavior = tooltip.getClass().getDeclaredField("BEHAVIOR");
        fieldBehavior.setAccessible(true);
        Object objBehavior = fieldBehavior.get(tooltip);

        Field fieldTimer = objBehavior.getClass().getDeclaredField("activationTimer");
        fieldTimer.setAccessible(true);
        Timeline objTimer = (Timeline) fieldTimer.get(objBehavior);

        objTimer.getKeyFrames().clear();
        objTimer.getKeyFrames().add(new KeyFrame(new Duration(250)));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Igor Luzhanov
  • 780
  • 1
  • 7
  • 14
  • This works well for my needs. And, as a bonus, it seems you only have to call it once for the whole application, not for every Tooltip instance you create. – David Apr 20 '17 at 14:02
  • 2
    Bonus: you can do this for the DURATION of the tooltip too! The field name is "hideTimer" instead of "activationTimer". – user3804769 Sep 11 '18 at 13:20
  • which imports are you using? – SovietFrontier Mar 21 '19 at 16:21
  • FYI, this is no longer needed since JavaFX9 see here: https://stackoverflow.com/questions/46408538/set-custom-duration-for-tooltips-in-javafx – Vivek V K Nov 13 '20 at 08:18
  • @David How does this work once for the whole application? That's not what I see so I think you're mistaken. (I wish you weren't!) It does take a Tooltip instance so it makes sense it would need to be done for every Tooltip. The other problem here though is that Tooltips can also be created in FXML, not just in code. – Manius Apr 01 '22 at 14:33
  • @Manius, my comment is four years old and, sadly, I don't remember what my code looked like back then. I haven't used JavaFX since so I could not even begin to try and answer you. Sorry... – David Apr 07 '22 at 15:25
  • Yeah that happens to me too, haha. The "hack" for Java 8 in another answer works fine though. You should use JFX again, it's awesome. :) – Manius Apr 09 '22 at 22:24
24

In Java 9 and later, you can do

Tooltip tooltip = new Tooltip("A tooltip");
tooltip.setShowDelay(Duration.seconds(3));

There is also a hideDelay property, for the delay between the tooltip appearing and it being hidden again. The default values are 1 second for the showDelay and 200 milliseconds for the hideDelay.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Aren't the default values the other way around? Surely 200ms is not enough time to read any tooltip. Unless hide delay is something other than the total time it stays up before auto-hiding... – Manius Nov 30 '21 at 21:57
  • @Manius Not according to the [documentation](https://openjfx.io/javadoc/16/javafx.controls/javafx/scene/control/Tooltip.html#hideDelayProperty), though I agree it seems weird. – James_D Nov 30 '21 at 21:59
16

There is an existing feature request for this: JDK-8090477 Customizable visibility timing for Tooltip.

The feature request is currently scheduled for integration into Java 9. Attached to the issue I linked is a patch you can apply to allow you to get this functionality in earlier Java versions.

Your other option is just to create your own popup control using one of the techniques in:

Community
  • 1
  • 1
jewelsea
  • 150,031
  • 14
  • 366
  • 406
9

I find that with the above implementation there's still a delay sometimes that I cannot explain.

The following has worked for me and removed the delay entirely:

public static void bindTooltip(final Node node, final Tooltip tooltip){
   node.setOnMouseMoved(new EventHandler<MouseEvent>(){
      @Override  
      public void handle(MouseEvent event) {
         // +15 moves the tooltip 15 pixels below the mouse cursor;
         // if you don't change the y coordinate of the tooltip, you
         // will see constant screen flicker
         tooltip.show(node, event.getScreenX(), event.getScreenY() + 15);
      }
   });  
   node.setOnMouseExited(new EventHandler<MouseEvent>(){
      @Override
      public void handle(MouseEvent event){
         tooltip.hide();
      }
   });
}
Aleks Messier
  • 91
  • 1
  • 2
  • This will show a bunch of tooltips one after another while it will not hide anything. The tooltips won't disappear from my screen until I kill the process:)) – Madrugada Aug 28 '18 at 11:46
  • The reason is that the mouse event MouseMoved is being used. I changed it to MouseEntered. This has the disadvantage that it doesn't follow the cursor. What you can do though is change the MouseMoved function to MouseEntered, and in the mouseMoved function you set tooltip.setAnchorX(event.getScreenX()) and likewise with Y. I suggested an edit. If it isn't there yet, reply and I send you the code. – JoschJava Sep 02 '18 at 15:24
  • Instead of suggesting an edit please post an answer of your own here. And add the link to this answer explain what's wrong with this answer, what do you've improved in your answer. – Shashanth Sep 02 '18 at 17:33
  • which imports are you using? – SovietFrontier Mar 21 '19 at 16:22
6

In JavaFx 9, the tooltip delay can be set via CSS. It sounds pervert, to do this in a stylesheet, but probably this is what they mean when they say "The details (such as the amount of delay, etc) is left to the Skin implementation.".

https://docs.oracle.com/javase/9/docs/api/javafx/scene/doc-files/cssref.html#tooltip

So you can do something like this:

.tooltip {
    -fx-show-delay: 250ms;
}
Franz Deschler
  • 2,456
  • 5
  • 25
  • 39
5

Extends Tooltip or put on Application class. (Only java8)

    /**
     * <p>
     * Hack TooltipBehavior 
     */
    static {
        try {
            Tooltip obj = new Tooltip();
            Class<?> clazz = obj.getClass().getDeclaredClasses()[1];
            Constructor<?> constructor = clazz.getDeclaredConstructor(
                    Duration.class,
                    Duration.class,
                    Duration.class,
                    boolean.class);
            constructor.setAccessible(true);
            Object tooltipBehavior = constructor.newInstance(
                    new Duration(250),  //open
                    new Duration(5000), //visible
                    new Duration(200),  //close
                    false);
            Field fieldBehavior = obj.getClass().getDeclaredField("BEHAVIOR");
            fieldBehavior.setAccessible(true);
            fieldBehavior.set(obj, tooltipBehavior);
        }
        catch (Exception e) {
            Logger.error(e);
        }
    }
Bruno Prado
  • 51
  • 1
  • 1
  • 4
    a similar approach based however on a separate utility method to allow setting the global timing parameters on tooltips in JavaFX (Java 8) can be found here: https://gist.github.com/darmbrust/9559744d1b1dada434a3 – Marco May 27 '16 at 11:39
3

Indeed since JavaFX 2, the behavior of the tooltips is managed internally within the class Tooltip by the static field BEHAVIOR that cannot be modified using specific public methods so for now If you don't want to reinvent the wheel or wait for Java 9, the only way is by using the Reflection API as next:

/**
 * Hack allowing to modify the default behavior of the tooltips.
 * @param openDelay The open delay, knowing that by default it is set to 1000.
 * @param visibleDuration The visible duration, knowing that by default it is set to 5000.
 * @param closeDelay The close delay, knowing that by default it is set to 200.
 * @param hideOnExit Indicates whether the tooltip should be hide on exit, 
 * knowing that by default it is set to false.
 */
private static void updateTooltipBehavior(double openDelay, double visibleDuration,
    double closeDelay, boolean hideOnExit) {
    try {
        // Get the non public field "BEHAVIOR"
        Field fieldBehavior = Tooltip.class.getDeclaredField("BEHAVIOR");
        // Make the field accessible to be able to get and set its value
        fieldBehavior.setAccessible(true);
        // Get the value of the static field
        Object objBehavior = fieldBehavior.get(null);
        // Get the constructor of the private static inner class TooltipBehavior
        Constructor<?> constructor = objBehavior.getClass().getDeclaredConstructor(
            Duration.class, Duration.class, Duration.class, boolean.class
        );
        // Make the constructor accessible to be able to invoke it
        constructor.setAccessible(true);
        // Create a new instance of the private static inner class TooltipBehavior
        Object tooltipBehavior = constructor.newInstance(
            new Duration(openDelay), new Duration(visibleDuration),
            new Duration(closeDelay), hideOnExit
        );
        // Set the new instance of TooltipBehavior
        fieldBehavior.set(null, tooltipBehavior);
    } catch (Exception e) {
        throw new IllegalStateException(e);
    }
}
Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122
3

Hello, I can't comment until I reach a reputation of 50 but I wanted to correct the response given by Bruno Pado. The code he posted did not work in JDK 8u121. The issue was in which declared field it accessed. The fix is simple, change the index from 1 to 0. Working code posted below:

/**
 * <p>
 * Hack TooltipBehavior 
 */
static {
    try {
        Tooltip obj = new Tooltip();
        Class<?> clazz = obj.getClass().getDeclaredClasses()[0];
        Constructor<?> constructor = clazz.getDeclaredConstructor(
                Duration.class,
                Duration.class,
                Duration.class,
                boolean.class);
        constructor.setAccessible(true);
        Object tooltipBehavior = constructor.newInstance(
                new Duration(250),  //open
                new Duration(5000), //visible
                new Duration(200),  //close
                false);
        Field fieldBehavior = obj.getClass().getDeclaredField("BEHAVIOR");
        fieldBehavior.setAccessible(true);
        fieldBehavior.set(obj, tooltipBehavior);
    }
    catch (Exception e) {
        Logger.error(e);
    }
}

Hope this helps anyone who's looking to edit the tooltip delay while we wait for JFX9.

d_scalzi
  • 425
  • 4
  • 13
  • 1
    I think the point to take away here is that relying on the order that `getDeclaredClasses` is probably not a good idea since it is not always consistent between binary versions. If you know the class you're looking for then you can probably filter by that. – apokryfos Apr 08 '17 at 07:36