Ajuda, como entender e usar o : <fx:root> , no JavaFX? com Scene Builder

Olá!
Andei lendo um artigo:
https://stackoverflow.com/questions/23600926/how-to-understand-and-use-fxroot-in-javafx

porém não me ficou muito claro, alguém sabe sobre essa funcionalidade do JavaFX?
Desde já muito Obrigado!

Olá, essa tag em resumo serve para informar ao Java qual componente será o root, ou seja, o container em que o layout FXML será renderizado.

Sem essa tag o Java cria uma instância do container definido e injeta o layout nela, ex: se sua tela esiver dentro de um AnchorPane quando for carregar esse layout será retornada uma instância de AnchorPane no método FXMLLoader.getRoot().

Caso informada essa tag ex: <fx:root type="javafx.scene.layout.AnchorPane"> você poderá criar sua própria instância de um AnchorPane e passar para o loader no método FXMLLoader.setRoot(), dessa forma o layout será “injetado” dentro da sua intrância.

Pelo que entendi da resposta isso só é possível através dessa tag.

Obs.: Nunca usei essa tag, por desconhecer, mas já tive casos em que ela seria bem útil.

1 curtida

Muito Obrigado!
:smiley:

O Andrauss, disse tudo.

Como estou aprendendo JavaFX, tive curiosidade de ver o funcionamento desta tag.
Eu estava procurando algo assim, pois permite utilizar os construtores com parâmetros, além de facilitar a criação de componentes personalizados, pois o css é assumido fora da classe e pode ser manipulado dentro desta, FACILITANDO efeitos na aplicação, fora outras possibilidades com reaproveitamente de código.

Pra mim, as vantagens são significativa pois só de poder ** utilizar construtores com parêmetros**, posso remover parte da camada de abstração que realizava a comunicação entre os controllers.

A melhor comparação que tenho em mente é você acaba programando de forma muito próxima ao swing.

Pequeno teste que realizei:

package fxroot;

