Adding a list of specific nodes to a JavaFX FXML custom control
I am 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 relevant 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 get this exception:
java.lang.IllegalArgumentException: Unable to coerce ButtonBarButton@2fd0ac8f[styleClass=button ButtonBarButton]'' to
interface javafx.collections.ObservableList.
Thanks in advance.
source to share
Since you have a method set setButtons
, it FXMLLoader
will try to pass to this element the value defined by the elements inside <buttons>...</buttons>
. (See Introduction to FXML and note, in particular, "A read-only property is a Bean property whose receiver returns an instance java.util.List
and does not have a corresponding setter." Is my emphasis.)
If you have a method getButtons()
that returns a list and a method setButtons
, then it FXMLLoader
will do what you want, and pass the value corresponding to each element to the method add
called on the result of the call getButtons()
. That is, it does
getButtons().add(new ButtonBarButton(...));
getButtons().add(new ButtonBarButton(...));
...
You either want it to be done or you want it to do
setButtons(FXCollections.observableArrayList(new ButtonBarButton(...), new ButtonBarButton(...), ...));
So you have two fixes, either remove the method setButtons(...)
from ButtonBar
, or agree to pass to 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>
source to share