我正在制作媒体播放器,并且当我将鼠标悬停在滑块栏上时,我试图将光标位置设置为播放滑块。为了做到这一点,我使用了以下内容:
timeSlider.addEventFilter(MouseEvent.MOUSE_MOVED, event -> System.out.println("hovering"));
只要鼠标在滑块上改变位置,就会打印“悬停”。谁能告诉我如何在当前光标位置获取滑块的值?我只能弄清楚如何在拇指位置获取值。
提前致谢。
答案 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个问题的阻碍:
轴必须是可见的,即必须显示至少一个刻度线或标签(实际上不是一个很大的障碍:如果你想在mouseOver上获取值,你很可能会显示无论如何都要打勾)
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());
}
请注意,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很有希望。