简体   繁体   English

mousemoved事件中的Javafx滑块值

[英]Javafx slider value at mousemoved event

I am making a media player and am trying to get the playback slider value at the cursor position when hovering over the slider bar. 我正在制作媒体播放器,并且当我将鼠标悬停在滑块栏上时,我试图将光标位置的播放滑块值设置为。 In an attempt to do this, i have used the following: 为了做到这一点,我使用了以下内容:

    timeSlider.addEventFilter(MouseEvent.MOUSE_MOVED, event -> System.out.println("hovering"));

which prints "hovering" whenever the mouse changes position over the slider. 只要鼠标在滑块上改变位置,就会打印“悬停”。 Can anyone please show me how to get the value of the slider at the current cursor position? 谁能告诉我如何在当前光标位置获取滑块的值? I can only figure out how to get the value at the thumb position. 我只能弄清楚如何在拇指位置获取值。

Thanks in advance. 提前致谢。

Here is a bit (maybe more than a bit) of a hack that works if you are showing the axis under the slider. 如果您在滑块下显示轴,则有一点(可能不止一点)黑客有效。 It relies on looking up the axis via its css class, converting the mouse coordinates to coordinates relative to the axis, and then using API from ValueAxis to convert to the value: 它依赖于通过其css类查找轴,将鼠标坐标转换为相对于轴的坐标,然后使用ValueAxis API转换为值:

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;

public class TooltipOnSlider extends Application {

    @Override
    public void start(Stage primaryStage) {
        Slider slider = new Slider(5, 25, 15);
        slider.setShowTickMarks(true);
        slider.setShowTickLabels(true);
        slider.setMajorTickUnit(5);

        Label label = new Label();
        Popup popup = new Popup();
        popup.getContent().add(label);

        double offset = 10 ;

        slider.setOnMouseMoved(e -> {
            NumberAxis axis = (NumberAxis) slider.lookup(".axis");
            Point2D locationInAxis = axis.sceneToLocal(e.getSceneX(), e.getSceneY());
            double mouseX = locationInAxis.getX() ;
            double value = axis.getValueForDisplay(mouseX).doubleValue() ;
            if (value >= slider.getMin() && value <= slider.getMax()) {
                label.setText(String.format("Value: %.1f", value));
            } else {
                label.setText("Value: ---");
            }
            popup.setAnchorX(e.getScreenX());
            popup.setAnchorY(e.getScreenY() + offset);
        });

        slider.setOnMouseEntered(e -> popup.show(slider, e.getScreenX(), e.getScreenY() + offset));
        slider.setOnMouseExited(e -> popup.hide());

        StackPane root = new StackPane(slider);
        primaryStage.setScene(new Scene(root, 350, 80));
        primaryStage.show();

    }

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

This is mostly a bug-track-down: James's answer is perfect - only hampered by 2 issues: 这主要是一个错误追踪:詹姆斯的答案是完美的 - 只受到两个问题的阻碍:

