44 votes

Comment créer des composants personnalisés dans JavaFX 2.0 à l'aide de FXML ?

Je n'arrive pas à trouver de matériel 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, j'alimente une ListView avec des instances de ce composant personnalisé.

MISE À JOUR :

MISE À JOUR 2 : Pour un tutoriel mis à jour, veuillez consulter la documentation officielle . Il y avait beaucoup de nouvelle substance qui a été ajouté en 2.2. Enfin, le Introduction à FXML couvre à peu près tout ce que vous devez savoir sur FXML.

MISE À JOUR 3 : Hendrik Ebbers a fait un commentaire extrêmement utile article de blog sur les contrôles d'interface utilisateur personnalisés.

0 votes

L'exemple de contrôle personnalisé figurant dans votre lien "documentation officielle" ne fonctionne pas. Il contient deux méthodes qui ne font pas partie de l'API.

0 votes

@danLeon tout d'abord, ce n'est pas MA "documentation officielle". C'est LA "documentation officielle" écrite par les employés d'Oracle qui travaillent sur JavaFX. Ensuite, le code auquel je renvoie contient un exemple fonctionnel de la façon de créer des composants personnalisés dans JavaFX 2.2. Il est fort probable que la version que vous avez soit plus ancienne, d'où l'absence de méthodes. Voici un extrait de cette page : "Avant de commencer, assurez-vous que la version de NetBeans IDE que vous utilisez prend en charge JavaFX 2.2".

0 votes

Vous avez raison ! Mon IDE était sous JavaFx 2.1, merci pour le commentaire. Maintenant sur 2.2, j'ai supprimé toute version précédente de java dans mon ordinateur.

41voto

Andrey Points 2523

Mise à jour : Pour un tutoriel mis à jour, veuillez consulter la documentation officielle . Il y avait beaucoup de nouvelle substance qui a été ajouté dans la version 2.2. De plus, le Introduction à FXML couvre à peu près tout ce que vous devez savoir sur FXML. Enfin, Hendrik Ebbers a créé un document extrêmement utile. article de blog sur les contrôles d'interface utilisateur personnalisés.


Après quelques jours d'observation de la API et la lecture de quelques documents ( Introduction à FXML , Démarrer avec FXML Liaison de propriété , L'avenir de FXML ), j'ai trouvé une solution assez sensée. L'information la moins directe que j'ai apprise lors de cette petite expérience est que l'instance d'un contrôleur (déclarée avec fx:controller dans FXML) est détenue par la fonction FXMLLoader qui a chargé le fichier FXML... Le pire, c'est que ce fait important n'est mentionné qu'en un endroit dans tous les docs que j'ai vus :

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

