JavaFX组件高度与场景一致,底部按钮被遮挡问题排查
问题详情
基于Maven + Java17 + JavaFX17.0.1 + JFoenix9.0.10开发的程序,创建了宽1035、高680的窗口。窗口左侧放置了一个高度为680的自定义ButtonsBox组件,但添加在组件底部的label3按钮被窗口边缘遮挡。最大化窗口时按钮可正常显示,手动修改Stage尺寸无法解决问题,仅调整ButtonsBox的高度才能让按钮显示。
相关代码
Draw类
package io.github.ctimet.lieinbedapp.gui; import io.github.ctimet.lieinbedapp.gui.ui.ButtonsBox; import io.github.ctimet.lieinbedapp.gui.util.CSSStyle; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; import javafx.scene.control.TextArea; import javafx.scene.image.Image; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URI; import java.util.Objects; import java.util.Optional; public class Draw { private static final Logger logger = LoggerFactory.getLogger(Draw.class); private static final AnchorPane pane = new AnchorPane(); public static void draw(Stage stage) { ButtonsBox box = new ButtonsBox(0, 0, 162, 680) .setSidesSpace(0) .setTopSpace(5) .setDownSpace(5) .setChildrenHeight(48) .setEachSpace(5) .setTextFill(Color.valueOf("#f5f5f5")) .setButtonStyle("/node.css", "button") .addTopButton("label", null, event -> { System.out.println("Label"); }) .addTopButton("label2", null, event -> { System.out.println("Label2"); }) .addDownButton("label3", null, event -> { System.out.println("1"); }); CSSStyle.addDefaultStyle(box, "box"); pane.getChildren().add(box); pane.setStyle("-fx-background-color: #f5f5f5;"); Scene scene = new Scene(pane, 1035, 680); stage.setScene(scene); stage.setTitle("Lie In Bed"); Image icon = new Image(Objects.requireNonNull(Draw.class.getResourceAsStream("/icon.png"))); stage.getIcons().add(icon); stage.show(); } }
ButtonsBox类
package io.github.ctimet.lieinbedapp.gui.ui; import com.jfoenix.controls.JFXButton; import io.github.ctimet.lieinbedapp.gui.util.CSSStyle; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; public class ButtonsBox extends Pane { private double childrenHeight; private double sidesSpace; private double topSpace; private double downSpace; private double eachSpace; private String cssFile; private String cssClass; private Paint textFill = Color.WHITE; private int topButtonsQuantity = 0; private int downButtonsQuantity = 0; public ButtonsBox(double layoutX, double layoutY, double prefWidth, double prefHeight) { setLayoutX(layoutX); setLayoutY(layoutY); setPrefWidth(prefWidth); setPrefHeight(prefHeight); } public ButtonsBox setTopSpace(double topSpace) { this.topSpace = topSpace; return this; } public ButtonsBox setDownSpace(double downSpace) { this.downSpace = downSpace; return this; } public ButtonsBox setChildrenHeight(double childrenHeight) { this.childrenHeight = childrenHeight; return this; } public ButtonsBox setSidesSpace(double sidesSpace) { this.sidesSpace = sidesSpace; return this; } public ButtonsBox setEachSpace(double eachSpace) { this.eachSpace = eachSpace; return this; } public ButtonsBox setTextFill(Paint paint) { this.textFill = paint; return this; } public ButtonsBox setButtonStyle(String cssFile, String cssClass) { this.cssFile = cssFile; this.cssClass = cssClass; return this; } public ButtonsBox addTopButton(String label, Node graphic, EventHandler<ActionEvent> handler) { JFXButton child = new JFXButton(label); child.setLayoutX(getLayoutX() + sidesSpace); child.setLayoutY(topButtonsQuantity * (childrenHeight + eachSpace) + topSpace); child.setPrefWidth(getPrefWidth() - 2*sidesSpace); child.setPrefHeight(childrenHeight); child.setTextFill(textFill); child.setCursor(Cursor.HAND); if (graphic != null) child.setGraphic(graphic); if (cssClass != null && cssFile != null) CSSStyle.addStyle(child, cssClass, cssFile); child.setOnAction(handler); getChildren().add(child); topButtonsQuantity += 1; return this; } public ButtonsBox addDownButton(String label, Node graphic, EventHandler<ActionEvent> handler) { JFXButton child = new JFXButton(label); child.setLayoutX(getLayoutX() + sidesSpace); System.out.println(getPrefHeight() - downButtonsQuantity * (childrenHeight + eachSpace) - downSpace); child.setLayoutY(getPrefHeight() - downButtonsQuantity * (childrenHeight + eachSpace) - downSpace); child.setPrefWidth(getPrefWidth() - 2*sidesSpace); child.setPrefHeight(childrenHeight); child.setTextFill(textFill); child.setCursor(Cursor.HAND); if (graphic != null) child.setGraphic(graphic); if (cssFile != null && cssClass != null) CSSStyle.addStyle(child, cssClass, cssFile); child.setOnAction(handler); getChildren().add(child); downButtonsQuantity += 1; return this; } }
原因分析
问题出在ButtonsBox的addDownButton方法中,按钮Y轴位置的计算逻辑遗漏了按钮自身的高度。当前代码计算的是按钮顶部的位置,但仅减去了底部间距和已添加按钮的占位高度,没有考虑按钮自身的高度,导致按钮底部超出ButtonsBox的边界:
// 原错误计算:仅计算了按钮顶部位置,但未减去按钮高度 child.setLayoutY(getPrefHeight() - downButtonsQuantity * (childrenHeight + eachSpace) - downSpace);
以当前参数为例:getPrefHeight()=680,downSpace=5,childrenHeight=48。添加第一个底部按钮时,downButtonsQuantity=0,计算出的layoutY=680-0-5=675,按钮底部位置为675+48=723,远超ButtonsBox的高度680。由于ButtonsBox高度与窗口Scene高度一致,超出部分被窗口边缘遮挡。最大化窗口时Scene高度增加,超出部分得以显示。
解决方案
修改addDownButton中的layoutY计算逻辑,在原有基础上减去按钮自身的高度childrenHeight,确保按钮底部不超出ButtonsBox的边界:
// 修正后的计算:减去按钮自身高度,让按钮顶部位置对齐到正确位置 child.setLayoutY(getPrefHeight() - downButtonsQuantity * (childrenHeight + eachSpace) - downSpace - childrenHeight);
或者用更直观的逻辑,以按钮底部对齐ButtonsBox的底部间距位置:
// 按钮底部位置 = 组件高度 - 底部间距 // 按钮顶部位置 = 按钮底部位置 - 按钮高度 - 已添加按钮的总占位(按钮高度+间距) child.setLayoutY(getPrefHeight() - downSpace - childrenHeight - downButtonsQuantity * (childrenHeight + eachSpace));
修改后,第一个底部按钮的layoutY=680-5-48-0=627,按钮底部位置为627+48=675,刚好在ButtonsBox的底部间距(5)上方,不会超出组件边界,也就不会被窗口遮挡。
内容的提问来源于stack exchange,提问作者CTimet bukii