import fxroot.view.MainController;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class FxRoot extends Application {
    
    private MainController mainController;
    
    @Override
    public void start(Stage primaryStage) {
        mainController = new MainController("Testanto fxRoot", 1);
        Scene scene = new Scene(mainController, 300, 250);        
        primaryStage.setTitle(mainController.getTitutlo()+" "+mainController.getValor());
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
    
}

.

package fxroot.view;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;

/**
 * FXML Controller class
 */
public class MainController extends AnchorPane {

    private final String titulo;
    private int valor;
    @FXML
    private TextField txtInteragir;
    @FXML
    private Label lblInteragir;
    @FXML
    private Button btnInteragir;
    @FXML
    private FXMLLoader loader;

    public MainController(String informacao, int valor) {
        this.titulo = informacao;
        this.valor = valor;
        carregarFXML();
        interagir();
    }

    private void carregarFXML() {
        try {
            loader = new FXMLLoader(getClass().getResource("main.fxml"));
            loader.setController(this);
            loader.setRoot(this);
            loader.load();
        } catch (IOException ex) {
            Logger.getLogger(MainController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void interagir() {
        btnInteragir.setOnAction(seClicar -> {
            lblInteragir.setText("Interação[" + valor++ + "]" + txtInteragir.getText());
        }
        );
    }

    public int getValor() {
        return valor;
    }

    public String getTitulo() {
        return titulo;
    }

}

.//arquivo main.css não tem “nada”

.mainFxmlClass {

}

.

//arquivo main.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.net.URL?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>

<fx:root id="AnchorPane" prefHeight="400.0" prefWidth="600.0" styleClass="mainFxmlClass" type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.111">
    <stylesheets>
        <URL value="@main.css" />
    </stylesheets>
   <children>
      <Button fx:id="btnInteragir" layoutX="228.0" layoutY="67.0" mnemonicParsing="false" text="Interagir" />
      <Label fx:id="lblInteragir" layoutX="47.0" layoutY="133.0" text="Label" />
      <TextField fx:id="txtInteragir" layoutX="47.0" layoutY="67.0" />
   </children>
</fx:root>
2 curtidas

Interessante, nunca tinha usado essa tag. Sua abordagem é muito boa para criação de janelas e views.

Alterei um pequeno projeto é e realmente como estava imaginando.
Consegui reaproveitar muito do padrão anterior e eliminei a maior parte da abstração que fazia a comunicação entre os controllers, tudo isso porque os construtores agora recebem parâmetros.

Pra mim, foi um ganho considerável, pricipalmente em velocidade de desenvolvimento e manutenção.

O padrão que que passo a usar é bem adaptável, sendo uma variação do anterior.
Eis o padrão:

package ambiente;

import javafx.application.Application;
import javafx.stage.Stage;
import view.Cenario;
import view.main.MainController;

public class Ambiente extends Application {

    private Stage palco;

    @Override
    public void start(Stage primaryStage) {
        MainController mainController = new MainController(this);
        palco = primaryStage;
        atualizarScene(mainController.getCenario());
    }


    public void atualizarScene(Cenario cenario) {
        atualizarFechamento(cenario);
        palco.setScene(cenario.getCena());
        palco.setTitle(cenario.getTitulo());
        palco.setResizable(cenario.isResisable());
        palco.show();
    }

    private void atualizarFechamento(Cenario cenario) {
        palco.setOnCloseRequest((event) -> {
            if (!cenario.sair()) {
                event.consume();
                return;
            }
            //Serializar.gravar();
        });
    }

    public Stage getPalco() {
        return palco;
    }
    
    public static void main(String[] args) {
        launch(args);  
    }
    
}

.

package view;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;

public class Cenario extends AnchorPane {

    private String titulo;
    private final String urlFXML;
    private Scene cena;
    private boolean sair, resisable;

    public Cenario(String titulo, String urlFXML) {
        this.titulo = titulo;
        this.urlFXML = urlFXML.endsWith(".fxml")? urlFXML: urlFXML+".fxml";
    }

    public Scene getCena() {
        return cena;
    }
    /**este método é responsável encerrar a aplicação
     * @return true ou false
     * @see ambiente.Ambiente#atualizarFechamento
     */
    public boolean sair() {
        return sair;
    }

    public void setSair(boolean sair) {
        this.sair = sair;
    }

    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }

    protected final void carregarFXML(Controller controller) {
        try {
            FXMLLoader loader = new FXMLLoader(controller.getClass().getResource(urlFXML));
            loader.setController(controller);
            loader.setRoot(this);
            this.cena = new Scene(loader.load());
        } catch (IOException ex) {
            Logger.getLogger(Cenario.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public boolean isResisable() {
        return resisable;
    }

    public void setResisable(boolean resisable) {
        this.resisable = resisable;
    }

}

.

package view;

import ambiente.Ambiente;

public class Controller{
    private final Cenario cenario;
    protected final Ambiente ambienteExecucao;

    public Controller(Cenario cenario, Ambiente ambienteExecucao) {
        this.cenario = cenario;
        this.cenario.carregarFXML(Controller.this);
        this.ambienteExecucao = ambienteExecucao;
    }

    public Cenario getCenario() {
        return cenario;
    }

    public Ambiente getAmbienteExecucao() {
        return ambienteExecucao;
    }
}

.

package view;

import java.util.Arrays;
import javafx.scene.control.TextField;

public interface Formulario {

    boolean validar();

    void submeter();

    void clearForm();

    default void limparCampos(TextField... campos) {
        Arrays.stream(campos).forEach(campo -> campo.setText(""));
    }

}

.

package view.main;

import ambiente.Ambiente;
import java.net.URL;
import java.util.Arrays;
import java.util.ResourceBundle;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import view.Cenario;
import view.Controller;
import view.Formulario;
import view.other.OtherController;

public class MainController extends Controller implements Initializable, Formulario {
    
    @FXML
    private Button btnInformarPastaRaiz, btnInformarPastaDestinoScript, btnUnificarScript;
    @FXML
    private ComboBox<String> comboLinguagem;

    public MainController(Ambiente ambienteExecucao) {
        super(new Cenario("Ekemera Stage", "main.fxml"), ambienteExecucao);
    }
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        desabilitarBotoes();
        btnInformarPastaRaiz.setOnAction(onAction -> abrirPastaScripts());
        btnInformarPastaDestinoScript.setOnAction(onAction -> abrirPastaScripts());
        btnUnificarScript.setOnAction(onAction -> unificarScript());
        configurarComboLinguagem();

    }

    private void desabilitarBotoes() {
        desabilitarBotoes(true, btnInformarPastaDestinoScript, btnInformarPastaRaiz, btnUnificarScript);
    }

    private static void desabilitarBotoes(boolean desabilitar, Button... botoes) {
        Arrays.asList(botoes).forEach(botao -> botao.disableProperty().set(desabilitar));
    }

    private void configurarComboLinguagem() {
        comboLinguagem.getItems().addAll("Selecione uma linguagem","Java","JavaScript");
        comboLinguagem.getSelectionModel().select(0);
        comboLinguagem.valueProperty().addListener(
                (ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
                    desabilitarBotoes(comboLinguagem.getSelectionModel().getSelectedIndex() == 0, btnInformarPastaRaiz);
                    desabilitarBotoes(true, btnInformarPastaDestinoScript, btnUnificarScript);
                }
        );
    }

    private void unificarScript() {

    }
    
    //passando o ambiente de execução por meio dos construtores
    private void abrirPastaScripts() {
        desabilitarBotoes(!linguagemSelecionada().contains("cript"), btnInformarPastaDestinoScript, btnUnificarScript);
        OtherController outraJanela = new OtherController(ambienteExecucao);
        ambienteExecucao.atualizarScene(outraJanela.getCenario());
    }

    private String linguagemSelecionada() {
        return comboLinguagem.getSelectionModel().getSelectedItem();
    }
    
    @Override
    public boolean validar() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public void submeter() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public void clearForm() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
    
}

.//main.css

.paneRed{
    -fx-background-color: rgb(250,20,20);
}

.//main.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.net.URL?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>

<fx:root type="javafx.scene.layout.AnchorPane" prefHeight="400.0" prefWidth="600.0" styleClass="mainFxmlClass" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
    <stylesheets>
        <URL value="@main.css" />
        <URL value="@padrao.css" />
    </stylesheets>
   <children>
      <Pane fx:id="panePrincipal" prefHeight="400.0" prefWidth="600.0" styleClass="paneRed">
         <children>
            <Button fx:id="btnInformarPastaRaiz" layoutX="56.0" layoutY="92.0" maxHeight="50.0" maxWidth="238.0" mnemonicParsing="false" prefHeight="50.0" prefWidth="238.0" styleClass="buttonTopImage" text="Informar pasta raiz dos códigos Fonte" textAlignment="CENTER" textOverrun="CLIP" />
            <Button fx:id="btnInformarPastaDestinoScript" layoutX="163.0" layoutY="174.0" maxHeight="50.0" maxWidth="238.0" mnemonicParsing="false" prefHeight="50.0" prefWidth="238.0" styleClass="buttonTopImage" text="Informar pasta destino" textAlignment="CENTER" textOverrun="CLIP" />
            <Button fx:id="btnUnificarScript" layoutX="271.0" layoutY="259.0" maxHeight="50.0" maxWidth="238.0" mnemonicParsing="false" prefHeight="50.0" prefWidth="238.0" styleClass="buttonTopImage" text="..." textAlignment="CENTER" textOverrun="CLIP" />
            <ComboBox fx:id="comboLinguagem" layoutX="56.0" layoutY="47.0" prefHeight="25.0" prefWidth="238.0" />
         </children>
      </Pane>
   </children>
</fx:root>

.//padrao.css não tem nada

package view.other;

import ambiente.Ambiente;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.Initializable;
import view.Cenario;
import view.Controller;

public class OtherController extends Controller implements Initializable {

    public OtherController(Ambiente ambienteExecucao) {
        super(new Cenario("Outro cenário", "other.fxml"), ambienteExecucao);
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
    }    
    
}

.//other.css não tem nada

<?xml version="1.0" encoding="UTF-8"?>

<?import java.net.URL?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<fx:root prefHeight="400.0" prefWidth="600.0" styleClass="mainFxmlClass" type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.111">
    <stylesheets>
        <URL value="@other.css" />
    </stylesheets>
   <children>
      <Label layoutX="105.0" layoutY="123.0" text="Easy" />
   </children>
</fx:root>
1 curtida

Interessante, fica tudo numa só tela, dessa forma fica fácil aplicar transições e animações entre as telas e passar parâmetros. Vou aproveitar pra atualizar a minha lib, pois isso vai resolver alguns problemas que encontrei no desenvolvimento dela.

2 curtidas

Meu amigo, muita coisa se esclareceu para mim agora. Obrigado!
Você diria que fazendo dessa forma é melhor do que usando a tag <fx:include> ?

Eu nunca usei a essa tag fx:include.
Faz menos de 2 meses que comecei a estudar JavaFX, pois havia decidido que faria a última prova com esta tecnologia.

O que posso dizer é que talvez, pois não exergo concorrência destrutiva e entre as tags.

Use as duas.

O que já era divertido, ficou melhor.

1 curtida

Perfeito, novamente obrigado!