【问题标题】:Rectangle size binding to Scene size - JavaFX矩形大小绑定到场景大小 - JavaFX
【发布时间】:2019-08-27 19:36:37
【问题描述】:

我正在尝试编写一个简单的 JavaFX 应用程序,其中包含 NxM 矩阵的图形表示(默认为 100 x 100)。我想将矩阵大小绑定到场景大小,所以当我调整应用程序窗口的大小时,矩阵会跟随它并保持纵横比。绑定对于小矩阵(例如 10x10)无缝工作,但是当矩阵变大(例如 50x50)并且矩形变小以适合我的屏幕时,绑定过程变得不连续。矩阵有时甚至会大于窗口大小(无法看到所有单元格/矩形),并且矩阵大小似乎在整数值之间切换。

我所做的是创建一个 StackPane 作为场景内的根节点,并创建一个 GridPane 作为 StackPane 的子节点。我用 NxM 矩形矩阵填充了 GridPane。

代码如下:

public class GridPaneExample extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        int n = 100, m = 100; // matrix size
        double rectw = 5, recth = 5; // size of each rectangle
        StackPane root = new StackPane();
        Scene scene = new Scene(root, (m + 2) * rectw, (n + 2) * recth, Color.DARKOLIVEGREEN); // creating a scene with
                                                                                                // a frame around matrix
        primaryStage.setScene(scene);
        primaryStage.sizeToScene();

        GridPane gp = new GridPane();
        root.getChildren().add(gp);
        Rectangle[][] rects = new Rectangle[n][m];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j) {
                rects[i][j] = new Rectangle();
                rects[i][j].setWidth(rectw);
                rects[i][j].setHeight(recth);
                rects[i][j].setFill(Color.ANTIQUEWHITE);
                rects[i][j].setStrokeType(StrokeType.INSIDE);
                rects[i][j].setStrokeWidth(0.2);
                rects[i][j].setStroke(Color.GREY);

                rects[i][j].widthProperty().bind(gp.widthProperty().divide(m));
                rects[i][j].heightProperty().bind(gp.heightProperty().divide(n));

                GridPane.setRowIndex(rects[i][j], i);
                GridPane.setColumnIndex(rects[i][j], j);
                gp.getChildren().add(rects[i][j]);
            }

        rects[0][0].setFill(Color.RED);
        rects[0][m - 1].setFill(Color.RED);
        rects[n - 1][0].setFill(Color.RED);
        rects[n - 1][m - 1].setFill(Color.RED);

        StackPane.setAlignment(gp, Pos.TOP_CENTER);
        gp.minHeightProperty().bind(scene.heightProperty().subtract(2 * recth));
        gp.minWidthProperty().bind(scene.widthProperty().subtract(2 * rectw));
        gp.maxHeightProperty().bind(scene.heightProperty().subtract(2 * recth));
        gp.maxWidthProperty().bind(scene.widthProperty().subtract(2 * rectw));
        root.setLayoutY(recth);

        primaryStage.show();
        primaryStage.setMinHeight(primaryStage.getHeight());
        primaryStage.setMinWidth(primaryStage.getWidth());

    }
}

以下是显示不同大小矩阵的程序截图。

启动时的 10 x 10 矩阵应用程序(矩形大小为 20x20):

https://i.imgur.com/I2vMLSD.png

拉伸后的 10 x 10 矩阵应用:

https://i.imgur.com/9rR3e0g.png

启动时的 100 x 100 矩阵应用程序(矩形大小为 5x5):

https://i.imgur.com/1KCKg6W.png

一个 100 x 100 矩阵应用程序,稍微拉伸一下,看看矩阵是如何越界的:

https://i.imgur.com/pG8DxXA.png

那么,有没有办法让这种绑定体验变得流畅和一致,也许是使用矩形以外的其他东西?

编辑:发生了一件奇怪的事情。当我从代码中删除矩形绑定线并使用 100x100 矩阵启动应用程序时,矩阵看起来应该如此。该程序打印出 GridPane 和红色矩形的大小。正如预期的那样,它们分别是 500x500 和 5x5。但是,当我包含矩形绑定时(如代码所示),矩阵超出范围,如附加的第三张照片所示,但程序仍会打印出相同尺寸的 GridPane 和矩形。

