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

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 checkbox 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.

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 progtwigr (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

             

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 choicesView; @Override public void initialize(URL url, ResourceBundle rb) { choicesView.setCellFactory(new Callback, ListCell>() { public ListCell call(ListView 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

            

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 LABEL_CHANGE_LISTENER = new ChangeListener() { public void changed(ObservableValue property, String oldValue, String newValue) { updateLabelView(newValue); } }; private final ChangeListener IS_SELECTED_CHANGE_LISTENER = new ChangeListener() { public void changed(ObservableValue 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 { @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); } } } } 

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

             

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, 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

             

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

La respuesta rápida es la etiqueta ; sin embargo, deberá establecer ChoiceModel en la clase Controller.

   ****