mousemoved事件中的Javafx滑块值

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

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

只要鼠标在滑块上改变位置,就会打印“hover”。 谁能告诉我如何在当前光标位置获取滑块的值? 我只能弄清楚如何在拇指位置获取值。

提前致谢。

如果您在滑块下显示轴,则有一点(可能不止一点)黑客有效。 它依赖于通过其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. 轴必须是可见的,即必须显示刻度线或标签中的至少一个(实际上不是一个很大的障碍:如果你想获得mouseOver的值,你最有可能显示刻度线)

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

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

请注意,有一个未解决的问题报告了类似的行为(虽然不是那么容易看到)

查看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。 由于需要使用reflection来访问超级的字段/方法,但现在有滑块和轴值同步,看起来很糟糕。

快速破解:

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

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