JavaFX Slider Tick Marks Initialized via CSS Disappear



我正在使用CSS配置我的JavaFX Slider,然后在代码中应用样式:

cssSlider.getStyleClass().add("slider-style");

当我第一次打开我的窗口时,勾号出现在CSS配置的Slider上。当我关闭并重新打开窗口时,勾号将不再存在。

下面的例子演示了使用2个Slider的异常情况,一个直接配置,另一个通过CSS配置。单击按钮可将窗口隐藏2秒钟。请注意,我直接在其中配置属性的Slider在隐藏和重新显示后工作良好,但CSS配置的Slider会在隐藏和再次显示后丢失其记号。

有人知道为什么显示、隐藏和重新显示窗口会导致勾号从CSS配置的Slider中消失吗?我是做错了什么,还是这是一个JavaFX错误?

sample.css:

.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}

CssExample.java:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.io.IOException;
import java.net.URL;

/**
* This simple example demonstrates that JavaFX Sliders configured with CSS only show their tick marks the first time
* they are shown.  If the Slider is hidden, then shown again, the tick marks are gone forever.
*/
public class CssExample extends Application {

@Override
public void start(Stage stage) throws InterruptedException, IOException {
Group root = new Group();
Scene scene = new Scene(root, 400, 200);
stage.setScene(scene);
stage.setTitle("Slider Sample");
scene.setFill(Color.BLACK);

GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(10);
grid.setHgap(70);

scene.setRoot(grid);
int rowNumber = 1;

Label directLabel = new Label("Slider from attribute assignment");
GridPane.setConstraints(directLabel, 1, rowNumber++);
grid.getChildren().add(directLabel);

Slider directSlider = new Slider();
GridPane.setConstraints(directSlider, 1, rowNumber++);
grid.getChildren().add(directSlider);
directSlider.setShowTickMarks(true);
directSlider.setSnapToTicks(true);
directSlider.setMajorTickUnit(5);
directSlider.setMinorTickCount(5);

Label cssLabel = new Label("Slider from CSS (tick marks disappear after hidden)");
GridPane.setConstraints(cssLabel, 1, rowNumber++);
grid.getChildren().add(cssLabel);

Slider cssSlider = new Slider();
GridPane.setConstraints(cssSlider, 1, rowNumber++);
grid.getChildren().add(cssSlider);
URL url = getClass().getResource("sample.css");
String cssString = url.toExternalForm();
scene.getStylesheets().add(cssString);
cssSlider.getStyleClass().add("slider-style");

Button button = new Button("Hide for 2 Seconds");
GridPane.setConstraints(button, 1, rowNumber++);
grid.getChildren().add(button);
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
stage.hide();
stage.show();
}
});

stage.show();
}

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

这是JavaFX错误吗?

是。

请参阅:https://github.com/openjdk/jfx/blob/fdc88341f1df8fb9c99356ada54b25124b77ea6e/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SliderSkin.java#L398

这是SliderSkinsetShowTickMarks方法的内部实现中的一个错误(在JavaFX 18.0.1中验证(

测试用例:

import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
public class CssExample extends Application {
private static final String CSS = // language=CSS
"""
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}      
""";
private static final String CSS_INLINE = "data:text/css," + CSS;
@Override
public void start(Stage stage) throws InterruptedException, IOException {
Platform.setImplicitExit(false);
Slider cssSlider = new Slider();
cssSlider.showTickMarksProperty().addListener((observable, oldValue, newValue) ->
System.out.println(cssSlider.showTickMarksProperty())
);
cssSlider.getStyleClass().add("slider-style");
PauseTransition hideAnimation = new PauseTransition(Duration.seconds(2));
hideAnimation.setOnFinished(e -> stage.show());
Button hideWindow = new Button("Hide for 2 Seconds");
hideWindow.setOnAction(e -> {
stage.hide();
hideAnimation.play();
});
Button closeApp = new Button("Close app");
closeApp.setOnAction(e -> Platform.exit());
VBox layout = new VBox(
10,
cssSlider, hideWindow, closeApp
);
layout.setPadding(new Insets(10));
layout.setPrefSize(400, 120);
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS_INLINE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

测试输出:

BooleanProperty [bean: Slider@132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
BooleanProperty [bean: Slider@132908b9[styleClass=slider slider-style], name: showTickMarks, value: false]
BooleanProperty [bean: Slider@132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]

它将showTicks从true切换到false,再切换回true,从而触发错误。

在当前实现的setShowTicks方法中:

private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
showTickMarks = (ticksVisible || labelsVisible);
Slider slider = getSkinnable();
if (showTickMarks) {
if (tickLine == null) {
tickLine = new NumberAxis();
tickLine.setAutoRanging(false);
tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
tickLine.setUpperBound(slider.getMax());
tickLine.setLowerBound(slider.getMin());
tickLine.setTickUnit(slider.getMajorTickUnit());
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setMinorTickVisible(ticksVisible);
// add 1 to the slider minor tick count since the axis draws one
// less minor ticks than the number given.
tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
if (slider.getLabelFormatter() != null) {
tickLine.setTickLabelFormatter(stringConverterWrapper);
}
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
} else {
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setMinorTickVisible(ticksVisible);
}
}
else  {
getChildren().clear();
getChildren().addAll(track, thumb);
//            tickLine = null;
}
getSkinnable().requestLayout();
}

第一次显示勾号时,它会这样做:

getChildren().clear();
getChildren().addAll(tickLine, track, thumb);

然后,当勾号被隐藏时,它会这样做:

getChildren().clear();
getChildren().addAll(track, thumb);

然后,当应该再次显示记号时,tickLine不会添加回子项,因此它再也不会显示记号。

最新更新