Adding a custom component to SceneBuilder 2.0
Asked Answered
P

2

21

I have the need to have a selection listener and select method on a pane to be able to monitor and present a highlight when a node is clicked on.

I did the following:

public class PaneWithSelectionListener extends Pane {

    private ObjectProperty<Annotation> selectedAnnotation = new SimpleObjectProperty<>();

    public PaneWithSelectionListener() { 
        super();
        selectedAnnotation.addListener((obs, oldAnno, newAnno) -> {
            if (oldAnno != null) {
                oldAnno.setStyle("");
            }
            if (newAnno != null) {
                newAnno.setStyle("-fx-border-color: blue;-fx-border-insets: 5;-fx-border-width: 1;-fx-border-style: dashed;");
            }
        });

        setOnMouseClicked(e->selectAnnotation(null));
    }

    public void selectAnnotation(Annotation ann){
        selectedAnnotation.set(ann);
    }
}

And this works great - however I am not able to work with SceneBuilder anymore since my FXML references this PaneWithSelectionListener rather than Pane. I am not sure how to get my custom pane into SceneBuilder. I have looked at other questions and they are all a combination of FXML and Controllers - where this is just a Pane.

Does anyone know of a way to do this, or perhaps swap the Pane for a PaneWithSelectionListener at initialization time?

Thanks

Primulaceous answered 5/5, 2015 at 21:45 Comment(2)
Do you just need single selection?Klepac
Yes - Single selection is all I need. I am representing annotations on top of an image. If the user clicks on one - it should select. I don't think they need multiple selection. Just an indicator on which one will be affected. Thanks.Primulaceous
K
32

If the issue is just to make your custom class available in SceneBuilder, you can do so with the following steps:

  1. Bundle your custom class (and any supporting classes, such as Annotation) as a jar file
  2. In SceneBuilder, activate the drop-down button next to "Library" in the top of the left pane: enter image description here
  3. Choose "Import JAR/FXML File..."
  4. Select the Jar file created from step 1
  5. Make sure the class you need access to in SceneBuilder (PaneWithSelectionListener) is checked
  6. Press "Import Component"
  7. PaneWithSelectionListener will now appear in SceneBuilder under "Custom" in the left pane: enter image description here

You'll notice the drop-down in SceneBuilder has a "Custom Library Folder" option, from which you can open the folder where the jar files are stored. For a quick option, you can just copy jar files to this folder and (after a short delay), the contained classes will appear in the "Custom" list.

Klepac answered 6/5, 2015 at 13:26 Comment(7)
I did try that yesterday, however when I attempt to load it, the selection is blank.Primulaceous
Ok this is weird. I moved my component into another package and exported the jar and it worked.Primulaceous
If you had it in the default package, I think that makes it fail (FXML in general does not play nicely with the default package, though the only loss there is for very quick test code anyway).Klepac
If working with SceneBuilder 2.0 or lower, this solution will still fail to load an fxml using a custom control when opening from eclipse. In order to make this work you need to add the jar to SceneBuilder's package.cfg file. See the bottom part of this accepted answer here. Note: Linux uses a ':' instead of a ';' to separate the jars on the pathKirshbaum
Doesn't work for me either. The component class is in the jar file but the selection dialog is blank. No, I'm not going to move it to a separate package and then have to track and maintain two projects.Gladdie
If you followed the other steps correctly, the symptom of 'selection dialog is blank' is likely due to SceneBuilder 11.x.x not supporting Java JDK 12 compiled custom component. You need to build with Java JDK 11 and create (or export) your custom component to a jar from that build.Lightening
Related step by step example for SceneBuilder 17: JavaFX custom component usage in SceneBuilder.Antebellum
L
0

I created a CustomCB a combo box which is of type a userObject. In my case I have used <APerson> as userObject. Entire project and the TESTER are here. First one is CustomCB, the components and the JAR file generated out of this code is added to the Library. This can be loaded in SCENE BUILDER. Thereafter it is available for scene design.

Then of course, you have a tester CustomCB2, which also can use a FXML instead of the way I have done.

I actually want the items starting with the text I type in the ComboBox to appear in the list. But I don't know how to do it. Because the FIRST NAME or LAST NAME of the PERSON class can start with 'Pe'. If I find a solution, I will post it here.

This is the Custom Component.

