How do I scroll to a certain component in Java FX? - javafx

I need to scroll down to a certain label component in Java FX. How do I do that ? I have ids attached to label component.
<ScrollPane>
<VBox fx:id="menuItemsBox">
<Label text="....."/>
<Label text="....."/>
<Label text="....."/>
....
<Label text="....."/>
</VBox>
</ScrollPane>

You can set the scrollPane's vValue to the Node's LayoutY value. Since the setVvlaue() accepts value between 0.0 to 1.0, you need to have computation to range your input according to it.
scrollPane.setVvalue(/*Some Computation*/);
This must be set after the stage's isShowing() is true.
Complete Example
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ScrollPane pane = new ScrollPane();
VBox box = new VBox();
IntStream.range(1, 10).mapToObj(i -> new Label("Label" + i)).forEach(box.getChildren()::add);
pane.setContent(box);
Scene scene = new Scene(pane, 200, 50);
primaryStage.setScene(scene);
primaryStage.show();
// Logic to scroll to the nth child
Bounds bounds = pane.getViewportBounds();
// get(3) is the index to `label4`.
// You can change it to any label you want
pane.setVvalue(box.getChildren().get(3).getLayoutY() *
(1/(box.getHeight()-bounds.getHeight())));
}
}

Related

How to make a GridPane right of BorderPane closeable?

I have code in the form:
<BorderPane>
...
<right>
<GridPane>
...
</GridPane>
</right>
...
</BorderPane>
Obviously, now the GridPane takes a big space right of my BorderPane. What I'd like to do is add a button (or another element) that minimizes and maximizes the GridPane, so it's only fully in the view of the user when it is really needed. How can I easily achieve this?
You can do what you want by setting the Visible and Managed properties of your GridPane off and on. The centre of the BorderPane will automatically expand to take over the entire width of the BorderPane. "Managed" controls whether or not the layout manager will leave space for the node, so if you just turn Visible off, then you'll have an unused area the size of your GridPane on the right. The following code demonstrates it, I put the buttons in a VBox with a border around it so that you can see how it expands:
public class ResizeRight extends BorderPane {
public ResizeRight() {
Button openButton = new Button("Open");
Button closeButton = new Button("Close");
GridPane gridPane = new GridPane();
gridPane.addRow(0, new Text("This is just some text"));
gridPane.addRow(1, new Text("This is just some more text"));
VBox vbox = new VBox(10, openButton, closeButton);
vbox.setBorder(new Border(new BorderStroke(Color.BLACK,
BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT)));
setCenter(vbox);
setRight(gridPane);
setPadding(new Insets(10));
openButton.setOnAction(evt -> {
gridPane.setVisible(true);
gridPane.setManaged(true);
});
closeButton.setOnAction(evt -> {
gridPane.setVisible(false);
gridPane.setManaged(false);
});
}
}
Run it from something like this:
public class Sample1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new ResizeRight(), 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}

Java FX Set Margin

