Keep Tooltip open as long as mouse is over it

2019-09-15 19:08发布

I create a Tooltip for a TableColumn header via fxml like this:

<TableColumn>
    <cellValueFactory>
        <PropertyValueFactory property="someProperty" />
    </cellValueFactory>
    <graphic>
        <Label text="Column 1">
            <tooltip>
                <Tooltip text="Tooltip text" />
            </tooltip>
        </Label>
    </graphic>
</TableColumn>

I would like to keep the tooltip open if I move the mouse over the tooltip. Eventually I would like to have clickable links in the tooltip text (Just like Eclipse JavaDoc tooltips). Is that possible?

Edit: Considering the answer, I am trying the following now:

Label label = new Label();
label.setText("test text");
DelayedTooltip beakerTip = new DelayedTooltip();
beakerTip.setDuration(3000);
beakerTip.setText("Science from Base: 12");
beakerTip.isHoveringTarget(label);
Tooltip tooltip = new Tooltip();
tooltip.setText("test tooltip text");
label.setTooltip(beakerTip);
myTableColumn.setGraphic(label);

Here the problem is that the label is not the same as the Tooltip. So if the mouse is over the Tooltip but not over the label, the Tooltip is hidden. I cannot pass the Tooltip itself as a hover target, since it is not a Node.

3条回答
干净又极端
2楼-- · 2019-09-15 19:19

Indeed it is possible, but it involves basically gutting most of the basic functionality of the tooltip. This is how I implemented the same thing:

First I made a custom tooltip that was based off the basic tooltip(this code is a modification of a similar question)

public class DelayedTooltip extends Tooltip {

    private int duration = 0;
    private BooleanProperty isHoveringPrimary = new SimpleBooleanProperty(false);
    private BooleanProperty isHoveringSecondary = new SimpleBooleanProperty(false);

    public void setDuration(int d) {
        duration = d;
    }

    public BooleanProperty isHoveringPrimaryProperty()
    {
        return isHoveringPrimary;
    }

    public BooleanProperty isHoveringSecondaryProperty()
    {
        return isHoveringSecondary;
    }

    public void isHoveringTargetPrimary(Node node){
        node.setOnMouseEntered(e -> isHoveringPrimary.set(true));
        node.setOnMouseExited(e -> isHoveringPrimary.set(false));
    }

       //Usually you will use the tooltip here so enter tooltip.getGraphic() for the node.
    public void isHoveringTargetSecondary(Node node){
        node.setOnMouseEntered(e -> isHoveringTooltip.set(true)):
        node.setOnMouseExited(e -> isHoveringTooltip.set(false));
    }

    @Override
    public void hide() {
        if(isHoveringPrimary.get()==true || isHoveringTooltip.get()==true)
        {
            Timeline timeline = new Timeline();
            KeyFrame key = new KeyFrame(Duration.millis(duration));
            timeline.getKeyFrames().add(key);
            timeline.setOnFinished(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent t) {
                    DelayedTooltip.super.hide();
                }
            });
            timeline.play();
        }
        else
        {
            DelayedTooltip.super.hide();
        }
    }
}

And then this is how I installed the tooltip

    DelayedTooltip beakerTip = new DelayedTooltip();
    beakerTip.setDuration(999999);
    beakerTip.setText("Science from Base: 12");
    beakerTip.isHoveringTargetPrimary(beakerView);
        beakerTip.isHoveringTargetSecondary(beakerTip.geoGraphic());

You could edit this and make it into one method with multiple parameters if you so wish, but otherwise, this does work.

查看更多
时光不老,我们不散
3楼-- · 2019-09-15 19:29

I use this class now and it works as expected:

public class HoveringTooltip extends Tooltip {

    private Timer timer = new Timer();
    private Map<Object, Boolean> mapHoveringTarget2Hovering = new ConcurrentHashMap<>();
    private final int duration;

    public HoveringTooltip(int duration) {
        super.setAutoHide(false);
        this.duration = duration;
    }

    public void addHoveringTarget(Node object) {

        mapHoveringTarget2Hovering.put(object, false);
        object.setOnMouseEntered(e -> {
            onMouseEntered(object);
        });
        object.setOnMouseExited(e -> {
            onMouseExited(object);
        });
    }

    public void addHoveringTarget(Scene object) {

        mapHoveringTarget2Hovering.put(object, false);
        object.setOnMouseEntered(e -> {
            onMouseEntered(object);
        });
        object.setOnMouseExited(e -> {
            onMouseExited(object);
        });
    }

    @Override
    public void hide() {

        // super.hide();
    }

    public boolean isHovering() {

        return isHoveringProperty().get();
    }

    public BooleanProperty isHoveringProperty() {

        synchronized(mapHoveringTarget2Hovering) {
            for(Entry<Object, Boolean> e : mapHoveringTarget2Hovering.entrySet()) {
                if(e.getValue()) {
                    // if one hovering target is hovering, return true
                    return new ReadOnlyBooleanWrapper(true);
                }
            }
            // no hovering on any target, return false
            return new ReadOnlyBooleanWrapper(false);
        }
    }

    private synchronized void onMouseEntered(Object object) {

        // System.err.println("Mouse entered for " + object + ", canelling timer");
        // stop a potentially running hide timer
        timer.cancel();
        mapHoveringTarget2Hovering.put(object, true);
    }

    private synchronized void onMouseExited(Object object) {

        // System.err.println("Mouse exit for " + object + ", starting timer");
        mapHoveringTarget2Hovering.put(object, false);
        startTimer();
    }

    private void startTimer() {

        timer.cancel();
        timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {

                Platform.runLater(new Runnable() {

                    @Override
                    public void run() {

                        if(!isHovering())
                            HoveringTooltip.super.hide();
                    }
                });
            }
        }, duration);
    }
}
查看更多
Bombasti
4楼-- · 2019-09-15 19:34

Here's a way to hack the behavior of a Tooltip:

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;

import java.lang.reflect.Field;

/**
 * @author rdeardorff
 */
public class TooltipDelay {

    public static void hackTooltipActivationTimer( Tooltip tooltip, int delay ) {
        hackTooltipTiming( tooltip, delay, "activationTimer" );
    }

    public static void hackTooltipHideTimer( Tooltip tooltip, int delay ) {
        hackTooltipTiming( tooltip, delay, "hideTimer" );
    }

    private static void hackTooltipTiming( Tooltip tooltip, int delay, String property ) {
        try {
            Field fieldBehavior = tooltip.getClass().getDeclaredField( "BEHAVIOR" );
            fieldBehavior.setAccessible( true );
            Object objBehavior = fieldBehavior.get( tooltip );

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

            objTimer.getKeyFrames().clear();
            objTimer.getKeyFrames().add( new KeyFrame( new Duration( delay ) ) );
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }
}
查看更多
登录 后发表回答