【问题讨论】:

  • 如果您调用(在初始化时)GridPane#setSnapToPixel(false) 会发生什么?我记得当元素变得如此之小以至于整个像素太大而无法精确显示时,我会遇到这个问题。
  • @n247s 这解决了我的问题!没想到这么简单。请在答案中发布,以便我接受。

标签: java javafx


【解决方案1】:

如前所述,此问题可通过在 GridPane 上调用 Node#setSnapToPixel(false) 来解决。

说明

当节点变得如此之小以至于单个像素变得太大而无法准确显示时,就会出现此问题。

JavaFX 默认使用“像素捕捉”功能来确保“清晰/干净”的外观。这主要是可见的,边界在不使用时变得“模糊/模糊”。

JavaFX在后端定义了snapSpacesnapSizesnapPosition方法,分别对应Math.roundMath.ceilMath.round

这意味着如果节点的宽度为 2.5,边框为 1 像素,则它的大小应为 (1 + 2.5 + 1) = 4.5,上限为 5.0。如果您有 100 个元素获得 0.5 宽度奖励,则会导致 50 像素溢出!

将 'snapToPixel' 设置为 false 意味着它将忽略上述机制,并使用每个像素的混合颜色。如果您仔细观察,这种“模糊”效果在边界上很明显。但是为了完美的 IMO 布局,这是值得的。

