mousemoved事件中的Javafx滑块值

时间:2015-12-02 06:33:29

标签: java javafx-8 uicontrol

我正在制作媒体播放器,并且当我将鼠标悬停在滑块栏上时,我试图将光标位置设置为播放滑块。为了做到这一点,我使用了以下内容:

    timeSlider.addEventFilter(MouseEvent.MOUSE_MOVED, event -> System.out.println("hovering"));
只要鼠标在滑块上改变位置,

就会打印“悬停”。谁能告诉我如何在当前光标位置获取滑块的值?我只能弄清楚如何在拇指位置获取值。

提前致谢。

2 个答案:

答案 0 :(得分:1)

如果您在滑块下显示轴,则有一点(可能不止一点)黑客有效。它依赖于通过其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);
    }
}

答案 1 :(得分:0)

这主要是一个错误追踪:詹姆斯的答案是完美的 - 只受到2个问题的阻碍:

  1. 轴必须是可见的,即必须显示至少一个刻度线或标签(实际上不是一个很大的障碍:如果你想在mouseOver上获取值,你很可能会显示无论如何都要打勾)

  2. SliderSkin中的一个错误,它引入了轴值与滑块值的轻微偏斜。

  3. 要看到后者,这里是詹姆斯代码的略微变化。要查看异步性,请将鼠标移到滑块上,然后单击。我们希望弹出窗口的值与滑块的值相同(显示在底部的标签中)。 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());
    }
    

    请注意,open issue会报告类似的行为(虽然不是很容易看到)

    查看SliderSkin的代码,罪魁祸首似乎是对轨道上鼠标事件的相对值的错误计算:

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

    其中曲目位于滑块中:

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

    请注意,轨道的活动宽度(aka:trackLenght)由trackRadius偏移,因此计算轨道上原始mousePosition的相对距离会产生轻微错误。

    下面是一个原始的自定义皮肤,如果小应用程序按预期运行,它将简单地替换为calc。看起来很糟糕,因为需要使用反射来访问超级域/方法,但现在有滑块和轴值同步。

    快速破解:

    /**
     * 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();
        }
    
    }
    

    更深层次的修复可能是将所有脏坐标/值转换委托给轴 - 这就是它的设计目的。这要求轴始终是场景图的一部分,并且仅用刻度/标签显示来切换其可见性。 first experiment很有希望。