¿Cómo crear componentes personalizados en JavaFX 2.0 usando FXML?

Resuelto Andrey asked hace 13 años • 4 respuestas

Parece que no puedo encontrar ningún material sobre el tema. Para dar un ejemplo más concreto, digamos que quiero crear un componente simple que combine una casilla de verificación y una etiqueta. Luego, complete un ListView con instancias de este componente personalizado.

ACTUALIZACIÓN: vea mi respuesta para ver el código completo

ACTUALIZACIÓN 2: Para obtener un tutorial actualizado, consulte la documentación oficial . Se agregaron muchas cosas nuevas en 2.2. Finalmente, la Introducción a FXML cubre prácticamente todo lo que necesita saber sobre FXML.

ACTUALIZACIÓN 3: Hendrik Ebbers hizo una publicación de blog extremadamente útil sobre controles de interfaz de usuario personalizados.

Andrey avatar Dec 09 '11 00:12 Andrey
Aceptado

Actualización: para obtener un tutorial actualizado, consulte la documentación oficial . Se agregaron muchas cosas nuevas en 2.2. Además, la Introducción a FXML cubre prácticamente todo lo que necesita saber sobre FXML. Finalmente, Hendrik Ebbers hizo una publicación de blog extremadamente útil sobre controles de interfaz de usuario personalizados.


Después de unos días de explorar la API y leer algunos documentos ( Introducción a FXML , Introducción al enlace de propiedades de FXML , Futuro de FXML ), se me ocurrió una solución bastante sensata. La información menos sencilla que aprendí de este pequeño experimento fue que la instancia de un controlador (declarada con fx:controller en FXML) está en manos del FXMLLoader que cargó el archivo FXML... Lo peor de todo, este hecho importante solo se menciona en un lugar en todos los documentos que vi:

un controlador generalmente solo es visible para el cargador FXML que lo crea

Entonces, recuerde, para obtener mediante programación (desde código Java) una referencia a la instancia de un controlador que se declaró en FXML con el fx:controlleruso FXMLLoader.getController() (consulte la implementación de la clase ChoiceCell a continuación para obtener un ejemplo completo).

Otra cosa a tener en cuenta es que Property.bindBiderctional() establecerá el valor de la propiedad que llama al valor de la propiedad pasada como argumento. Dadas dos propiedades booleanas target(originalmente configuradas en false) y source(inicialmente configuradas en true), la llamada target.bindBidirectional(source)establecerá el valor de targeten true. Obviamente, cualquier cambio posterior en cualquiera de las propiedades cambiará el valor de la otra propiedad ( target.set(false)hará que el valor de sourcese establezca en false):

BooleanProperty target = new SimpleBooleanProperty();//value is false
BooleanProperty source = new SimpleBooleanProperty(true);//value is true
target.bindBidirectional(source);//target.get() will now return true
target.set(false);//both values are now false
source.set(true);//both values are now true

De todos modos, aquí está el código completo que demuestra cómo FXML y Java pueden trabajar juntos (además de algunas otras cosas útiles)

Estructura del paquete:

com.example.javafx.choice
  ChoiceCell.java
  ChoiceController.java
  ChoiceModel.java
  ChoiceView.fxml
com.example.javafx.mvc
  FxmlMvcPatternDemo.java
  MainController.java
  MainView.fxml
  MainView.properties

FxmlMvcPatternDemo.java

package com.example.javafx.mvc;

import java.util.ResourceBundle;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class FxmlMvcPatternDemo extends Application
{
    public static void main(String[] args) throws ClassNotFoundException
    {
        Application.launch(FxmlMvcPatternDemo.class, args);
    }

    @Override
    public void start(Stage stage) throws Exception
    {
        Parent root = FXMLLoader.load
        (
            FxmlMvcPatternDemo.class.getResource("MainView.fxml"),
            ResourceBundle.getBundle(FxmlMvcPatternDemo.class.getPackage().getName()+".MainView")/*properties file*/
        );

        stage.setScene(new Scene(root));
        stage.show();
    }
}