package customCB;
    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;

    /**
     *
     * @author Hornigold Arthur
     */

    public class APerson {

    private final StringProperty firstName;
    private final StringProperty lastName;
    private final IntegerProperty familyID;
    private final IntegerProperty personID;

    public APerson() {
        this(null, null, 0,0);
    }

    /**
     * Constructor with some initial data.
     * 
     * @param familyID
     * @param familyName
     */

    public  APerson (String firstName, String lastName, int familyID, int personID) {
        this.firstName = new SimpleStringProperty(firstName);
        this.lastName = new SimpleStringProperty(lastName);
        this.familyID = new SimpleIntegerProperty(familyID);
        this.personID = new SimpleIntegerProperty(personID);
    }

    public int getFamilyID() {
        return familyID.get();
    }

    public void setFamilyID(int FamilyID) {
        this.familyID.set(FamilyID);
    }

    public IntegerProperty familyIDProperty() {
        return familyID;
    }

    public int getPersonID() {
        return personID.get();
    }

    public void setPersonID(int PersonID) {
        this.personID.set(PersonID);
    }

    public IntegerProperty personIDProperty() {
        return personID;
    }

    public String getFirstName() {
        return firstName.get();
    }

    public void setFirstName(String FirstName) {
        this.firstName.set(FirstName);
    }

    public StringProperty firstNameProperty() {
        return firstName;
    }

    public String getLastName() {
        return lastName.get();
    }

    public void setLastName(String LastName) {
        this.lastName.set(LastName);
    }

    public StringProperty lastNameProperty() {
        return lastName;
    }


    public String toString() {
        String name = getFirstName() + " " + getLastName()+ " [" + getFamilyID() +"]";
        return name;
    }
}

This is the FXML for the Custom Component.

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

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.VBox?>

<fx:root stylesheets="@application.css" type="javafx.scene.layout.VBox" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <ComboBox fx:id="myCustomCombo" editable="true" onAction="#cbOnAction" prefWidth="300.0" style="-fx-background-color: white;" />
</fx:root>

This is the controller for this FXML but do not mention it in FXML file. It throws error.

package customCB;

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

import org.controlsfx.control.textfield.TextFields;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;

public class CustomComboController extends VBox{

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private ComboBox<APerson> myCustomCombo;

    @FXML
    void cbOnAction(ActionEvent event) {

    }

    @FXML
    void initialize() {
        assert myCustomCombo != null : "fx:id=\"myCustomCombo\" was not injected: check your FXML file 'CustomLvFXML.fxml'.";
    }

    public CustomComboController() {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("customCombo.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

    public void setCBValues(javafx.collections.ObservableList<APerson> values) {
        myCustomCombo.setItems(values);
        myCustomCombo.setEditable(true);
        TextFields.bindAutoCompletion(myCustomCombo.getEditor(), myCustomCombo.getItems());
    }

}

Download controlsfx-8.40.12.jar from WEB and include this in the BUILD PATH as library.

Now create a jar file for this project. "CustomCB.jar".

This JAR file has to be included as custom control in Scene Builder. I use version 10.0.

Now that it is part of Scene builder you can use this component in designing, unless you can do it the way I have done in my TEST CODE. You need to include "CustomCB.jar" as part of the library for building.

This is the code for the tester.

package customCB2;


    import javafx.application.Application;
    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.layout.VBox;


    public class Main extends Application {

        static private javafx.collections.ObservableList<APerson> fathers = javafx.collections.FXCollections.observableArrayList();
        static private javafx.collections.ObservableList<APerson> mothers = javafx.collections.FXCollections.observableArrayList();



    @Override
    public void start(Stage stage) throws Exception {

        CustomComboController customControl2 = new CustomComboController();
        CustomComboController customControl3 = new CustomComboController();

        loadFathers();
        loadMothers();

        customControl2.setCBValues(fathers);
        customControl3.setCBValues(mothers);

        VBox root = new VBox();
        root.getChildren().addAll(customControl2, customControl3);

        stage.setScene(new Scene(root));
        stage.setTitle("Custom Control Combo box");
        stage.setWidth(300);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    private void loadFathers() {
        fathers.clear();
        fathers.add(new APerson("Hornigold","Arthur",1,63));
        fathers.add(new APerson("Andrews","Sundareson",2,60));
        fathers.add(new APerson("Christopher","Easweradoss",3,57));
        fathers.add(new APerson("Arthur","Kennedy",4,55));
    }

    private void loadMothers() {
        mothers.clear();
        mothers.add(new APerson("Victoria","Arthur",1,95));
        mothers.add(new APerson("Eliza", "Daniel",1,60));
        mothers.add(new APerson("Nesammal", "Rivington",2,57));
        mothers.add(new APerson("Ratnammal","Andews",1,55));
    }


}

It needs lot of improvements. If anyone can improvise, please add it here.

Longevous answered 24/7, 2018 at 16:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.