Hi, In Region class we have the property snapToPixel, which is intended to snap/round-off the pixel values of bounds for achieving crisp user interfaces (as mentioned in the javadoc <https://download.java.net/java/GA/javafx21.0.1/e5ab43c6aed54893b0840c1f2dcfca4d/docs/api/javafx.graphics/javafx/scene/layout/Region.html#snapToPixelProperty()> ).
But is it intended/expected that, toggling this property value can affect the core layout behavior? At-least for standard controls? *The Problem:* In scenarios, when the TableRow width is 1px greater than the VirtualFlow width, the horizontal scroll bar of VirtualFlow is displayed. Which is valid and I agree with that (as in below screenshot). *In the below picture, the VirtualFlow width is 307px and the TableRow width is 308px*. [image: LPH0Ukdr.png] In the above scenario, when I drag the scroll bar thumb, I would expect a one pixel movement of the TableRow. But that is not happening. In fact, nothing happens when I drag the thumb (neither the content is moving nor the thumb is moving). Upon careful investigation, it is found that the scroll bar track width is same as thumb width, which is causing to ignore the drag event. Below is the code of thumb drag event in ScrollBarSkin class, where you can notice the if condition to skip the computing if thumb length is not less than track length. thumb.setOnMouseDragged(me -> { if (me.isSynthesized()) { // touch-screen events handled by Scroll handler me.consume(); return; } /* ** if max isn't greater than min then there is nothing to do here */ if (getSkinnable().getMax() > getSkinnable().getMin()) { /* ** if the tracklength isn't greater then do nothing.... */ if (trackLength > thumbLength) { Point2D cur = thumb.localToParent(me.getX(), me.getY()); if (dragStart == null) { // we're getting dragged without getting a mouse press dragStart = thumb.localToParent(me.getX(), me.getY()); } double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX(); behavior.thumbDragged(preDragThumbPos + dragPos / (trackLength - thumbLength)); } me.consume(); } }); Now, when I further investigate why the track and thumb lengths are same, I noticed that it is in fact the code of thumb length calcuation that snaps the computed value. thumbLength = snapSizeX(Utils.clamp(minThumbLength(), (trackLength * visiblePortion), trackLength)); In the above line, the computed trackLength is 289.0px and the computed thumbLength is 288.06168831168833px. But because this value is snapped, the value is rounded to 289.0px which is equal to trackLength. *Solution / Workaround:* >From the above observation, it is clear that the snapToPixel property of ScrollBar is impacting the computed values. So I created a custom VirtualFlow to access the scroll bars and turn off the snapToPixel property. class CustomVirtualFlow<T extends IndexedCell> extends VirtualFlow<T> { public CustomVirtualFlow() { getHbar().setSnapToPixel(false); getVbar().setSnapToPixel(false); } } Once I included this tweak, the scroll bar is active and when I drag the thumb, it is sliding my content. You can notice the difference in the below gif. [image: pBqQPKDf.gif] So my question here is: Is this intended behavior to have snapToPixel=true by default, which is causing to show the scrollbar unnecessarily and it is my responsibility to turn off the snapToPixel properties? Or in other words, is this a JavaFX bug or not? Below is the demo code: import javafx.application.Application;import javafx.beans.property.ReadOnlyObjectWrapper;import javafx.geometry.Insets;import javafx.scene.Scene;import javafx.scene.control.*;import javafx.scene.control.skin.TableViewSkin;import javafx.scene.control.skin.VirtualFlow;import javafx.scene.layout.Priority;import javafx.scene.layout.VBox;import javafx.stage.Stage; import java.util.ArrayList;import java.util.List; public class HorizontalScrollBarIssueDemo extends Application { String CSS = "data:text/css," + """ .label{ -fx-font-weight: bold; } """; @Override public void start(final Stage stage) throws Exception { final VBox root = new VBox(); root.setSpacing(30); root.setPadding(new Insets(10)); final Scene scene = new Scene(root, 500, 350); scene.getStylesheets().add(CSS); stage.setScene(scene); stage.setTitle("Horizontal ScrollBar Issue " + System.getProperty("javafx.runtime.version")); stage.show(); VBox defaultBox = new VBox(10, new Label("Default TableView"), getTable(false)); VBox customBox = new VBox(10, new Label("TableView whose scrollbar's snapToPixel=false"), getTable(true)); root.getChildren().addAll(defaultBox, customBox); } private TableView<List<String>> getTable(boolean custom) { TableView<List<String>> tableView; if (custom) { tableView = new TableView<>() { @Override protected Skin<?> createDefaultSkin() { return new TableViewSkin<>(this) { @Override protected VirtualFlow<TableRow<List<String>>> createVirtualFlow() { return new CustomVirtualFlow<>(); } }; } }; } else { tableView = new TableView<>(); } tableView.setMaxHeight(98); tableView.setMaxWidth(309); VBox.setVgrow(tableView, Priority.ALWAYS); int colCount = 4; for (int i = 0; i < colCount; i++) { final int index = i; TableColumn<List<String>, String> column = new TableColumn<>("Option " + i); column.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().get(index))); tableView.getColumns().add(column); } for (int j = 0; j < 1; j++) { List<String> row = new ArrayList<>(); for (int i = 0; i < colCount; i++) { row.add("Row" + j + "-Opt" + i); } tableView.getItems().add(row); } return tableView; } class CustomVirtualFlow<T extends IndexedCell> extends VirtualFlow<T> { public CustomVirtualFlow() { getHbar().setSnapToPixel(false); getVbar().setSnapToPixel(false); } } } Regards, Sai Pradeep Dandem.