Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does JavaFX have a component with flow columns and possibility to select an item?

Tags:

java

javafx

I have a list of N items and I need them to be displayed in columns like in vertical TilePane. But I also need to select them via keyboard and mouse, to get selected item and to scroll to the selected item. Something like this:

enter image description here

Could anyone say what JavaFX component can be used in such case? I thought about TilePane but it doesn't have a lot of features I need.

like image 312
SilverCube Avatar asked Sep 02 '25 04:09

SilverCube


1 Answers

If you are trying to implement by FlowPane, below is an approach that you can get some initial idea.

The general idea is to create a custom FlowPane that:

  • wraps the provided nodes with some StackPane that highlights the selection

  • have key events to listen for UP/Down

  • selected index to highlight the wrapper based on click or key events.

class SelectableFlowPane extends FlowPane {
    private IntegerProperty selectedIndex = new SimpleIntegerProperty();
    private PseudoClass selectedPseudo = PseudoClass.getPseudoClass("selected");

    public SelectableFlowPane() {
        selectedIndex.addListener((obs, old, val) -> {
            if (old.intValue() > -1) {
                getChildren().get(old.intValue()).pseudoClassStateChanged(selectedPseudo, false);
            }
            if (val.intValue() > -1) {
                getChildren().get(val.intValue()).pseudoClassStateChanged(selectedPseudo, true);
            }
        });
        
        addEventFilter(KeyEvent.KEY_PRESSED, e -> {
            if (selectedIndex.get() < 0) {
                return;
            }
            KeyCode code = e.getCode();
            if (code == KeyCode.DOWN) {
                int nextIndex = selectedIndex.get() + 1 < getChildren().size() ? selectedIndex.get() + 1 : 0;
                selectedIndex.set(nextIndex);
            } else if (code == KeyCode.UP) {
                int nextIndex = selectedIndex.get() - 1 < 0 ? getChildren().size() - 1 : selectedIndex.get() - 1;
                selectedIndex.set(nextIndex);
            }
        });
    }

    public void addChildren(List<? extends Node> nodes) {
        nodes.forEach(n -> {
            StackPane outer = new StackPane(n);
            outer.setOnMouseClicked(e -> {
                selectedIndex.set(getChildren().indexOf(outer));
            });
            outer.getStyleClass().add("outer");
            getChildren().add(outer);
        });
    }
}

You can check the full working demo below. There is lot of scope to fine tune this code :)

enter image description here

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.css.PseudoClass;
import javafx.event.Event;
import javafx.event.EventDispatcher;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public class SelectableFlowPaneDemo extends Application {
    SecureRandom rnd = new SecureRandom();
    public static final String CSS = "data:text/css," + // language=CSS
            """
                    .label{
                       -fx-font-size: 15px;
                       -fx-font-weight: bold;
                       -fx-padding: 8px 4px;
                    }
                    .outer{
                        -fx-alignment: CENTER_LEFT;
                    }
                    .outer:hover{
                       -fx-background-color: #AAAAAA50;
                    }
                    .outer:selected{
                       -fx-background-color: #99d1ff, #cce8ff;
                       -fx-background-insets: 0, 1;
                    }
                    """;

    @Override
    public void start(Stage primaryStage) throws Exception {
        SelectableFlowPane flowPane = new SelectableFlowPane();
        List<Label> items = new ArrayList<>();
        IntStream.range(1, 100).forEach(i -> {
            String t = rnd.nextInt() % 2 == 0 ? "Item " : "";
            items.add(new Label("Item " + t + rnd.nextInt(256589)));
        });
        flowPane.addChildren(items);
        flowPane.setOrientation(Orientation.VERTICAL);
        flowPane.setHgap(10);
        flowPane.setHgap(10);
        flowPane.setPadding(new Insets(10));

        ScrollPane root = new ScrollPane(flowPane);
        EventDispatcher scrollPaneEventDispatcher = root.getEventDispatcher();
        root.setEventDispatcher((event, tail) -> {
            Event eventToDispatch = scrollPaneEventDispatcher.dispatchEvent(event, tail);
            if (KeyEvent.KEY_PRESSED.equals(event.getEventType())) {
                if (KeyCode.UP.equals(((KeyEvent) event).getCode()) || KeyCode.DOWN.equals(((KeyEvent) event).getCode())) {
                    if (eventToDispatch == null) {
                        return event;
                    }
                }
            }
            return eventToDispatch;
        });
        flowPane.selectedIndex.addListener((obs, old, val) -> {
            if (val.intValue() > -1) {
                ensureVisible(root, flowPane.getChildren().get(val.intValue()));
            }
        });
        root.setFitToHeight(true);
        Scene scene = new Scene(root, 500, 500);
        scene.getStylesheets().add(CSS);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Selectable FlowPane Demo");
        primaryStage.show();
    }

    private static void ensureVisible(ScrollPane pane, Node node) {
        double width = pane.getContent().getBoundsInLocal().getWidth();
        double height = pane.getContent().getBoundsInLocal().getHeight();
        double x = node.getBoundsInParent().getMaxX();
        double y = node.getBoundsInParent().getMaxY();

        pane.setVvalue(y / height);
        pane.setHvalue(x / width);
        node.requestFocus();
    }

    class SelectableFlowPane extends FlowPane {
        private IntegerProperty selectedIndex = new SimpleIntegerProperty();
        private PseudoClass selectedPseudo = PseudoClass.getPseudoClass("selected");

        public SelectableFlowPane() {
            selectedIndex.addListener((obs, old, val) -> {
                if (old.intValue() > -1) {
                    getChildren().get(old.intValue()).pseudoClassStateChanged(selectedPseudo, false);
                }
                if (val.intValue() > -1) {
                    getChildren().get(val.intValue()).pseudoClassStateChanged(selectedPseudo, true);
                }
            });

            addEventFilter(KeyEvent.KEY_PRESSED, e -> {
                if (selectedIndex.get() < 0) {
                    return;
                }
                KeyCode code = e.getCode();
                if (code == KeyCode.DOWN) {
                    int nextIndex = selectedIndex.get() + 1 < getChildren().size() ? selectedIndex.get() + 1 : 0;
                    selectedIndex.set(nextIndex);
                } else if (code == KeyCode.UP) {
                    int nextIndex = selectedIndex.get() - 1 < 0 ? getChildren().size() - 1 : selectedIndex.get() - 1;
                    selectedIndex.set(nextIndex);
                }
            });
        }

        public void addChildren(List<? extends Node> nodes) {
            nodes.forEach(n -> {
                StackPane outer = new StackPane(n);
                outer.setOnMouseClicked(e -> {
                    selectedIndex.set(getChildren().indexOf(outer));
                });
                outer.getStyleClass().add("outer");
                getChildren().add(outer);
            });
        }
    }
}
like image 86
Sai Dandem Avatar answered Sep 05 '25 00:09

Sai Dandem