This simple example creates an area with 2 rectangle areas marked in red.
I want to push the right area by n pixels towards the right using the VBox margin method but nothing happens. Why is margin not working in this example ? It works in scene builder though..
public class LayoutContainerTest extends Application {
#Override
public void start(Stage primaryStage) {
VBox areaLeft = new VBox();
areaLeft.setStyle("-fx-background-color: red;");
areaLeft.setPrefSize(100, 200);
VBox areaMiddle = new VBox();
areaMiddle.setStyle("-fx-background-color: red;");
areaMiddle.setPrefSize(100, 200);
VBox areaRight = new VBox();
areaRight.setStyle("-fx-background-color: red;");
areaRight.setPrefSize(100, 200);
VBox.setMargin(areaRight, new Insets(0,0,0,50));
HBox root = new HBox(areaLeft,areaMiddle,areaRight);
root.setSpacing(30);
Scene scene = new Scene(root, 600, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
You are using VBox.setMargin() but should be using the HBox method instead:
HBox.setMargin(areaRight, new Insets(0, 0, 0, 50));
The reason being, you are setting the margins for the children of a VBox, while areaRight is the child of a HBox. If you were to use your code and then place areaRight into a VBox, you would be seeing the margin as expected.
You mention that it works in SceneBuilder, but if you inspect the actual FXML code, you'll see that SceneBuilder is correctly using HBox:
<VBox fx:id="areaRight" prefHeight="200.0" prefWidth="100.0" style="-fx-background-color: red;">
<HBox.margin>
<Insets left="50.0" />
</HBox.margin>
</VBox>

JavaFX: Dynamically added VBox does not show up

I'm working on a project that requires a "tabled" representation of VBoxes. My hierarchical layout of the application is GridPane -> VBox (in one of the Cells) -> VBoxes (that display different datasets on top of each other) -> Data. I have two Scenes.
Data is displayed on Scene 1. The user can add data through a form and by clicking a button on Scene 2. Then, the added data should be displayed below the existing data as a VBox within the parent-VBox on Scene 1 again.
Here is the code that will make it clear:
My Scene 1 .fxml file looks the following (Simplified):
<GridPane fx:id="grid" fx:controller="application.Controller">
[here: ColumnConstraints]
<children>
<VBox fx:id="parentBox" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<Button fx:id="goToScene2" text="+" onAction="#goToScene2"/>
</children>
</GridPane>
Scene 2 just has a button and a TextField:
<GridPane fx:id="grid" fx:controller="application.AddDataController">
[here: ColumnConstraints]
<children>
<Button fx:id="addData" text="add" onAction="#bAddData"/>
<TextField fx:id="data"/>
</children>
</GridPane>
My Scene 1 controller (controller) looks like this:
public class Controller implements Initializable {
#FXML Button goToScene2;
#FXML VBox parentBox;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void addData(String s) {
Label lbl = new Label(s);
VBox dataBox = new VBox();
dataBox.setPadding(new Insets(15, 5, 15, 5));
dataBox.setSpacing(5);
dataBox.setMaxHeight(80);
dataBox.getChildren().add(lbl);
parentBox.getChildren().add(dataBox);
}
}
This is designed as it is because the dataBox contains more elements than the label, but that doesn't seem relevant to me in this context.
My Scene 2 controller (addDataController) looks like this:
#FXML Button addData;
#FXML TextField data;
#FXML protected void bAddData(){
String content = data.getText();
FXMLLoader fxmlLoader = new FXMLLoader();
Pane p = fxmlLoader.load(getClass().getResource("scn1.fxml").openStream());
Controller cont = (Controller) fxmlLoader.getController();
cont.addData(content);
}
So, when one clicks on the Add-Data-Button in Scene 2, the triggered method passes the entered data to the Controller of Scene 1. This is because the new data should be displayed in Scene 1 now.
I feel like the logic does not work (edited here), because when I ask for
System.out.println(parentBox.getChildren().size();
before and after the data was added, it always has one single Child, even though it should have one more...
If I artificially fill a String-Array and move everything from addData to(String s) to Initialize(...), it does work and the data shows up as VBoxes in the parent-VBox.
I didn't post the main class, because loading Controllers and Scene change is not an issue.
Thank you all very very much for your help! :)
Just to provide more detailed information, together with an idea, that I don't know how to implement.
This is my main class:
#Override
public void start(Stage primaryStage) {
currentStage = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("Scene1.fxml"));
scene1 = new Scene(root);
}
Could I load the controller at this point and make a getter that passes the Controller? So I'd only have one single instance of it throughout the program.
I tried inserting:
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.load(getClass().getResource("Scene1.fxml").openStream());
Controller controller = (Controller) fxmlLoader.getController();
after the
Parent root ...
line. But that's loading the .xml file twice. How can I nicely connect both parts?
Thanks so much for you patience!
EDIT:::::::::
The following code worked for me:
#Override
public void start(Stage primaryStage) {
currentStage = primaryStage;
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Scene1.fxml"));
Parent root =(Parent) fxmlLoader.load();
controller = (Controller) fxmlLoader.getController();
}
Now, I can ask the main class for controller and it always passes that single instance.
Thank you for pointing me to the right direction! :)

