Javafx: get a Scrollbar from a Treeview at a Button Event - javafx

I want to create a button event to create a list. On this list, the Scrollbar
should be moved. But I become an error because the lookup method is only usable after rendering.
HBox root = new HBox();
Button b = new Button("initList");
b.setOnAction(e->
{
ListView<String> list = new ListView<String>();
for (int i = 0; i < 20; i++)
{
list.getItems().add(i+"");
}
ScrollBar bar = (ScrollBar) list.lookup(".scroll-bar");
bar.setValue(0.5);
root.getChildren().add(list);
});
root.getChildren().add(b);
Scene scene = new Scene(root,400,400);
primaryStage.setScene(scene);
primaryStage.show();

To ensure to be called after the layout, you can queue the task using runLater:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class LookupTest extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
Button b = new Button("initList");
b.setOnAction(e ->
{
ListView<String> list = new ListView<String>();
for (int i = 0; i < 20; i++) {
list.getItems().add(i + "");
}
Platform.runLater(() -> {
ScrollBar bar = (ScrollBar) list.lookup(".scroll-bar");
if (bar != null) bar.setValue(0.5);
});
root.getChildren().add(list);
});
root.getChildren().add(b);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

How to set popup scene mouse transparent in javafx

How to do that in JavaFX?
The popup shows up when the mouse enters a node. When the mouse enters the showing popup, the popup obscures the mouse from the node. Then the node fire exit event. How to make the popup ignore the mouse events?
code
package sample;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 300, 275));
Label labelNode = new Label("Label Node");
labelNode.setPrefHeight(200);
labelNode.styleProperty().set("-fx-background-color: orange");
Popup popup = new Popup();
popup.getScene().getRoot().setMouseTransparent(true);
AnchorPane popContent =new AnchorPane();
popContent.styleProperty().set("-fx-background-color: red");
popContent.setPrefHeight(100);
popContent.getChildren().add(new Label("Popup content"));
popup.getContent().add(popContent);
labelNode.setOnMouseEntered(event->{
Point3D point3D = labelNode.localToScene(event.getX(), event.getY(), 0);
popup.show(primaryStage, point3D.getX()-5, point3D.getY()-5);
});
labelNode.setOnMouseExited(event->{
popup.hide();
});
root.getChildren().add(labelNode);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Please try moving the cursor in to "yellow" several times.
Solution:
Keep two boolean nodeExited and popupExited statuses. Hide popup when both are true.
package sample;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class Main extends Application {
boolean nodeExited = false;
boolean popupExited = false;
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 300, 275));
Label labelNode = new Label("Label Node");
labelNode.setPrefHeight(200);
labelNode.styleProperty().set("-fx-background-color: orange");
Popup popup = new Popup();
popup.getScene().getRoot().setMouseTransparent(true);
AnchorPane popContent = new AnchorPane();
popContent.styleProperty().set("-fx-background-color: red");
popContent.setPrefHeight(100);
popContent.getChildren().add(new Label("Popup content"));
popup.getContent().add(popContent);
popup.getScene().setOnMouseEntered(event -> {
popupExited = false;
});
popup.getScene().setOnMouseExited(event -> {
popupExited = true;
if (nodeExited)
popup.hide();
});
labelNode.setOnMouseEntered(event -> {
nodeExited = false;
Point3D point3D = labelNode.localToScene(event.getX(), event.getY(), 0);
popup.show(primaryStage, point3D.getX() - 5, point3D.getY() - 5);
});
labelNode.setOnMouseExited(event -> {
nodeExited = true;
if (popupExited)
popup.hide();
});
root.getChildren().add(labelNode);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Scroll ScrollPane down or up when dragging/dropping an item into it.

