Tenho uma classe que constrói uma grade com um array de TextFields usando GridPane. Preciso inserir essa grade em um ScrollPane que só aceita Node no método setContent(). Então estendo essa classe do GridPane. A classe Grid é instanciada e definida no ScrollPane pelo método onMnuItemNewAction da classe MainViewController.java, mas a grade não é mostrada. Obrigado pela ajuda.
MainView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane prefHeight="277.0" prefWidth="495.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="br.com.ablogic.crossword.MainViewController">
<top>
<VBox prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<MenuBar fx:id="mnuBar" prefHeight="25.0" prefWidth="360.0">
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem fx:id="mnuItemNew" mnemonicParsing="false" onAction="#onMnuItemNewAction" text="New grid" />
</items>
</Menu>
</menus>
</MenuBar>
</children>
</VBox>
</top>
<center>
<ScrollPane fx:id="scpGrid" fitToHeight="true" fitToWidth="true" pannable="true" style="-fx-background-color: #dbbb92; -fx-background: #dbbb92;" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
Principal.java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class Main extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("MainView.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 800, 600);
stage.setTitle("Grid Demo");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
MainViewController.java (o método de chamada)
import javafx.geometry.Pos;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class MainViewController implements Initializable {
@FXML
private MenuItem mnuItemNew;
@FXML
private ScrollPane scpGrid;
@FXML
public void onMnuItemNewAction() {
int cols = 10;
int rows = 10;
int horizontalGap = 1;
int verticalGap = 1;
int fieldHorizontalSize = 40;
int fieldVerticalSize = 40;
var newGrid = new Grid(cols, rows, horizontalGap, verticalGap, fieldHorizontalSize, fieldVerticalSize);
scpGrid.setContent(newGrid);
newGrid.setAlignment(Pos.CENTER);
}
@Override
public void initialize(URL url, ResourceBundle rb) {
}
}
Grade.java
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import java.net.URL;
import java.util.ResourceBundle;
public class Grid extends GridPane implements Initializable {
private final int totalColumnFields;
private final int totalRowFields;
private final int horizontalGap;
private final int verticalGap;
private final int fieldHorizontalSize;
private final int fieldVerticalSize;
public Grid(int totalColumnFields, int totalRowFields, int horizontalGap, int verticalGap, int fieldHorizontalSize, int fieldVerticalSize) {
this.totalColumnFields = totalColumnFields;
this.totalRowFields = totalRowFields;
this.horizontalGap = horizontalGap;
this.verticalGap = verticalGap;
this.fieldHorizontalSize = fieldHorizontalSize;
this.fieldVerticalSize = fieldVerticalSize;
}
@Override
public void initialize(URL url, ResourceBundle rb) {
this.setHgap(horizontalGap);
this.setVgap(verticalGap);
TextField[][] arrayLetterField = new TextField[totalColumnFields][totalRowFields];
for (int row = 0; row < totalRowFields; row++) {
for (int col = 0; col < totalColumnFields; col++) {
arrayLetterField[col][row] = new TextField();
arrayLetterField[col][row].setMinSize(fieldHorizontalSize, fieldVerticalSize);
arrayLetterField[col][row].setMaxSize(fieldHorizontalSize, fieldVerticalSize );
this.add(arrayLetterField[col][row], col, row);
}
}
}
}
A
Initializable
interface e seu método correspondentevoid initialize(URL, ResourceBundle)
são destinados ao uso por classes de controladores que atuam como controladores para documentos FXML. Quando oFXMLLoader
carrega um arquivo FXML que especifica uma classe em seufx:controller
atributo, oFXMLLoader
instancia essa classe e então invoca oinitialize(...)
método nela. 1Sua
Grid
classe não é uma classe controladora para nenhum documento FXML. Ela não é instanciada por umFXMLLoader
(você a instancia diretamente chamandovar newGrid = new Grid(...)
aMainViewController
classe) e, portanto, oinitialize(...)
método não é automaticamente invocado para você em nenhum momento.Consequentemente, o
initialize()
método emGrid
nunca é chamado, então os campos de texto nunca são criados e nunca são adicionados ao painel de grade. Então, a grade que você adiciona ao painel de rolagem fica vazia, e nada fica visível.Não há necessidade de a
Grid
classe implementarInitializable
, já que ela não está associada a um arquivo FXML. O código noinitialize()
método é o código que você quer que seja executado quando aGrid
for criado, então mova-o para o construtor:Isso dá o resultado desejado:
Outros comentários sobre seu código:
Note que não há necessidade de replicar os valores
horizontalGap
andverticalGap
na sua classe, pois eles já estão armazenados como as propriedadeshgap
andvgap
herdadas deGridPane
. Então você pode reduzir um pouco o tamanho da sua classe com:Se precisar referenciar esses valores a qualquer momento, você pode fazer isso com
getHgap()
egetVgap()
.Eu também recomendo não fazer subclasses
GridPane
aqui. Você deve reservar a subclasse de classes existentes quando estiver adicionando funcionalidade a elas. Aqui você está realmente configurando apenas uma instância da classe existente. A subclasseGridPane
também expõe os detalhes internos da estratégia de layout para o resto do seu aplicativo, potencialmente tornando muito mais difícil alterar o layout mais tarde (por exemplo, para umaTilePane
ou alguma outra estratégia) se você quisesse fazer isso. Eu recomendo "favorecer a agregação em vez da herança" aqui e apenas dar acesso a um agregadoGridPane
sem expor detalhes de qual layout você está usando:E então a pequena alteração correspondente no código do cliente:
(1) Note que a partir do JavaFX 2.1 a
Initializable
interface é essencialmente redundante. Da documentação :Isso significa que mesmo uma classe controladora para um documento FXML não precisa implementar
Initializable
. Se você precisar executar a inicialização após os@FXML
campos anotados com - terem sido injetados, basta definir uminitialize()
método no-arg para fazer isso. Você pode até mesmo fazer esse métodoprivate
se anotá-lo com@FXML
, reforçando melhor o encapsulamento. Se você precisar de acesso às propriedadeslocation
ouresources
, elas podem ser injetadas da mesma forma que os elementos do arquivo FXML. Por exemplo: