JavaFX 8游戏控制台全屏缩放实现问题求助
解决JavaFX控制台全屏缩放及重复子节点错误问题
问题背景
此前已实现启动屏的全屏缩放,但游戏控制台的缩放仍无法正常工作。运行代码后出现重复子节点添加错误,移除Console类中的BorderPane后窗口仅显示空白,需要实现控制台的全屏缩放并正常显示。
错误信息
Caused by: java.lang.RuntimeException: Exception in Application start method at javafx.graphics@18.0.1/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:901) at javafx.graphics@18.0.1/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: java.lang.IllegalArgumentException: Children: duplicate children added: parent = BorderPane@56dca201
现有代码
Console类
package application.console; import java.util.List; import application.areas.startingArea.SA; import application.areas.vanguardForest.VFCmds; import application.areas.vanguardForest.VFNavi; import application.areas.vanguardForest.VFPkups; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.scene.control.ScrollPane; import javafx.scene.control.TextField; import javafx.scene.control.ScrollPane.ScrollBarPolicy; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.Text; public class Console extends Region { public static double WIDTH = 990; public static double HEIGHT = 525; private final Font cinzel; private final BorderPane root; private final VBox console; private final ScrollPane scroll; private final HBox inputBar; private final TextField input; private final Text carrot; public Console() { this.root = new BorderPane(); root.setStyle("-fx-background-color: #232323;"); this.cinzel = Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 16); this.console = new VBox(); console.setPrefWidth(WIDTH); console.setPrefHeight(HEIGHT); this.scroll = new ScrollPane(); scroll.setContent(console); scroll.setStyle("-fx-background: #232323;" + "-fx-background-color: transparent;" + "-fx-border-color: #232323;" + "-fx-focus-color: #232323;"); scroll.setHbarPolicy(ScrollBarPolicy.NEVER); scroll.setVbarPolicy(ScrollBarPolicy.NEVER); scroll.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); console.setStyle("-fx-background-color: #232323;" + "-fx-focus-color: #232323;"); console.heightProperty().addListener(new ChangeListener<Object>() { @Override public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) { scroll.setVvalue((Double)newValue); } }); this.inputBar = new HBox(); inputBar.setPrefSize(WIDTH, 16); this.input = new TextField(); input.setStyle("-fx-background-color: transparent;" + "-fx-text-fill: #FFFFFF;" + "-fx-highlight-fill: #FFFFFF;" + "-fx-highlight-text-fill: #232323;" ); input.setFont(cinzel); input.setPrefWidth(WIDTH); this.carrot = new Text(" > "); carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 24)); carrot.setFill(Color.WHITE); inputBar.getChildren().addAll(carrot, input); root.setMinSize(WIDTH, (HEIGHT - input.getHeight())); input.setOnAction(e -> { String s = (input.getText()).stripTrailing(); if ((SA.isBuried || SA.inVF) && SS.gameStart) { Cmds.commands(s); //has general functions VFCmds.commands(s); //doesn't have function until specific command VFPkups.pickUp(s); //commands that allows pickups to be made within the game VFNavi.commands(s); //commands navigation for selected area } else { Cmds.commands(s); } input.clear(); }); root.getChildren().addAll(console, scroll, inputBar); root.setBottom(inputBar); } @Override protected void layoutChildren() { double xScale = getWidth() / root.getWidth(); double yScale = getHeight() / root.getHeight(); double scale = Math.min(xScale, yScale); for (BorderPane bp : List.of(root)) { scaleAndCenter(bp, scale); } for (VBox vb : List.of(console)) { scaleAndCenter(vb, scale); } for (HBox hb : List.of(inputBar)) { scaleAndCenter(hb, scale); } for (ScrollPane sp : List.of(scroll)) { scaleAndCenter(sp, scale); } for (TextField tf : List.of(input)) { scaleAndCenter(tf, scale); } for (Text text : List.of(carrot)) { scaleAndCenter(text, scale); } } private void scaleAndCenter(BorderPane root, double scale) { double w = scale * root.getWidth(); double h = scale * root.getHeight(); root.setPrefSize(w, h); root.relocate((getWidth() - w) / 2, (getHeight() - h) / 2); } private void scaleAndCenter(VBox vb, double scale) { double w = scale * vb.getWidth(); double h = scale * vb.getHeight(); vb.setPrefSize(w, h); vb.relocate((getWidth() - w) / 2, (getHeight() - h) / 2); } private void scaleAndCenter(HBox hb, double scale) { double w = scale * hb.getWidth(); double h = scale * hb.getHeight(); hb.setPrefSize(w, h); hb.relocate((getWidth() - w) / 2, (getHeight() - h) / 2); } private void scaleAndCenter(TextField input, double scale) { double w = scale * input.getWidth(); double h = scale * input.getHeight(); input.setPrefSize(w, 16); input.relocate((getWidth() - w) / 2, (getHeight() - h) / 2); } private void scaleAndCenter(ScrollPane scroll, double scale) { double w = scale * scroll.getWidth(); double h = scale * scroll.getHeight(); scroll.setPrefSize(w, h); scroll.relocate((getWidth() - w) / 2, (getHeight() - h) / 2); } private void scaleAndCenter(Text text, double scale) { double w = scale * text.getLayoutBounds().getWidth(); double h = scale * text.getLayoutBounds().getHeight(); double size = scale * text.getFont().getSize(); text.setFont(Font.font( text.getFont().getFamily(), size)); text.relocate((getWidth() - w) / 2, (getHeight() - h) / 2); } }
game类
package application.console; import javafx.scene.Parent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.BorderPane; public class game { public static Parent runGame() { Console c = new Console(); AnchorPane anchor = new AnchorPane(c); AnchorPane.setTopAnchor(c, 0.0); AnchorPane.setRightAnchor(c, 0.0); AnchorPane.setBottomAnchor(c, 0.0); AnchorPane.setLeftAnchor(c, 0.0); BorderPane root = new BorderPane(); root.setCenter(anchor); return root; } }
解决方案
1. 修复重复子节点错误
BorderPane不能通过getChildren().addAll()直接添加子节点,且你已经通过root.setBottom(inputBar)添加了inputBar,再调用addAll会导致重复添加。同时console已经被设置为scroll的内容,不需要再添加到root中。修改Console构造方法中的布局代码:
// 移除错误代码 // root.getChildren().addAll(console, scroll, inputBar); // 改为正确的BorderPane布局方式 root.setCenter(scroll); root.setBottom(inputBar);
2. 简化缩放逻辑
不需要逐个缩放每个子控件,只需要缩放根BorderPane即可,子控件会跟随根节点自动适配。同时要确保根BorderPane被添加到Console的子节点中,否则不会显示内容。修改layoutChildren方法和缩放方法:
@Override protected void layoutChildren() { double xScale = getWidth() / WIDTH; double yScale = getHeight() / HEIGHT; double scale = Math.min(xScale, yScale); // 缩放并居中根BorderPane scaleAndCenter(root, scale); // 确保根节点被添加到Console的子节点中 if (!getChildren().contains(root)) { getChildren().add(root); } // 单独调整Text的字体大小以适配缩放 double newFontSize = scale * 24; carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", newFontSize)); } private void scaleAndCenter(Region node, double scale) { double w = scale * node.getPrefWidth(); double h = scale * node.getPrefHeight(); node.setPrefSize(w, h); node.relocate((getWidth() - w) / 2, (getHeight() - h) / 2); }
修改后的完整Console类
package application.console; import application.areas.startingArea.SA; import application.areas.vanguardForest.VFCmds; import application.areas.vanguardForest.VFNavi; import application.areas.vanguardForest.VFPkups; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.scene.control.ScrollPane; import javafx.scene.control.TextField; import javafx.scene.control.ScrollPane.ScrollBarPolicy; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.Text; public class Console extends Region { public static double WIDTH = 990; public static double HEIGHT = 525; private final Font cinzel; private final BorderPane root; private final VBox console; private final ScrollPane scroll; private final HBox inputBar; private final TextField input; private final Text carrot; public Console() { this.root = new BorderPane(); root.setStyle("-fx-background-color: #232323;"); this.cinzel = Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 16); this.console = new VBox(); console.setPrefWidth(WIDTH); console.setPrefHeight(HEIGHT); this.scroll = new ScrollPane(); scroll.setContent(console); scroll.setStyle("-fx-background: #232323;" + "-fx-background-color: transparent;" + "-fx-border-color: #232323;" + "-fx-focus-color: #232323;"); scroll.setHbarPolicy(ScrollBarPolicy.NEVER); scroll.setVbarPolicy(ScrollBarPolicy.NEVER); scroll.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null))); console.setStyle("-fx-background-color: #232323;" + "-fx-focus-color: #232323;"); console.heightProperty().addListener(new ChangeListener<Object>() { @Override public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) { scroll.setVvalue((Double)newValue); } }); this.inputBar = new HBox(); inputBar.setPrefSize(WIDTH, 16); this.input = new TextField(); input.setStyle("-fx-background-color: transparent;" + "-fx-text-fill: #FFFFFF;" + "-fx-highlight-fill: #FFFFFF;" + "-fx-highlight-text-fill: #232323;" ); input.setFont(cinzel); input.setPrefWidth(WIDTH); this.carrot = new Text(" > "); carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 24)); carrot.setFill(Color.WHITE); inputBar.getChildren().addAll(carrot, input); root.setMinSize(WIDTH, (HEIGHT - input.getHeight())); input.setOnAction(e -> { String s = (input.getText()).stripTrailing(); if ((SA.isBuried || SA.inVF) && SS.gameStart) { Cmds.commands(s); //has general functions VFCmds.commands(s); //doesn't have function until specific command VFPkups.pickUp(s); //commands that allows pickups to be made within the game VFNavi.commands(s); //commands navigation for selected area } else { Cmds.commands(s); } input.clear(); }); // 正确设置BorderPane的子节点 root.setCenter(scroll); root.setBottom(inputBar); } @Override protected void layoutChildren() { double xScale = getWidth() / WIDTH; double yScale = getHeight() / HEIGHT; double scale = Math.min(xScale, yScale); // 缩放并居中根BorderPane scaleAndCenter(root, scale); // 确保根节点被添加到Console的子节点中 if (!getChildren().contains(root)) { getChildren().add(root); } // 单独调整Text的字体大小以适配缩放 double newFontSize = scale * 24; carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", newFontSize)); } private void scaleAndCenter(Region node, double scale) { double w = scale * node.getPrefWidth(); double h = scale * node.getPrefHeight(); node.setPrefSize(w, h); node.relocate((getWidth() - w) / 2, (getHeight() - h) / 2); } }
说明
- 修复了重复子节点错误:通过BorderPane的
setCenter和setBottom方法正确添加子节点,避免直接调用getChildren().addAll()。 - 实现全屏缩放:通过计算缩放比例,只缩放根BorderPane,子控件会自动跟随适配,同时单独调整Text的字体大小保证显示正常。
- 确保内容显示:将根BorderPane添加到Console的子节点中,解决移除BorderPane后空白的问题。
内容的提问来源于stack exchange,提问作者Providence
相关产品推荐
相关产品推荐

