Comment créer des composants personnalisés dans JavaFX 2.0 en utilisant FXML?

je n'arrive pas à trouver des documents sur le sujet. Pour donner un exemple plus concret, disons que je veux créer un composant simple qui combine une case à cocher et une étiquette. Ensuite, peuplez un ListView avec les instances de ce composant personnalisé.

mise à jour: voir ma réponse pour le code complet

mise à jour 2: Pour une mise à jour du tutoriel, s'il vous plaît, consulter la documentation officielle. Il y avait beaucoup de nouveautés ajouté en 2.2. Enfin, l' Introduction to FXML couvre à peu près tout ce que vous devez savoir sur FXML.

mise à jour 3: Hendrik Ebbers a fait un très utile blog à propos des contrôles personnalisés de L'interface utilisateur.

32
demandé sur Andrey 2011-12-08 21:08:02

3 réponses

mise à Jour: Pour une mise à jour du tutoriel, s'il vous plaît, consulter la documentation officielle. Il y avait beaucoup de nouveautés ajouté en 2.2. Aussi, le Introduction to FXML couvre à peu près tout ce que vous devez savoir sur FXML. Enfin, Hendrik Ebbers a fait un excellent travail!--30-->blog à propos des contrôles personnalisés de L'interface utilisateur.


Après quelques jours de recherche autour de l' API et la lecture de quelques docs (Introduction to FXML,mise en route avec FXMLfixation de la propriété,l'Avenir de FXML) j'ai trouvé une solution assez raisonnable. L'information la moins directe que j'ai apprise de cette petite expérience est que l'instance d'un controller (déclaré avec FX:controller en FXML) est détenue par le FXMLLoader qui a chargé le fichier FXML... Le pire de tous, ce fait important n'est mentionné qu'en place dans tous les docs que j'ai vu:

un contrôleur n'est généralement visible que par le chargeur FXML qui le crée

alors, rappelez-vous, afin de programmer (à partir du code Java) obtenir une référence à l'instance d'un controller qui a été déclaré dans FXML avec fx:controller utiliser FXMLLoader.getController () (reportez-vous à la mise en œuvre de la classe de exemple complet).

une Autre chose à noter est que Propriété.bindBiderctional () définir la valeur de l'appel de la propriété à la valeur du bien transmis comme argument. Compte tenu de deux propriétés booléennes target (réglé à false et source (initialement fixée à true), l'appel d' target.bindBidirectional(source) définir la valeur de targettrue. Évidemment, tout changement subséquent à l'une ou l'autre propriété changera la valeur de l'autre propriété (target.set(false) cause la valeur de source à définir à 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

bref, voici le code complet qui montre comment FXML et Java peuvent fonctionner ensemble (ainsi que quelques autres choses utiles)

paquet structure:

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.propriétés

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);
        }
    }
}

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

Choisecontroller.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);
            }
        }
    }
}
31
répondu Andrey 2016-12-01 21:49:37

pour JavaFx 2.1, vous pouvez créer un composant de contrôle FXML personnalisé de cette façon:

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

Composant le code:

Moncomposant.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!");
    }
}

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

Ce code doit vérifier si il n'y a pas de fuite de mémoire.

6
répondu Daniel De León 2012-10-02 01:53:38

la réponse rapide est tag, cependant, vous aurez besoin de définir le ChoiceModel dans la classe Controller.

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

  fx:controller="fxmltestinclude.ChoiceDemo"
>
  <children>
    **<fx:include source="Choice.fxml" />**
    <ListView fx:id="choices" />
  </children>
</VBox>
2
répondu JimClarke 2011-12-08 21:55:05