【讨论】:

    【解决方案2】:

    这是我很久以前写的一个示例应用程序,它做了类似的事情。它使用 GridPane 中的可调整大小节点和 layoutBoundsProperty 上的侦听器来选择可调整大小节点的大小。不确定它是否真的是您感兴趣的(您在问题中采用的方法,经过一些修复或调整,可能适合您希望完成的工作),但可能值得看看另一种方法如果需要。

    相关代码适用于下面示例代码中ColorChooser 构造函数中的swatch 节点(抱歉,它有点长,它是为不同的目的而编写的,但看起来足够接近,值得完整发布):

    import javafx.application.Application;
    import javafx.beans.property.*;
    import javafx.event.*;
    import javafx.geometry.*;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.layout.*;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.stage.Stage;
    import javafx.stage.StageStyle;
    
    /**
     * Sample application for using the color chooser
     */
    public class ColorChooserSample extends Application {
        public void start(final Stage stage) throws Exception {
            // initialize the stage.
            stage.setTitle("Color Chooser");
            stage.initStyle(StageStyle.UTILITY);
    
            // create a new color chooser sized to the stage.
            final String[][] smallPalette = {
                    {"aliceblue", "#f0f8ff"}, {"antiquewhite", "#faebd7"}, {"aqua", "#00ffff"}, {"aquamarine", "#7fffd4"},
                    {"azure", "#f0ffff"}, {"beige", "#f5f5dc"}, {"bisque", "#ffe4c4"}, {"black", "#000000"},
                    {"blanchedalmond", "#ffebcd"}, {"blue", "#0000ff"}, {"blueviolet", "#8a2be2"}, {"brown", "#a52a2a"},
                    {"burlywood", "#deb887"}, {"cadetblue", "#5f9ea0"}, {"chartreuse", "#7fff00"}, {"chocolate", "#d2691e"},
                    {"coral", "#ff7f50"}, {"cornflowerblue", "#6495ed"}, {"cornsilk", "#fff8dc"}, {"crimson", "#dc143c"},
                    {"cyan", "#00ffff"}, {"darkblue", "#00008b"}, {"darkcyan", "#008b8b"}, {"darkgoldenrod", "#b8860b"},
            };
            final ColorChooser colorChooser = new ColorChooser(smallPalette);
            // to use the full web palette, just use the default constructor.
            // final ColorChooser colorChooser = new ColorChooser();
    
            final Scene scene = new Scene(colorChooser, 600, 500);
    
            // show the stage.
            stage.setScene(scene);
            stage.show();
    
            // monitor the color chooser's chosen color and respond to it.
            colorChooser.chosenColorProperty().addListener((observableValue, oldColor, newColor) ->
                    System.out.println("Chose: " + colorChooser.getChosenColorName() + " " + colorChooser.getChosenColor())
            );
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    /**
     * A Color Chooser Component - allows the user to select a color from a palette.
     */
    class ColorChooser extends VBox {
        private final double GOLDEN_RATIO = 1.618;
        private final double MIN_TILE_SIZE = 5;
        private final double nColumns;
        private final double nRows;
    
        /**
         * The color the user has selected or the default initial color (the first color in the palette)
         */
        private final ReadOnlyObjectWrapper<Color> chosenColor = new ReadOnlyObjectWrapper<Color>();
    
        public Color getChosenColor() {
            return chosenColor.get();
        }
    
        public ReadOnlyObjectProperty<Color> chosenColorProperty() {
            return chosenColor.getReadOnlyProperty();
        }
    
        /**
         * Friendly name for the chosen color
         */
        private final ReadOnlyObjectWrapper<String> chosenColorName = new ReadOnlyObjectWrapper<String>();
    
        public String getChosenColorName() {
            return chosenColorName.get();
        }
    
        /**
         * Preferred size for a web palette tile
         */
        private DoubleProperty prefTileSize = new SimpleDoubleProperty(MIN_TILE_SIZE);
    
        /**
         * A palette of colors from http://docs.oracle.com/javafx/2.0/api/javafx/scene/doc-files/cssref.html#typecolor
         */
        private static final String[][] webPalette = {
                {"aliceblue", "#f0f8ff"}, {"antiquewhite", "#faebd7"}, {"aqua", "#00ffff"}, {"aquamarine", "#7fffd4"},
                {"azure", "#f0ffff"}, {"beige", "#f5f5dc"}, {"bisque", "#ffe4c4"}, {"black", "#000000"},
                {"blanchedalmond", "#ffebcd"}, {"blue", "#0000ff"}, {"blueviolet", "#8a2be2"}, {"brown", "#a52a2a"},
                {"burlywood", "#deb887"}, {"cadetblue", "#5f9ea0"}, {"chartreuse", "#7fff00"}, {"chocolate", "#d2691e"},
                {"coral", "#ff7f50"}, {"cornflowerblue", "#6495ed"}, {"cornsilk", "#fff8dc"}, {"crimson", "#dc143c"},
                {"cyan", "#00ffff"}, {"darkblue", "#00008b"}, {"darkcyan", "#008b8b"}, {"darkgoldenrod", "#b8860b"},
                {"darkgray", "#a9a9a9"}, {"darkgreen", "#006400"}, {"darkgrey", "#a9a9a9"}, {"darkkhaki", "#bdb76b"},
                {"darkmagenta", "#8b008b"}, {"darkolivegreen", "#556b2f"}, {"darkorange", "#ff8c00"}, {"darkorchid", "#9932cc"},
                {"darkred", "#8b0000"}, {"darksalmon", "#e9967a"}, {"darkseagreen", "#8fbc8f"}, {"darkslateblue", "#483d8b"},
                {"darkslategray", "#2f4f4f"}, {"darkslategrey", "#2f4f4f"}, {"darkturquoise", "#00ced1"}, {"darkviolet", "#9400d3"},
                {"deeppink", "#ff1493"}, {"deepskyblue", "#00bfff"}, {"dimgray", "#696969"}, {"dimgrey", "#696969"},
                {"dodgerblue", "#1e90ff"}, {"firebrick", "#b22222"}, {"floralwhite", "#fffaf0"}, {"forestgreen", "#228b22"},
                {"fuchsia", "#ff00ff"}, {"gainsboro", "#dcdcdc"}, {"ghostwhite", "#f8f8ff"}, {"gold", "#ffd700"},
                {"goldenrod", "#daa520"}, {"gray", "#808080"}, {"green", "#008000"}, {"greenyellow", "#adff2f"},
                {"grey", "#808080"}, {"honeydew", "#f0fff0"}, {"hotpink", "#ff69b4"}, {"indianred", "#cd5c5c"},
                {"indigo", "#4b0082"}, {"ivory", "#fffff0"}, {"khaki", "#f0e68c"}, {"lavender", "#e6e6fa"},
                {"lavenderblush", "#fff0f5"}, {"lawngreen", "#7cfc00"}, {"lemonchiffon", "#fffacd"}, {"lightblue", "#add8e6"},
                {"lightcoral", "#f08080"}, {"lightcyan", "#e0ffff"}, {"lightgoldenrodyellow", "#fafad2"}, {"lightgray", "#d3d3d3"},
                {"lightgreen", "#90ee90"}, {"lightgrey", "#d3d3d3"}, {"lightpink", "#ffb6c1"}, {"lightsalmon", "#ffa07a"},
                {"lightseagreen", "#20b2aa"}, {"lightskyblue", "#87cefa"}, {"lightslategray", "#778899"}, {"lightslategrey", "#778899"},
                {"lightsteelblue", "#b0c4de"}, {"lightyellow", "#ffffe0"}, {"lime", "#00ff00"}, {"limegreen", "#32cd32"},
                {"linen", "#faf0e6"}, {"magenta", "#ff00ff"}, {"maroon", "#800000"}, {"mediumaquamarine", "#66cdaa"},
                {"mediumblue", "#0000cd"}, {"mediumorchid", "#ba55d3"}, {"mediumpurple", "#9370db"}, {"mediumseagreen", "#3cb371"},
                {"mediumslateblue", "#7b68ee"}, {"mediumspringgreen", "#00fa9a"}, {"mediumturquoise", "#48d1cc"}, {"mediumvioletred", "#c71585"},
                {"midnightblue", "#191970"}, {"mintcream", "#f5fffa"}, {"mistyrose", "#ffe4e1"}, {"moccasin", "#ffe4b5"},
                {"navajowhite", "#ffdead"}, {"navy", "#000080"}, {"oldlace", "#fdf5e6"}, {"olive", "#808000"},
                {"olivedrab", "#6b8e23"}, {"orange", "#ffa500"}, {"orangered", "#ff4500"}, {"orchid", "#da70d6"},
                {"palegoldenrod", "#eee8aa"}, {"palegreen", "#98fb98"}, {"paleturquoise", "#afeeee"}, {"palevioletred", "#db7093"},
                {"papayawhip", "#ffefd5"}, {"peachpuff", "#ffdab9"}, {"peru", "#cd853f"}, {"pink", "#ffc0cb"},
                {"plum", "#dda0dd"}, {"powderblue", "#b0e0e6"}, {"purple", "#800080"}, {"red", "#ff0000"},
                {"rosybrown", "#bc8f8f"}, {"royalblue", "#4169e1"}, {"saddlebrown", "#8b4513"}, {"salmon", "#fa8072"},
                {"sandybrown", "#f4a460"}, {"seagreen", "#2e8b57"}, {"seashell", "#fff5ee"}, {"sienna", "#a0522d"},
                {"silver", "#c0c0c0"}, {"skyblue", "#87ceeb"}, {"slateblue", "#6a5acd"}, {"slategray", "#708090"},
                {"slategrey", "#708090"}, {"snow", "#fffafa"}, {"springgreen", "#00ff7f"}, {"steelblue", "#4682b4"},
                {"tan", "#d2b48c"}, {"teal", "#008080"}, {"thistle", "#d8bfd8"}, {"tomato", "#ff6347"},
                {"turquoise", "#40e0d0"}, {"violet", "#ee82ee"}, {"wheat", "#f5deb3"}, {"white", "#ffffff"},
                {"whitesmoke", "#f5f5f5"}, {"yellow", "#ffff00"}, {"yellowgreen", "#9acd32"}
        };
    
        public ColorChooser() {
            this(webPalette);
        }
    
        public ColorChooser(String[][] colors) {
            super();
    
            // create a pane for showing info on the chosen color.
            final HBox colorInfo = new HBox();
            final Label selectedColorName = new Label();
            HBox.setMargin(selectedColorName, new Insets(2, 0, 2, 10));
            colorInfo.getChildren().addAll(selectedColorName);
            chosenColorName.addListener((observableValue, oldName, newName) -> {
                if (newName != null) {
                    colorInfo.setStyle("-fx-background-color: " + newName + ";");
                    selectedColorName.setText(newName);
                    chosenColor.set(Color.web(newName));
                }
            });
    
            // create a color swatch.
            final GridPane swatch = new GridPane();
            swatch.setSnapToPixel(false);
    
            // calculate the number of columns and rows based on the number of colors and a golden ratio for layout.
            nColumns = Math.floor(Math.sqrt(colors.length) * 2 / GOLDEN_RATIO);
            nRows = Math.ceil(colors.length / nColumns);
    
            // create a bunch of button controls for color selection.
            int i = 0;
            for (String[] namedColor : colors) {
                final String colorName = namedColor[0];
                final String colorHex = namedColor[1];
    
                // create a button for choosing a color.
                final Button colorChoice = new Button();
                colorChoice.setUserData(colorName);
    
                // position the button in the grid.
                GridPane.setRowIndex(colorChoice, i / (int) nColumns);
                GridPane.setColumnIndex(colorChoice, i % (int) nColumns);
                colorChoice.setMinSize(MIN_TILE_SIZE, MIN_TILE_SIZE);
                colorChoice.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
    
                // add a mouseover tooltip to display more info on the colour being examined.
                // todo it would be nice to be able to have the tooltip appear immediately on mouseover, but there is no easy way to do this, (file jira feature request?)
                final Tooltip tooltip = new Tooltip(colorName);
                tooltip.setStyle("-fx-font-size: 14");
                tooltip.setContentDisplay(ContentDisplay.BOTTOM);
                final Rectangle graphic = new Rectangle(30, 30, Color.web(colorHex));
                graphic.widthProperty().bind(prefTileSize.multiply(1.5));
                graphic.heightProperty().bind(prefTileSize.multiply(1.5));
                tooltip.setGraphic(graphic);
                colorChoice.setTooltip(tooltip);
    
                // color the button appropriately and change it's hover functionality (doing some of this in a css sheet would be better).
                final String backgroundStyle = "-fx-background-color: " + colorHex + "; -fx-background-insets: 0; -fx-background-radius: 0;";
                colorChoice.setStyle(backgroundStyle);
                colorChoice.setOnMouseEntered(mouseEvent -> {
                    final String borderStyle = "-fx-border-color: ladder(" + colorHex + ", whitesmoke 49%, darkslategrey 50%); -fx-border-width: 2;";
                    colorChoice.setStyle(backgroundStyle + borderStyle);
                });
                colorChoice.setOnMouseExited(mouseEvent -> {
                    final String borderStyle = "-fx-border-width: 0; -fx-border-insets: 2;";
                    colorChoice.setStyle(backgroundStyle + borderStyle);
                });
    
                // choose the color when the button is clicked.
                colorChoice.setOnAction(new EventHandler<ActionEvent>() {
                    @Override
                    public void handle(ActionEvent actionEvent) {
                        chosenColorName.set((String) colorChoice.getUserData());
                    }
                });
    
                // add the color choice to the swatch selection.
                swatch.getChildren().add(colorChoice);
    
                i++;
            }
    
            // select the first color in the chooser.
            ((Button) swatch.getChildren().get(0)).fire();
    
            // layout the color picker.
            getChildren().addAll(swatch, colorInfo);
            VBox.setVgrow(swatch, Priority.ALWAYS);
            setStyle("-fx-background-color: black; -fx-font-size: 16;");
            swatch.layoutBoundsProperty().addListener((observableValue, oldBounds, newBounds) -> {
                prefTileSize.set(Math.max(MIN_TILE_SIZE, Math.min(newBounds.getWidth() / nColumns, newBounds.getHeight() / nRows)));
                for (Node child : swatch.getChildrenUnmodifiable()) {
                    Control tile = (Control) child;
                    final double margin = prefTileSize.get() / 10;
                    tile.setPrefSize(prefTileSize.get() - 2 * margin, prefTileSize.get() - 2 * margin);
                    GridPane.setMargin(child, new Insets(margin));
                }
            });
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-08
      • 2021-06-06
      • 2016-05-25
      • 1970-01-01
      相关资源
      最近更新 更多