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:
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.
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 :)
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);
});
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With