  1. the axis has to be visible, that is at least one of ticks or labels must be showing (in practice not a big obstacle: if you want to get the values at mouseOver you'r most probably showing the ticks anyway) 轴必须是可见的,即必须显示刻度线或标签中的至少一个(实际上不是一个很大的障碍:如果你想获得mouseOver上的值,你最有可能显示刻度线)

  2. A bug in SliderSkin which introduce a slight skew of axis value vs slider value. SliderSkin中的一个错误,它引入了轴值与滑块值的轻微偏斜。

To see the latter, here's a slight variation of James's code. 要看到后者,这里有詹姆斯代码的略微变化。 To see the asynchronicity, move the mouse over the slider then click. 要查看异步性,请将鼠标移到滑块上,然后单击。 We expect the value of the popup to be the same as the value of the slider (shown in the label at the bottom). 我们希望弹出窗口的值与滑块的值相同(显示在底部的标签中)。 With core SliderSkin, they differ slightly. 有了SliderSkin核心,它们略有不同。

public class TooltipOnSlider extends Application {

    private boolean useAxis;
    @Override
    public void start(Stage primaryStage) {
        Slider slider = new Slider(5, 25, 15);
        useAxis = true;
        // force an axis to be used
        slider.setShowTickMarks(true);
        slider.setShowTickLabels(true);
        slider.setMajorTickUnit(5);

        // slider.setOrientation(Orientation.VERTICAL);
        // hacking around the bugs in a custom skin
        //  slider.setSkin(new MySliderSkin(slider));
        //  slider.setSkin(new XSliderSkin(slider));

        Label label = new Label();
        Popup popup = new Popup();
        popup.getContent().add(label);

        double offset = 30 ;

        slider.setOnMouseMoved(e -> {
            NumberAxis axis = (NumberAxis) slider.lookup(".axis");
            StackPane track = (StackPane) slider.lookup(".track");
            StackPane thumb = (StackPane) slider.lookup(".thumb");
            if (useAxis) {
                // James: use axis to convert value/position
                Point2D locationInAxis = axis.sceneToLocal(e.getSceneX(), e.getSceneY());
                boolean isHorizontal = slider.getOrientation() == Orientation.HORIZONTAL;
                double mouseX = isHorizontal ? locationInAxis.getX() : locationInAxis.getY() ;
                double value = axis.getValueForDisplay(mouseX).doubleValue() ;
                if (value >= slider.getMin() && value <= slider.getMax()) {
                    label.setText("" + value);
                } else {
                    label.setText("Value: ---");
                }

            } else {
                // this can't work because we don't know the internals of the track
                Point2D locationInAxis = track.sceneToLocal(e.getSceneX(), e.getSceneY());
                double mouseX = locationInAxis.getX();
                double trackLength = track.getWidth();
                double percent = mouseX / trackLength;
                double value = slider.getMin() + ((slider.getMax() - slider.getMin()) * percent);
                if (value >= slider.getMin() && value <= slider.getMax()) {
                    label.setText("" + value);
                } else {
                    label.setText("Value: ---");
                }
            }
            popup.setAnchorX(e.getScreenX());
            popup.setAnchorY(e.getScreenY() + offset);
        });

        slider.setOnMouseEntered(e -> popup.show(slider, e.getScreenX(), e.getScreenY() + offset));
        slider.setOnMouseExited(e -> popup.hide());

        Label valueLabel = new Label("empty");
        valueLabel.textProperty().bind(slider.valueProperty().asString());
        BorderPane root = new BorderPane(slider);
        root.setBottom(valueLabel);
        primaryStage.setScene(new Scene(root, 350, 100));
        primaryStage.show();
        primaryStage.setTitle("useAxis: " + useAxis + " mySkin: " + slider.getSkin().getClass().getSimpleName());
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(TooltipOnSlider.class
            .getName());
}

Note that there's an open issue which reports a similar behavior (though not so easy to see) 请注意,有一个未解决的问题报告了类似的行为(虽然不是那么容易看到)

Looking into the code of SliderSkin, the culprit seems to be an incorrect calculation of the relative value from a mouse event on the track: 查看SliderSkin的代码,罪魁祸首似乎是对轨道上鼠标事件的相对值的错误计算:

track.setOnMousePressed(me -> {
    ... 
    double relPosition = (me.getX() / trackLength);
    getBehavior().trackPress(me, relPosition);
    ...
});

where track is positioned in the slider as: 轨道在滑块中的位置为:

// layout track 
track.resizeRelocate((int)(trackStart - trackRadius),
                     trackTop ,
                     (int)(trackLength + trackRadius + trackRadius),
                     trackHeight);

Note that the active width (aka: trackLenght) of the track is offset by trackRadius, thus calculating the relative distance with the raw mousePosition on the track gives a slight error. 请注意,轨道的活动宽度(aka:trackLenght)由trackRadius偏移,因此计算轨道上原始mousePosition的相对距离会产生轻微错误。

Below is a crude custom skin that replaces the calc simply as a test if the little application behaves as expected. 下面是一个原始的自定义外观,如果小应用程序按预期运行,则仅将测试替换为calc。 Looks terrible due the need to use reflection to access super's fields/methods but now has slider and axis value in synch. 由于需要使用反射来访问超级的字段/方法,但现在具有同步的滑块和轴值,看起来很糟糕。

The quick hack: 快速入侵:

/**
 * Trying to work around down to the slight offset.
 */
public static class MySliderSkin extends SliderSkin {

    /**
     * Hook for replacing the mouse pressed handler that's installed by super.
     */
    protected void installListeners() {
        StackPane track = (StackPane) getSkinnable().lookup(".track");
        track.setOnMousePressed(me -> {
            invokeSetField("trackClicked", true);
            double trackLength = invokeGetField("trackLength");
            double trackStart = invokeGetField("trackStart");
            // convert coordinates into slider
            MouseEvent e = me.copyFor(getSkinnable(), getSkinnable());
            double mouseX = e.getX(); 
            double position;
            if (mouseX < trackStart) {
                position = 0;
            } else if (mouseX > trackStart + trackLength) {
                position = 1;
            } else {
               position = (mouseX - trackStart) / trackLength;
            }
            getBehavior().trackPress(e, position);
            invokeSetField("trackClicked", false);
        });
    }

    private double invokeGetField(String name) {
        Class clazz = SliderSkin.class;
        Field field;
        try {
            field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            return field.getDouble(this);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return 0.;
    }

    private void invokeSetField(String name, Object value) {
        Class clazz = SliderSkin.class;
        try {
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            field.set(this, value);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    /**
     * Constructor - replaces listener on track.
     * @param slider
     */
    public MySliderSkin(Slider slider) {
        super(slider);
        installListeners();
    }

}

A deeper fix might be to delegate all the dirty coordinate/value transformations to the axis - that's what it is designed to do. 更深层次的修复可能是将所有脏坐标/值转换委托给轴 - 这就是它的设计目的。 This requires the axis to be part of the scenegraph always and only toggle its visibilty with ticks/labels showing. 这要求轴始终是场景图的一部分,并且仅用刻度/标签显示来切换其可见性。 A first experiment looks promising. 一个实验很有希望。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM