5

I have got a JavaFX 2.2 FlowPane and want it's children to be change their order on demand.

I found out that

Collections.sort( getChildren(), new Comparator(){...} );

causes the exception

java.lang.IllegalArgumentException: Children: duplicate children added: parent = ...

I also tried to sort using the child Nodes toFront() method (as suggested here How to change order of children in JavaFX), but this does not seem to influence the list order.

Is there anything else I could try or are children in a scene graph unmutable regarding their order?

Community
  • 1
  • 1
taranion
  • 51
  • 1
  • 2

1 Answers1

8

Update: Errata

Due to a bug in JavaFX 2.x, the sample solution for this answer will not work in JavaFX 2.x. Thanks to assylias for identifying the JavaFX 2.x bug:

When I run your program, the list does not update until I resize the window horizontally (vertically does not do anything) - jdk 7u25 / Windows 7 x64.

The bug is fixed in Java 8, so if you use Java 8, the sample solution will work.

Solution Strategy

  1. Create a new collection based on the flow pane children.
  2. Sort the new collection.
  3. Set the children of the flow pane to the contents of the sorted collection.

Code Snippet

ObservableList<Node> workingCollection = FXCollections.observableArrayList(
    flow.getChildren()
);

Collections.sort(workingCollection, new NodeComparator());

flow.getChildren().setAll(workingCollection);

Sample Output

Sample output before sorting:

unsorted

Sample output after sorting:

sorted

Sample Application

import javafx.application.Application;
import javafx.collections.*;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.util.*;

public class FlowGo extends Application {
    private final ObservableList<Label> labels = FXCollections.observableArrayList(
        createLabel("oranges"),
        createLabel("apples"),
        createLabel("pears"),
        createLabel("peaches"),
        createLabel("bananas")
    );

    @Override public void start(Stage stage) {
        FlowPane flow = createFlow();

        VBox layout = new VBox(10);
        layout.getChildren().setAll(
            flow,
            createActionButtons(flow)
        );
        layout.setAlignment(Pos.CENTER);
        layout.setStyle("-fx-padding: 10px; -fx-background-color: cornsilk;");

        stage.setScene(new Scene(layout));
        stage.show();
    }

    private HBox createActionButtons(final FlowPane flow) {
        Button sort = new Button("Sort");
        sort.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent actionEvent) {
                ObservableList<Node> workingCollection = FXCollections.observableArrayList(
                    flow.getChildren()
                );

                Collections.sort(workingCollection, new NodeComparator());

                flow.getChildren().setAll(workingCollection);
            }
        });

        Button reset = new Button("Reset");
        reset.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                flow.getChildren().setAll(labels);
            }
        });

        HBox buttonBar = new HBox(10);
        buttonBar.getChildren().addAll(
            sort,
            reset
        );
        buttonBar.setAlignment(Pos.CENTER);

        return buttonBar;
    }

    private FlowPane createFlow() {
        FlowPane flow = new FlowPane();
        flow.setHgap(10);
        flow.setVgap(10);
        flow.getChildren().setAll(labels);
        flow.setAlignment(Pos.CENTER);
        flow.setStyle("-fx-padding: 10px; -fx-background-color: lightblue; -fx-font-size: 16px;");

        return flow;
    }

    private Label createLabel(String text) {
        Label label = new Label(text);
        label.setUserData(text);

        return label;
    }

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

    private static class NodeComparator implements Comparator<Node> {
        @Override
        public int compare(Node o1, Node o2) {
            String s1 = (String) o1.getUserData();
            String s2 = (String) o2.getUserData();

            return s1.compareTo(s2);
        }
    }
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • When I run your program, the list does not update until I resize the window horizontally (vertically does not do anything) - jdk 7u25 / Windows 7 x64. – assylias Sep 06 '13 at 23:24
  • Often, when you do not get automatic updates, it is because the fields that are displayed in your GUI controls do not meet the minimum definition of a JavaFX bean and therefore are not full-fledged properties. In this case, the controls will not automatically receive change notifications and will not auto-update. – scottb Sep 07 '13 at 00:33
  • @assylias the solution works with Java 8, but not Java 7 - thanks for pointing this out. – jewelsea Sep 07 '13 at 01:41
  • @scottb Missing property definitions on beans is a common cause of update failures in other cases, but not this one. In this case, it is the child node list of a parent in the active scene graph which is being reordered, modifying the scene graph structure. It is the responsibility of the JavaFX framework to monitor any changes to the scene graph structure and update them appropriately. The framework performs correctly for Java 8, but not for Java 7 (JavaFX 2.2). – jewelsea Sep 07 '13 at 01:49