Vista principal.fxml

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

<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.example.javafx.mvc.MainController"

    prefWidth="300"
    prefHeight="400"
    fillWidth="false"
>
    <children>
        <Label text="%title" />
        <ListView fx:id="choicesView" />
        <Button text="Force Change" onAction="#handleForceChange" />
    </children>
</VBox>

Propiedades de MainView

title=JavaFX 2.0 FXML MVC demo

Controlador principal.java

package com.example.javafx.mvc;

import com.example.javafx.choice.ChoiceCell;
import com.example.javafx.choice.ChoiceModel;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;

public class MainController implements Initializable
{
    @FXML
    private ListView<ChoiceModel> choicesView;

    @Override
    public void initialize(URL url, ResourceBundle rb)
    {
        choicesView.setCellFactory(new Callback<ListView<ChoiceModel>, ListCell<ChoiceModel>>()
        {
            public ListCell<ChoiceModel> call(ListView<ChoiceModel> p)
            {
                return new ChoiceCell();
            }
        });
        choicesView.setItems(FXCollections.observableArrayList
        (
            new ChoiceModel("Tiger", true),
            new ChoiceModel("Shark", false),
            new ChoiceModel("Bear", false),
            new ChoiceModel("Wolf", true)
        ));
    }

    @FXML
    private void handleForceChange(ActionEvent event)
    {
        if(choicesView != null && choicesView.getItems().size() > 0)
        {
            boolean isSelected = choicesView.getItems().get(0).isSelected();
            choicesView.getItems().get(0).setSelected(!isSelected);
        }
    }
}

ChoiceView.fxml

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

<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<HBox
    xmlns:fx="http://javafx.com/fxml"

    fx:controller="com.example.javafx.choice.ChoiceController"
>
    <children>
        <CheckBox fx:id="isSelectedView" />
        <Label fx:id="labelView" />
    </children>
</HBox>

ChoiceController.java

package com.example.javafx.choice;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;

public class ChoiceController
{
    private final ChangeListener<String> LABEL_CHANGE_LISTENER = new ChangeListener<String>()
    {
        public void changed(ObservableValue<? extends String> property, String oldValue, String newValue)
        {
            updateLabelView(newValue);
        }
    };

    private final ChangeListener<Boolean> IS_SELECTED_CHANGE_LISTENER = new ChangeListener<Boolean>()
    {
        public void changed(ObservableValue<? extends Boolean> property, Boolean oldValue, Boolean newValue)
        {
            updateIsSelectedView(newValue);
        }
    };

    @FXML
    private Label labelView;

    @FXML
    private CheckBox isSelectedView;

    private ChoiceModel model;

    public ChoiceModel getModel()
    {
        return model;
    }

    public void setModel(ChoiceModel model)
    {
        if(this.model != null)
            removeModelListeners();
        this.model = model;
        setupModelListeners();
        updateView();
    }

    private void removeModelListeners()
    {
        model.labelProperty().removeListener(LABEL_CHANGE_LISTENER);
        model.isSelectedProperty().removeListener(IS_SELECTED_CHANGE_LISTENER);
        isSelectedView.selectedProperty().unbindBidirectional(model.isSelectedProperty())
    }

    private void setupModelListeners()
    {
        model.labelProperty().addListener(LABEL_CHANGE_LISTENER);
        model.isSelectedProperty().addListener(IS_SELECTED_CHANGE_LISTENER);
        isSelectedView.selectedProperty().bindBidirectional(model.isSelectedProperty());
    }

    private void updateView()
    {
        updateLabelView();
        updateIsSelectedView();
    }

    private void updateLabelView(){ updateLabelView(model.getLabel()); }
    private void updateLabelView(String newValue)
    {
        labelView.setText(newValue);
    }

    private void updateIsSelectedView(){ updateIsSelectedView(model.isSelected()); }
    private void updateIsSelectedView(boolean newValue)
    {
        isSelectedView.setSelected(newValue);
    }
}

ElecciónModelo.java