I would like to have a ScrollPane scroll up or down when a user drags something to its edge. The ScrollPane would have a VBox inside it and would be inside a VBox too.
I assume I need to put something in setOnDragExited. But what exactly?
Here a minimal program for an example:
package application;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
VBox outerBox = new VBox();
outerBox.setMaxSize(700, 300);
root.setCenter(outerBox);
Label outerLabel = new Label("I am outside!");
ScrollPane sp = new ScrollPane();
outerBox.getChildren().addAll(outerLabel,sp);
VBox innerBox = new VBox();
//setting size bigger than ScrollPane's view.
innerBox.setPrefSize(600, 600);
sp.setContent(innerBox);
Label dragMe = new Label("Drag me to the edge of scroll pane! \n"+"or drop me in the scrollpane!");
root.setTop(dragMe);
dragMe.setOnDragDetected((MouseEvent event) ->{
Dragboard db = dragMe.startDragAndDrop(TransferMode.ANY);
db.setDragView(((Node) event.getSource()).snapshot(null, null));
ClipboardContent content = new ClipboardContent();
content.putString((dragMe.getText()));
db.setContent(content);
event.consume();
});
sp.setOnDragOver((DragEvent event) ->{
event.acceptTransferModes(TransferMode.MOVE);
event.consume();
});
sp.setOnDragEntered((DragEvent event) -> {
});
sp.setOnDragExited((DragEvent event) -> {
System.out.println("-----Make the scrollpane scroll up or down depending on exiting on bottem or top------");
event.consume();
});
sp.setOnDragDropped((DragEvent event) ->{
Dragboard db = event.getDragboard();
System.out.println(((VBox) sp.getContent()).getChildren().add(new Label(db.getString())));
});
Scene scene = new Scene(root,1000,1000);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Found this answer here:
Want to trigger scroll when dragging node outside the visible area in ScrollPane
It was not answered completely and did not use a ScrollPane so I thought I post my work/findings as an answer.
I found out you can do this by creating an animation:
private Timeline scrolltimeline = new Timeline();
....
scrolltimeline.setCycleCount(Timeline.INDEFINITE);
scrolltimeline.getKeyFrames()
.add(new KeyFrame(Duration.millis(20), (ActionEvent) -> { dragScroll();}));
Minimal:
package application;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private ScrollPane sp;
private Timeline scrolltimeline = new Timeline();
private double scrollVelocity = 0;
boolean dropped;
//Higher speed value = slower scroll.
int speed = 200;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
sp = new ScrollPane();
sp.setPrefSize(300, 300);
VBox outer = new VBox(sp);
VBox innerBox = new VBox();
innerBox.setPrefSize(200,1000);
sp.setContent(innerBox);
root.setCenter(outer);
Label dragMe = new Label("drag me to edge!\n"+"or drop me in scrollpane!");
root.setTop(dragMe);
setupScrolling();
dragMe.setOnDragDetected((MouseEvent event) ->{
Dragboard db = dragMe.startDragAndDrop(TransferMode.ANY);
db.setDragView(((Node) event.getSource()).snapshot(null, null));
ClipboardContent content = new ClipboardContent();
content.putString((dragMe.getText()));
db.setContent(content);
event.consume();
});
Scene scene = new Scene(root, 640, 480);
primaryStage.setScene(scene);
primaryStage.show();
}
private void setupScrolling() {
scrolltimeline.setCycleCount(Timeline.INDEFINITE);
scrolltimeline.getKeyFrames().add(new KeyFrame(Duration.millis(20), (ActionEvent) -> { dragScroll();}));
sp.setOnDragExited((DragEvent event) -> {
if (event.getY() > 0) {
scrollVelocity = 1.0 / speed;
}
else {
scrollVelocity = -1.0 / speed;
}
if (!dropped){
scrolltimeline.play();
}
});
sp.setOnDragEntered(event -> {
scrolltimeline.stop();
dropped = false;
});
sp.setOnDragDone(event -> {
System.out.print("test");
scrolltimeline.stop();
});
sp.setOnDragDropped((DragEvent event) ->{
Dragboard db = event.getDragboard();
((VBox) sp.getContent()).getChildren().add(new Label(db.getString()));
scrolltimeline.stop();
event.setDropCompleted(true);
dropped = true;
});
sp.setOnDragOver((DragEvent event) ->{
event.acceptTransferModes(TransferMode.MOVE);
});
sp.setOnScroll((ScrollEvent event)-> {
scrolltimeline.stop();
});
sp.setOnMouseClicked((MouseEvent)->{
System.out.println(scrolltimeline.getStatus());
});
}
private void dragScroll() {
ScrollBar sb = getVerticalScrollbar();
if (sb != null) {
double newValue = sb.getValue() + scrollVelocity;
newValue = Math.min(newValue, 1.0);
newValue = Math.max(newValue, 0.0);
sb.setValue(newValue);
}
}
private ScrollBar getVerticalScrollbar() {
ScrollBar result = null;
for (Node n : sp.lookupAll(".scroll-bar")) {
if (n instanceof ScrollBar) {
ScrollBar bar = (ScrollBar) n;
if (bar.getOrientation().equals(Orientation.VERTICAL)) {
result = bar;
}
}
}
return result;
}
public static void main(String[] args) {
launch(args);
}
}
JavaFX-8 has not public API to scroll a ScrollPane to a certain position (https://bugs.openjdk.java.net/browse/JDK-8102126) and cast your vote to get such API in.
A hack to scroll to a certain position in Java8 (who will break in Java9!) is to get the Skin of the ScrollPane who is of type ScrollPaneSkin and call the onTraverse-method there.

Creating multiple text fields in javafx

Is there anyway I can do a for loop to create multiple textfields. Say I want like 20 text fields...do I have to create them individually?
It's not really clear what your question is. Just write a for loop and create each TextField inside it.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TwentyTextFields extends Application {
#Override
public void start(Stage primaryStage) {
final int numTextFields = 20 ;
TextField[] textFields = new TextField[numTextFields];
VBox root = new VBox(5);
for (int i = 1; i <= numTextFields; i++) {
TextField tf = new TextField();
String name = "Text field "+i ;
tf.setOnAction(e -> {
System.out.println("Action on "+name+": text is "+tf.getText());
});
root.getChildren().add(tf);
textFields[i-1] = tf ;
}
Scene scene = new Scene(new ScrollPane(root), 250, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Add content in TitlePane

I want to add several TreeViews into TitlePana.
private static TitledPane pane = new TitledPane("Connections", null);
public static void initTree(String name)
{
pane.setContent(tree(name));
}
pane.setContent(<node>);
But when I add TreeView the new node always replaces the old one. Is this a design problem. Any idea is this a code problem?
Set the content of the titled pane to some layout pane, and then add the tree views to the layout pane.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TitledPane;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TreesInATitledPane extends Application {
#Override
public void start(Stage primaryStage) {
final TitledPane titledPane = new TitledPane();
titledPane.setText("Trees");
final VBox forest = new VBox(5);
final int NUM_TREES = 4 ;
for (int i=1; i<= NUM_TREES; i++) {
forest.getChildren().add(createRandomTree("Tree "+i));
}
titledPane.setContent(forest);
final BorderPane root = new BorderPane();
root.setTop(titledPane);
final Scene scene = new Scene(root, 300, 600);
primaryStage.setTitle("Can't see the forest for the trees");
primaryStage.setScene(scene);
primaryStage.show();
}
public TreeView<String> createRandomTree(String prefix) {
final Random rng = new Random();
List<TreeItem<String>> nodes = new ArrayList<>();
TreeItem<String> root = new TreeItem<>(prefix + " Root");
root.setExpanded(true);
nodes.add(root);
for (int i=1 ; i < 15; i++) {
final TreeItem<String> treeItem = new TreeItem<>(prefix + " Item "+i);
treeItem.setExpanded(true);
nodes.get(rng.nextInt(i)).getChildren().add(treeItem);
nodes.add(treeItem);
}
TreeView<String> tree = new TreeView<>(root);
return tree ;
}
public static void main(String[] args) {
launch(args);
}
}

how to ensure visible a node into a TitlePane which is into a scrollpane in javafx

I have a scrollPane which has various TitledPane into this, each TitledPane has various textfield's as children. When i navigate throughout textfield's using Tab key, the scrollpane do not autoscroll to ensure visible the control gaining focus. This is a program demonstrating the problem.
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ScrollPaneEnsureVisible extends Application {
private static final Random random = new Random();
#Override
public void start(Stage primaryStage) {
VBox root = new VBox();
ScrollPane scrollPane = new ScrollPane();
Pane content = new Pane();
scrollPane.setContent(content);
for (int i = 0; i < 3; i++) {
TitledPane pane = new TitledPane();
AnchorPane paneContent = new AnchorPane();
for (int j = 0; j < 2; j++) {
TextField text = new TextField();
paneContent.getChildren().add(text);
text.setLayoutX(8);
text.setLayoutY(j*30);
}
pane.setCollapsible(false);
pane.setContent(paneContent);
pane.setLayoutY(i*100);
content.getChildren().add(pane);
}
root.getChildren().add(scrollPane);
scrollPane = new ScrollPane();
content = new Pane();
scrollPane.setContent(content);
for (int i = 0; i < 3; i++) {
TitledPane pane = new TitledPane();
AnchorPane paneContent = new AnchorPane();
pane.setContent(paneContent);
pane.setPrefWidth(200);
pane.setPrefHeight(80);
pane.setCollapsible(false);
pane.setLayoutY(i*100);
content.getChildren().add(pane);
}
root.getChildren().add(scrollPane);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) { launch(); }
}
For the second titledpane all is fine, but for first not. i need to sensure visible (autoscroll the scrollpane) that controls when they gain focus.

Resources