JavaFX: how to create slide in animation effect for a pane (inside a transparent stage)

I would like some guidelines on how to implement a slide in transition for a pane when user presses a button, just like Material Design does it for sliding menus.
This is a video link that illustrates my need.
I tried ScaleTransition, TranslateTransition, but they didn't do the trick.
The way I'm trying to implement it is not efficient.
// swipeMenuPane is builded in SceneBuilder and it is hidden,
// opacity = 0.0 and setX() = -getPrefWidth();
#FXML AnchorPane swipeMenuPane;
#FXML Button menuButton;
menuButton.setOnMouseClicked(e-> {
swipeMenuPane.setOpacity(1.0);
swipeTransition.play()
});
TranslateTransition swipeTransition = new TranslateTransition();
swipeTransition.setNode(swipeMenuPane);
swipeTransition.setDuration(Duration.millis(500));
swipeTransition.setToX(swipeMenuPane.getPrefWidth());
--- UPDATE ---
Here is the sample Gluon Application downloaded from here. It's a gradle project and I modified it to display a button instead of the default label.
I want to shrink the AnchorPane when user clicks the button.
What am I missing?
package com.helloworld;
import com.gluonhq.charm.glisten.animation.ShrinkExpandAnimation;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
ShrinkExpandAnimation anim;
#Override
public void start(Stage stage) {
Button btn = new Button("Click Me!");
btn.setOnMouseClicked(e-> {
System.out.println("swiping...");
anim.play();
});
AnchorPane pane = new AnchorPane();
pane.setStyle("-fx-background-color: coral");
pane.getChildren().add(btn);
// false to shrink or true to expand
anim = new ShrinkExpandAnimation(pane, false);
Scene scene = new Scene(new StackPane(pane), 640, 480);
stage.setScene(scene);
stage.show();
}
}
--- UPDATE 2 ---
I managed to implement something similar to what I want using native JavaFX API and no external libraries.
Although, I ran into some issues.
Shrinking an AnchorPane does NOT shrink/move ANY of its children nodes, because they stay in their layout positions.
Shrinking any other Pane except AnchorPane DOES shrink/move its children nodes except from ImageView nodes.
The next two images illustrate the 1st issue I ran into.
This is an AnchorPane (with coral color at its full width; expanded) inside an AnchorPane (root pane with grey color).
And this is what happens when you click Menu button to shrink/hide it. As you can see the coral-colored pane got shrinked/hidden, but NOT its nodes (Label, ImageView)
I post the whole code to reproduce the issue yourself:
public class SwipeMenuDemo extends Application {
AnchorPane swapPane;
Button btnMenu;
boolean isExpanded = true;
#Override
public void start(Stage stage) {
Label swapPaneLabel = new Label("Expandable Pane");
swapPaneLabel.setMinWidth(0);
ImageView swapPaneImage = new ImageView("http://vignette1.wikia.nocookie.net/jfx/images/5/5a/JavaFXIsland600x300.png");
swapPaneImage.setLayoutY(100);
Label rootPaneLabel = new Label("Root Pane");
rootPaneLabel.setStyle("-fx-font-size: 60;");
rootPaneLabel.setLayoutX(180);
rootPaneLabel.setLayoutY(180);
swapPane = new AnchorPane();
swapPane.setPrefSize(640, 440);
swapPane.setMinWidth(0);
swapPane.setLayoutY(40);
swapPane.setStyle("-fx-background-color: coral; -fx-font-size: 52;");
swapPane.getChildren().addAll(swapPaneImage, swapPaneLabel);
btnMenu = new Button("Menu");
btnMenu.setLayoutX(5);
btnMenu.setLayoutY(5);
btnMenu.setOnMouseClicked(e -> {
if (isExpanded) hideSwapPane().play();
else showSwapPane().play();
});
Button btnClose = new Button("Close");
btnClose.setLayoutX(590);
btnClose.setLayoutY(5);
btnClose.setOnMouseClicked(e -> Platform.exit());
AnchorPane rootPane = new AnchorPane();
rootPane.setStyle("-fx-background-color: grey;");
rootPane.getChildren().addAll(btnMenu, btnClose, rootPaneLabel, swapPane);
Scene scene = new Scene(rootPane, 640, 480);
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.show();
}
private Animation hideSwapPane() {
btnMenu.setMouseTransparent(true);
Animation collapsePanel = new Transition() {
{
setCycleDuration(Duration.millis(2500));
}
#Override
protected void interpolate(double fraction) {
swapPane.setPrefWidth(640 * (1.0 - fraction));
}
};
collapsePanel.setOnFinished(e-> {
isExpanded = false;
btnMenu.setMouseTransparent(false);
});
return collapsePanel;
}
private Animation showSwapPane() {
btnMenu.setMouseTransparent(true);
final Animation expandPanel = new Transition() {
{
setCycleDuration(Duration.millis(2500));
}
#Override
protected void interpolate(double fraction) {
swapPane.setPrefWidth(640 * fraction);
}
};
expandPanel.setOnFinished(e-> {
isExpanded = true;
btnMenu.setMouseTransparent(false);
});
return expandPanel;
}
}
--- UPDATE 3 ---
I modified the code Felipe Guizar Diaz provide me, according to my needs, since I want a dropshadow effect on my transparent stage window.
When I click the menu button to show the left pane it shows up in front of the shadow. Even though in SceneBuilder I've placed the StackPane with the dropshadow effect in front of all nodes.
This is the "artifact" when I press to show the menu and starts playing the open transition...
How can I fix it?
I am the author of the example video. I'll repeat the response that I did in the video comments:
"you should think of it as a navigation drawer in android, the navigation drawer in JavaFX would be an AnchorPane with 2 children, first a StackPane that is equivalent to a FrameLayout working as our main content, where transitions of pane are made depending of the chosen item from the left side menu, and ultimately a ListView as our left side menu with a negative translateX that equals to the Listview width. Then when the user presses a button you must play an animation that sest the value of translateX to 0."
You shouldn't use prefWidth() in the interpolate method of the two animations (collapse Panel, expand Pane), because the children don't resize, the margin arrangement is the only constraint that the AnchorPane has.
Check out this example that I did.
https://github.com/marconideveloper/leftsidemenuexample
public class FXMLDocumentController implements Initializable {
#FXML
private Button menu;
#FXML
private AnchorPane navList;
#Override
public void initialize(URL url, ResourceBundle rb) {
//navList.setItems(FXCollections.observableArrayList("Red","Yellow","Blue"));
prepareSlideMenuAnimation();
}
private void prepareSlideMenuAnimation() {
TranslateTransition openNav=new TranslateTransition(new Duration(350), navList);
openNav.setToX(0);
TranslateTransition closeNav=new TranslateTransition(new Duration(350), navList);
menu.setOnAction((ActionEvent evt)->{
if(navList.getTranslateX()!=0){
openNav.play();
}else{
closeNav.setToX(-(navList.getWidth()));
closeNav.play();
}
});
}
}
Here is the fxml:
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" id="AnchorPane" prefWidth="500" prefHeight="500" fx:controller="leftslidemenusample.FXMLDocumentController">
<children>
<ToolBar AnchorPane.topAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" minHeight="56.0" >
<Button text="menu" fx:id="menu" />
</ToolBar>
<StackPane fx:id="mainContent" style="-fx-background-color:rgba(0,0,0,0.30)" AnchorPane.bottomAnchor="0.0" AnchorPane.topAnchor="56.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" >
<children>
</children>
</StackPane>
<AnchorPane fx:id="navList" style="-fx-background-color:white" AnchorPane.topAnchor="56.0" AnchorPane.bottomAnchor="0.0" prefWidth="180.0" translateX="-180" >
<children>
<Label text="left side menu"/>
</children>
</AnchorPane>
</children>
</AnchorPane>
Finally, I get it done.
They key features are:
Set the shadow effect on the root pane using a custom pane that drows a shadow outside its layout bounds and crops its inside content, so it has a transparent content.
The root pane can be anything else than AnchorPane.
Clip the pane that holds the main content to its inside bounds.
Below is a snippet of the source code that controls these effects:
#Override
public void initialize(URL url, ResourceBundle rb) {
...
Rectangle clip = new Rectangle(rootPaneWidth, rootPaneHeight);
rootPane.setClip(clip);
rootPane.getChildren().add(setupShadowPane());
}
private Pane setupShadowPane() {
Pane shadowPane = new Pane();
shadowPane.setStyle(
"-fx-background-color: white;" +
"-fx-effect: dropshadow(gaussian, black, " + shadowSize + ", 0, 0, 0);" +
"-fx-background-insets: " + shadowSize + ";"
);
Rectangle innerBounds = new Rectangle();
Rectangle outerBounds = new Rectangle();
shadowPane.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
innerBounds.relocate(newBounds.getMinX() + shadowSize, newBounds.getMinY() + shadowSize);
innerBounds.setWidth(newBounds.getWidth() - shadowSize * 2);
innerBounds.setHeight(newBounds.getHeight() - shadowSize * 2);
outerBounds.setWidth(newBounds.getWidth());
outerBounds.setHeight(newBounds.getHeight());
Shape clip = Shape.subtract(outerBounds, innerBounds);
shadowPane.setClip(clip);
});
return shadowPane;
}
Slide Menu semi opened
Slide Menu fully opened
Slide Menu closed

