español - ¿Cómo crear componentes personalizados en JavaFX 2.0 usando FXML?
javafx tutorial español (3)
La respuesta rápida es la etiqueta <fx: include>; sin embargo, deberá establecer ChoiceModel en la clase Controller.
<VBox
xmlns:fx="http://javafx.com/fxml"
fx:controller="fxmltestinclude.ChoiceDemo"
>
<children>
**<fx:include source="Choice.fxml" />**
<ListView fx:id="choices" />
</children>
</VBox>
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. A continuación, complete un ListView con instancias de este componente personalizado.
ACTUALIZACIÓN: ver mi respuesta para el código completo
ACTUALIZACIÓN 2: para obtener un tutorial actualizado, consulte la documentación oficial . Hubo muchas cosas nuevas que se agregaron 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 los controles personalizados de interfaz de usuario.
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:
MyComponent.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();
}
}
MyComponentController.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!");
}
}
myComponent.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.
Actualización: para obtener un tutorial actualizado, consulte la documentación oficial . Hubo muchas cosas nuevas que se agregaron 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 los controles de interfaz de usuario personalizados.
Después de unos días de mirar alrededor de la API y leer algunos documentos ( Introducción a FXML , Primeros pasos con el enlace de propiedades FXML , Futuro de FXML ), he encontrado una solución bastante sensata. La información menos directa que aprendí de este pequeño experimento fue que la instancia de un controlador (declarado con fx: controller en FXML) está en manos de FXMLLoader que cargó el archivo FXML ... Lo peor de todo es que 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
Por lo tanto, recuerde, para programar (desde código Java) obtener una referencia a la instancia de un controlador que se declaró en FXML con fx:controller
use FXMLLoader.getController() (consulte la implementación de la clase ChoiceCell a continuación para obtener un completo ejemplo).
Otra cosa a tener en cuenta es que Property.bindBiderctional() establecerá el valor de la propiedad de llamada en el valor de la propiedad pasada como argumento. Dadas dos propiedades booleanas target
(originalmente configurado como false
) y source
(inicialmente configurado como true
), la llamada target.bindBidirectional(source)
establecerá el valor de target
en true
. Obviamente, cualquier cambio posterior a cualquiera de las propiedades cambiará el valor de la otra propiedad ( target.set(false)
hará que el valor de la source
se 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 funcionar juntos (así como 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();
}
}
MainView.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>
MainView.properties
title=JavaFX 2.0 FXML MVC demo
MainController.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);
}
}
ChoiceModel.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);
}
}
}
}