Hi Sam, all, FYI: I have been encountering a similar behaviour but I have been *wrongly* thinking it relates to ControlsFX PropertySheet which uses labels also. see https://github.com/controlsfx/controlsfx/issues/1235
The sample with buttons works fine while PropertySheet does not work as expected. -- Daniel On Fri, Jun 19, 2020 at 3:52 PM Sam Hutchins < [email protected]> wrote: > Hi, > > I've been doing some investigation into a layout bug in JavaFX on Windows > with non-integer scaling values. I think it's related to JDK-8199592, and > I've put a small example that will reproduce these layout bugs at the end > of this email. The most obvious layout error is truncation of labels in > many controls. I've used CheckBoxes in this example, but the truncation > errors are apparent in any labeled control. I suspect the issue affects > other controls too (and probably the entire layout), but in more subtle > ways. With scaling values of 1.25 or 1.5, the label truncation only happens > in the dialog box. At 1.75 the issue is apparent in both windows. > > I _think_ the issue is a combination of caching and invalid calculations. > I stepped through the `layoutLabelInArea(x, y, w, h, alignment)` method in > LabeledSkinBase and found that 3 layout passes take place. > > In the first and second passes, all the layout calculations appear to be > done without taking scaling into account. > The 3rd pass appears to be when a scene is involved, and the snap* methods > start having an impact. It looks like some values are scaled appropriately, > but others are still using the pre-scene cached values. In particular, the > call to `leftLabelPadding()` returns 5 for the first 2 passes, but 5.6 in > the 3rd (at 1.25 scaling). The value of `w` doesn't change though, and that > .6 increase in padding causes the label to be truncated. When you interact > with the CheckBox to trigger a 4th layout, the value of `w` is increased by > .6, and the layout appears to work as expected. > > I've hacked some of the caching out of `Parent` by adding a call to > clearSizeCache() to the start of the `prefWidth(height)` and > `prefHeight(width)` methods, which forces JavaFX to re-compute everything > on every call. This resolves the issues for simple layouts at 1.25 and 1.5 > scaling (but not 1.75). > > This doesn't resolve issues in more complicated layouts, however, as many > subclasses of `Parent` have caching of their own. For example, controls in > GridPanes will still exhibit this behaviour. I've hacked some caching out > of that by removing the early return in the `computePrefHeights(widths)` > and `computePrefWidths(heights)` methods, which resolves it for the > GridPane case. I imagine there are many other instances where these cached > values aren't correctly invalidated. > > I think there's also a fundamental issue with the layout calculations with > non-integer scaling, as the layout is _always_ wrong at 1.75. I suspect > that the layout calculations are always wrong, but the error at lower > scaling values isn't enough to cause visible issues after pixel snapping. > > I'm not really sure where to go from here, as I'm not at all familiar with > how and when JavaFX invalidates its layout calculations. If anyone can > point me at some other threads to pull, I'd be grateful. > > Thanks, > > Sam > > > Code to reproduce: > import javafx.application.Application; > import javafx.scene.Scene; > import javafx.scene.control.Alert; > import javafx.scene.control.Button; > import javafx.scene.control.ButtonType; > import javafx.scene.control.CheckBox; > import javafx.scene.layout.GridPane; > import javafx.scene.layout.VBox; > import javafx.stage.Stage; > public class LayoutBug > { > public static void main(String[] args) > { > System.setProperty("glass.win.uiScale", "1.5"); > Application.launch(MainWindow.class); > } > public static class MainWindow extends Application > { > public void start(final Stage primaryStage) > { > final var button = new Button("Show Dialog"); > button.setOnAction(e -> > { > final var alert = new Alert(Alert.AlertType.NONE); > alert.initOwner(primaryStage); > alert.getButtonTypes().add(ButtonType.OK); > alert.getDialogPane().setContent(createTestUi()); > alert.show(); > }); > final var root = createTestUi(); > root.getChildren().add(button); > primaryStage.setScene(new Scene(root, 400, 600)); > primaryStage.show(); > } > private VBox createTestUi() > { > final var gridPane = new GridPane(); > gridPane.addRow(0, new CheckBox("Checkbox in gridpane")); > return new VBox(10, > gridPane, > new CheckBox("Checkbox outside gridpane")); > } > } > } > >