JavaFx : Disable Divider

I have a JavaFX application with a SplitPane. I want to disable the Divider on SplitPane, so it would not be possible to change its position when application is running. How can I do this?
There's no API for that, so once the scene is shown we have to use a lookup function to find the node by its id. In this case, the Divider has this id: split-pane-divider.
Once we find the node, we set it transparent to mouse events:
#Override
public void start(Stage primaryStage) {
final SplitPane splitPane = new SplitPane();
splitPane.setOrientation(Orientation.HORIZONTAL);
splitPane.setDividerPositions(new double[]{0.5});
splitPane.getItems().add(new StackPane(new Label("Left")));
splitPane.getItems().add(new StackPane(new Label("Right")));
Scene scene = new Scene(splitPane, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
splitPane.lookupAll(".split-pane-divider").stream()
.forEach(div -> div.setMouseTransparent(true) );
}
None of the above posts worked for me. I have found this solution which worked for me:
This code works for the case when your splitPane is divided in the middle and has only one divider, therefore the divider's position is set to 0.5. If you don't know the position of the divider, you can get it by divider.getPosition();.
Divider divider = splitPane.getDividers().get(0);
divider.positionProperty().addListener(new ChangeListener<Number>()
{
#Override
public void changed( ObservableValue<? extends Number> observable, Number oldvalue, Number newvalue )
{
divider.setPosition(0.5);
}
});
This code is in the initialize() method of the GUI Controller class.
Set mouseTransparent="true" of SplitPane in Fxml file.
<SplitPane dividerPositions="0.5" mouseTransparent="true" prefHeight="652.0" prefWidth="858.0">
You can also modify the Skin class for SplitPane. Just copy the code from GrepCode for SplitPaneSkin (available here) and remove the MouseListeners in method initializeDivderEventHandlers() and also the setCursor calls in method setGrabberStyle() and then you can't resize the pane by dragging the divider ;-) At the end you only have to set the skin to the SplitPane by calling setSkin.

Resources