Marcos Supridatta
Marcos Supridatta

Reputation: 161

Adding a list of specific node on a custom JavaFX FXML control

I'm trying to create a toolbar in JavaFX to add buttons using FXML like this:

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

<?import java.lang.*?>
<?import java.net.*?


<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.supridatta.javafx.*?>

<BorderPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="200" prefWidth="320" fx:controller="com.supridatta.javafx.MainController">
    <stylesheets>
        <URL value="@main.css"/>
    </stylesheets>
    <top>
        <com.supridatta.javafx.ButtonBar fx:id="buttonBar">
            <buttons>
                <ButtonBarButton path="/com/supridatta/javafx/icons/plus.png"/>
                <ButtonBarButton path="/com/supridatta/javafx/icons/minus.png"/>
                <ButtonBarButton path="/com/supridatta/javafx/icons/last.png"/>
            </buttons>
        </com.supridatta.javafx.ButtonBar>
    </top>
    <bottom>
        <Button text="Exibir todos" onAction="#showAllButtons"/>
    </bottom>
</BorderPane>

Here is the corresponding java class:

package com.supridatta.javafx;

import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;

public class ButtonBar extends StackPane {

    private final HBox contentPane = new HBox();
    private final ObservableList<ButtonBarButton> buttons = FXCollections.observableArrayList();

    public ButtonBar() {
        initButtonBar();
    }

    private void initButtonBar() {
        getChildren().add(contentPane);
        setAlignment(Pos.CENTER);
        buttons.addListener(new ListChangeListener<Node>() {

            @Override
            public void onChanged(ListChangeListener.Change<? extends Node> c) {
                while (c.next()) {
                    if (c.wasAdded()) {
                        for (Node node : c.getAddedSubList()) {
                            contentPane.getChildren().add(node);
                        }
                    }
                    if (c.wasRemoved()) {
                        for (Node node : c.getAddedSubList()) {
                            contentPane.getChildren().remove(node);
                        }
                    }
                }
            }
        });
    }

    public void setButtons(ObservableList<ButtonBarButton> contents) {
        this.buttons.setAll(contents);
    }

    public ObservableList<ButtonBarButton> getButtons() {
        return buttons;
    }

    public void addButton(ButtonBarButton button) {
        buttons.add(button);
    }

    public void removeButton(ButtonBarButton button) {
        buttons.remove(button);
    }

}

When I run the project I'm getting this exception:

java.lang.IllegalArgumentException: Unable to coerce ButtonBarButton@2fd0ac8f[styleClass=button ButtonBarButton]'' to

interface javafx.collections.ObservableList.

Thanks in advance.

Upvotes: 3

Views: 3368

Answers (1)

James_D
James_D

Reputation: 209418

Since you have a setButtons method defined, the FXMLLoader is going to attempt to pass in the value defined by the elements inside <buttons>...</buttons> to that method. (See Introduction to FXML, and note in particular "A read-only list property is a Bean property whose getter returns an instance of java.util.List and has no corresponding setter method." - my emphasis.)

If you have a getButtons() method returning a list, and no setButtons method, then the FXMLLoader will do what you want, and pass the value corresponding to each element to the add method invoked on the result of calling getButtons(). I.e. it does

getButtons().add(new ButtonBarButton(...));
getButtons().add(new ButtonBarButton(...));
...

You either want it to do this, or you want it to do

   setButtons(FXCollections.observableArrayList(new ButtonBarButton(...), new ButtonBarButton(...), ...)); 

So you have two fixes: either remove the setButtons(...) method from ButtonBar, or arrange to pass in an ObservableList:

        <buttons>
            <FXCollections fx:factory="observableArrayList">
                <ButtonBarButton path="/com/supridatta/javafx/icons/plus.png"/>
                <ButtonBarButton path="/com/supridatta/javafx/icons/minus.png"/>
                <ButtonBarButton path="/com/supridatta/javafx/icons/last.png"/>
            </FXCollections>
        </buttons>

Upvotes: 4

Related Questions