Donc, rappelez-vous, pour obtenir de manière programmatique (à partir du code Java) une référence à l'instance d'un contrôleur qui a été déclaré dans FXML avec fx:controller utiliser FXMLLoader.getController() (voir l'implémentation de la classe ChoiceCell ci-dessous pour un exemple complet).

Une autre chose à noter est que Propriété.bindBiderctional() définira la valeur de la propriété appelante à la valeur de la propriété passée en argument. Étant donné deux propriétés booléennes target (initialement fixé à false ) et source (initialement fixé à true ) en appelant target.bindBidirectional(source) définira la valeur de target a true . Évidemment, toute modification ultérieure de l'une ou l'autre des propriétés modifiera la valeur de l'autre ( target.set(false) fera en sorte que la valeur de source à régler sur 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

Quoi qu'il en soit, voici le code complet qui démontre comment FXML et Java peuvent fonctionner ensemble (ainsi que quelques autres choses utiles).

Structure du paquet :

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

1 votes

Je suis heureux de vous aider. Cependant, assurez-vous de consulter les liens que j'ai fournis dans la mise à jour. Beaucoup de nouvelles choses ont été ajoutées dans la version 2.2.

10voto

Daniel De León Points 2842

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>

Code du composant :

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 s'il n'y a pas de fuite de mémoire.

1 votes

Merci beaucoup, ça m'a aidé.

2voto

Wolfgang Fahl Points 1920

Le chapitre de l'introduction à fxml sur les composants personnalisés

m'a donné le bon tuyau. Mon intention était de combiner une étiquette, un curseur et un champ de texte dans un composant personnalisé LabeledValueSlider.

Exemple d'utilisation : voir Ressources/Fx de rc-dukes Self-Driving RC Car Java FX App

    <LabeledValueSlider fx:id='cannyThreshold1' text="Canny threshold 1" blockIncrement="1" max="2000" min="0" value="20" format="\%.0f"/>
    <LabeledValueSlider fx:id="cannyThreshold2" text="Canny threshold 2"  blockIncrement="1" max="2000" min="0" value="50" format="\%.0f"/>
    <LabeledValueSlider fx:id="lineDetectRho" text="LineDetect rho" blockIncrement="0.01" max="20" min="0" value="0.5" />
    <LabeledValueSlider fx:id="lineDetectTheta" text="LineDetect theta" blockIncrement="0.01" max="5" min="-5" value="0.5" />
    <LabeledValueSlider fx:id="lineDetectThreshold" text="LineDetect threshold" blockIncrement="1" max="200" min="0" value="20" format="\%.0f" />
    <LabeledValueSlider fx:id="lineDetectMinLineLength" text="LineDetect minLineLength"  blockIncrement="1" max="200" min="0" value="50" format="\%.0f"/>
    <LabeledValueSlider fx:id="lineDetectMaxLineGap" text="LineDetect maxLineGap" blockIncrement="1" max="500" min="0" value="50" format="\%.0f"/>

7 LabeledValueSliders

Fichier FXML

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>

<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml">

    <padding>
        <Insets left="10" right="10" />
    </padding>
    <Label fx:id='label' text="Label for Slider" minWidth="180"/>
    <Slider fx:id='slider' blockIncrement="1" max="100" min="0" value="50" />
    <TextField fx:id="textField" maxWidth="75"/>
</fx:root>

Composant Code source voir LabeledValueSlider.java

package org.rcdukes.app;

import java.io.IOException;
import java.net.URL;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;

/**
 * a Slider with a Label and a value
 * 
 * @author wf
 *
 */
public class LabeledValueSlider extends HBox {
  public static boolean debug=true;
  protected static final Logger LOG = LoggerFactory
      .getLogger(LabeledValueSlider.class);
  @FXML
  private Label label;
  @FXML
  private Slider slider;
  @FXML
  private TextField textField;

  String format;

  public String getFormat() {
    return format;
  }

  public void setFormat(String format) {
    textField.textProperty().bind(slider.valueProperty().asString(format));
    this.format = format;
  }

  public double getBlockIncrement() {
    return slider.getBlockIncrement();
  }

  public void setBlockIncrement(double value) {
    slider.setBlockIncrement(value);
  }

  public double getMax() {
    return slider.getMax();
  }

  public void setMax(double value) {
    slider.setMax(value);
  }

  public double getMin() {
    return slider.getMin();
  }

  public void setMin(double value) {
    slider.setMin(value);
  }

  public double getValue() {
    return slider.getValue();
  }

  public void setValue(double value) {
    slider.setValue(value);
  }

  public String getText() {
    return label.getText();
  }

  public void setText(String pLabelText) {
    label.setText(pLabelText);
  }

  public URL  getResource(String path) {
    return getClass().getClassLoader().getResource(path);
  }

  /**
   * construct me
   * see https://docs.oracle.com/javase/9/docs/api/javafx/fxml/doc-files/introduction_to_fxml.html#custom_components
   */
  public LabeledValueSlider() {
    FXMLLoader fxmlLoader = new FXMLLoader(
        getResource("fx/labeledvalueslider.fxml"));
    try {
      // let's load the HBox - fxmlLoader doesn't know anything about us yet
      fxmlLoader.setController(this); 
      fxmlLoader.setRoot(this);
      Object loaded = fxmlLoader.load();
      Object root=fxmlLoader.getRoot();

      if (debug) {
        String msg=String.format("%s loaded for root %s", loaded.getClass().getName(),root.getClass().getName());
        LOG.info(msg);
      }

      textField.setAlignment(Pos.CENTER_RIGHT);
      if (format == null)
        setFormat("%.2f");
    } catch (IOException exception) {
      throw new RuntimeException(exception);
    }
  }
}

2voto

JimClarke Points 823

La réponse rapide est la balise <fx:include>, cependant, vous devrez 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>

0 votes

Consultez également ce document fxexperience.com/wp-content/uploads/2011/08/

0 votes

Ok, mais comment utiliser Choice.fxml pour rendre chaque élément de la liste des choix ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X