package com.example.javafx.choice;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class ChoiceModel
{
    private final StringProperty label;
    private final BooleanProperty isSelected;

    public ChoiceModel()
    {
        this(null, false);
    }

    public ChoiceModel(String label)
    {
        this(label, false);
    }

    public ChoiceModel(String label, boolean isSelected)
    {
        this.label = new SimpleStringProperty(label);
        this.isSelected = new SimpleBooleanProperty(isSelected);
    }

    public String getLabel(){ return label.get(); }
    public void setLabel(String label){ this.label.set(label); }
    public StringProperty labelProperty(){ return label; }

    public boolean isSelected(){ return isSelected.get(); }
    public void setSelected(boolean isSelected){ this.isSelected.set(isSelected); }
    public BooleanProperty isSelectedProperty(){ return isSelected; }
}

ChoiceCell.java

package com.example.javafx.choice;

import java.io.IOException;
import java.net.URL;
import javafx.fxml.FXMLLoader;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.Node;
import javafx.scene.control.ListCell;

public class ChoiceCell extends ListCell<ChoiceModel>
{
    @Override
    protected void updateItem(ChoiceModel model, boolean bln)
    {
        super.updateItem(model, bln);

        if(model != null)
        {
            URL location = ChoiceController.class.getResource("ChoiceView.fxml");

            FXMLLoader fxmlLoader = new FXMLLoader();
            fxmlLoader.setLocation(location);
            fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());

            try
            {
                Node root = (Node)fxmlLoader.load(location.openStream());
                ChoiceController controller = (ChoiceController)fxmlLoader.getController();
                controller.setModel(model);
                setGraphic(root);
            }
            catch(IOException ioe)
            {
                throw new IllegalStateException(ioe);
            }
        }
    }
}
Andrey avatar Dec 13 '2011 14:12 Andrey

Para JavaFx 2.1, puede crear un componente de control FXML personalizado de esta manera:

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

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import customcontrolexample.myCommponent.*?>

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="customcontrolexample.FXML1Controller">
    <children>
        <MyComponent welcome="1234"/>
    </children>
</VBox>

Código de componente:

MiComponente.java

package customcontrolexample.myCommponent;

import java.io.IOException;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.util.Callback;

public class MyComponent extends Pane {

    private Node view;
    private MyComponentController controller;

    public MyComponent() {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("myComponent.fxml"));
        fxmlLoader.setControllerFactory(new Callback<Class<?>, Object>() {
            @Override
            public Object call(Class<?> param) {
                return controller = new MyComponentController();
            }
        });
        try {
            view = (Node) fxmlLoader.load();

        } catch (IOException ex) {
        }
        getChildren().add(view);
    }

    public void setWelcome(String str) {
        controller.textField.setText(str);
    }

    public String getWelcome() {
        return controller.textField.getText();
    }

    public StringProperty welcomeProperty() {
        return controller.textField.textProperty();
    }
}

MiControladordeComponentes.java

package customcontrolexample.myCommponent;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;

public class MyComponentController implements Initializable {

    int i = 0;
    @FXML
    TextField textField;

    @FXML
    protected void doSomething() {
        textField.setText("The button was clicked #" + ++i);
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        textField.setText("Just click the button!");
    }
}

miComponente.fxml

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

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="customcontrolexample.myCommponent.MyComponentController">
  <children>
    <TextField fx:id="textField" prefWidth="200.0" />
    <Button mnemonicParsing="false" onAction="#doSomething" text="B" />
  </children>
</VBox>

Este código debe verificar si no hay pérdida de memoria.

Daniel De León avatar Oct 01 '2012 21:10 Daniel De León

La respuesta rápida es la etiqueta <fx:include>; sin embargo, deberá configurar ChoiceModel en la clase Controlador.

<VBox
  xmlns:fx="http://javafx.com/fxml"

  fx:controller="fxmltestinclude.ChoiceDemo"
>
  <children>
    **<fx:include source="Choice.fxml" />**
    <ListView fx:id="choices" />
  </children>
</VBox>
JimClarke avatar Dec 08 '2011 21:12 JimClarke