Add a node into a split pane divider - javafx

I'm trying to find a solution of how to add a label into a split pane divider or at least create the illusion of that. My approach was to add three panes into the split pane and drag the middle pane along with the two divider. The problem is that on rapid mouse movements the middle pane gets bigger, but if I define a max height then the dragging doesn't work at all.
My question is if there is a way of directly inserting a label onto the divider or if somebody has a better solution as mine to accomplish something like this.
MWE:
Class
public class MainSplit extends Application {
#FXML
private SplitPane splitPane;
#FXML
private Pane dividerPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/splitpane.fxml"));
loader.setController(this);
Parent root = loader.load();
AtomicReference<Double> start = new AtomicReference<>((double) 0);
dividerPane.setOnMousePressed(event -> {
start.set((1.0 / splitPane.getScene().getHeight()) * event.getSceneY());
});
dividerPane.setOnMouseDragged(event -> {
double p = (1.0 / splitPane.getScene().getHeight()) * event.getSceneY();
double diff = p - start.get();
start.set(p);
double[] d = splitPane.getDividerPositions();
splitPane.setDividerPosition(0, d[0] + diff);
splitPane.setDividerPosition(1, d[1] + diff);
});
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
FXML
<SplitPane fx:id="splitPane" dividerPositions="0.36, 0.43" orientation="VERTICAL" prefHeight="400.0" prefWidth="400.0"
xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane/>
<Pane fx:id="dividerPane" minHeight="-Infinity" prefHeight="20.0" style="-fx-background-color: #4545;">
<cursor>
<Cursor fx:constant="V_RESIZE"/>
</cursor>
</Pane>
<AnchorPane/>
</SplitPane>

Related

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>

How to change parent of child fxml into other parent?

Can I change a Node's parent to another parent in FXML?
Suppose a parent named stackcon with a child node.
I now want to move this child node to different parent, e.g., named stackmain.
Is this possible? If yes, please give me a link or a example code.
This is just one of many ways how to do this.
MainView.fxml, just a simple view containing a button, and on button click the label should be moved from right to left and vice-versa (see the onMousePressed declaration):
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<left>
<VBox fx:id="lefty">
<Label fx:id="switchy" text="Switching text"></Label>
</VBox>
</left>
<center>
<Button fx:id="switchBtn" text="Switch" onMousePressed="#switchButtonPressed"></Button>
</center>
<right>
<VBox fx:id="righty">
</VBox>
</right>
</fx:root>
The controller MainView.java with its switchButtonPressed event handler:
public class MainView extends BorderPane {
#FXML private VBox lefty;
#FXML private VBox righty;
#FXML private Label switchy;
public MainView() {
URL fxmlFile = MainView.class.getResource("MainView.fxml");
FXMLLoader fxmlLoader = new FXMLLoader(fxmlFile);
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
public void switchButtonPressed(MouseEvent mouseEvent) {
if(lefty.getChildren().contains(switchy)) {
lefty.getChildren().remove(switchy);
righty.getChildren().add(switchy);
} else {
righty.getChildren().remove(switchy);
lefty.getChildren().add(switchy);
}
}
}
You see, I just check on button click if the label is on the left side. If so, remove it on the left side and add it on the right side.
Analogous, if it is on the right side, remove it there and add it on the left side.
it may vary depending on type of the pane of stackmain & stackcon , i'm assuming that you use AnchorPane
but it is something like this
Node childNode = stackcon.getChildren().get(indexOfChild);
stackcon.getChildren().remove(indexOfChild);
stackmain.getChildren().add(childNode);

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 cannot access Scene elements

I am new to javafX, have run through various tutorials, and googled extensively. I am attempting to write a multiscreen javaFX program whose second screen has a split pane, whose right pane should display a grid and a button. The framework I am using for multiple screens is copied form Angela Caiedo's MultipleScreens Framework tutorial ( https://github.com/acaicedo/JFX-MultiScreen/tree/master/ScreensFramework) .
What works: The screens framework is successful in terms of flipping between my multiple screens.
My problem: I click the button on screen 1 to move to screen 2. Screen 2 successfully shows. On screen 2 I click a button to populate data in my vBox. The code in my controller class for screen 2 is unable to access the vbox (or any other defined containers on this screen).
My main class sets up the controller screens.
public class FieldMapCreator extends Application {
public static String mainID = "main";
public static String mainFile = "MainMapFXMLDocument.fxml";
public static String initialMapID = "initialMap";
public static String initialMapFile = "FXMLMapPicture.fxml";
#Override
public void start(Stage stage) throws Exception {
ScreensController mainContainer = new ScreensController();
mainContainer.loadScreen(FieldMapCreator.mainID, FieldMapCreator.mainFile);
mainContainer.loadScreen(FieldMapCreator.initialMapID, FieldMapCreator.initialMapFile);
mainContainer.setScreen(FieldMapCreator.mainID);
Group root = new Group();
root.getChildren().addAll(mainContainer);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
I get to the second screen by clicking a button on screen 1. Button1 code looks like:
#FXML
private void initialMapButtonAction(ActionEvent event) {
myController.setScreen(FieldMapCreator.initialMapID);
}
Screen 2 has a button that when clicked executes a method that creates data for the vbox. The fxml for screen 2 is this:
<AnchorPane id="AnchorPane" fx:id="mainAnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FieldMapCreator.FXMLMapPictureController">
<children>
<SplitPane fx:id="mapSplitPane" dividerPositions="0.29797979797979796" layoutX="3.0" layoutY="7.0" prefHeight="400.0" prefWidth="600.0">
<items>
<AnchorPane fx:id="anchorPaneLeft" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Button fx:id="backButton" layoutX="14.0" layoutY="348.0" mnemonicParsing="false" onAction="#goToMainScreen" text="Back" />
</children>
</AnchorPane>
<AnchorPane fx:id="anchorPaneRight" minHeight="0.0" minWidth="0.0" prefHeight="364.0" prefWidth="401.0">
<children>
<Button fx:id="vBoxShowMap" layoutX="24.0" layoutY="346.0" mnemonicParsing="false" onAction="#buildMapFromNurseries" prefHeight="26.0" prefWidth="91.0" text="show map" />
<VBox fx:id="mapVBox" layoutX="24.0" layoutY="24.0" onMouseClicked="#vBoxMouseClicked" prefHeight="200.0" prefWidth="309.0" />
</children></AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
The code that fails is in the "showInitialMap()" method of the controller class for screen 2. When executed, the last line of this method "mapVBox.getChildren().add(root)", results in a null pointer exception. I expected this to be resolved via the fxml injection. I also tried the commented out code to retrieve "mapVBox" using scene lookup, but it also results in a null pointer exception. The controller code is below:
public class FXMLMapPictureController implements Initializable, ControlledScreen {
ScreensController myController;
static MyNode[][] mainField ;
#FXML
private AnchorPane mainAnchorPane;
#FXML
private static SplitPane mapSplitPane;
#FXML
private AnchorPane anchorPaneLeft;
#FXML
private static AnchorPane anchorPaneRight;
#FXML
private static VBox mapVBox;
#FXML
private Button vBoxShowMap;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// Should something be in here ??
}
public void goToMainScreen() {
myController.setScreen(FieldMapCreator.mainID);
}
public void showInitialMap() {
int row = userFieldMap.getNumRows();
int col = userFieldMap.getNumCols();
double gridWidth = 309/col;
double gridHeight = 200/row;
Group root = new Group();
// initialize the field
for (int idx = 0; idx < col; idx++) {
for (int jdx = 0; jdx < row; jdx++) {
// create plot
Plot plot = new Plot(idx, jdx, "Plot" + idx + "/" + jdx);
// Create node to hold the Plot
MyNode node = new MyNode(idx*gridWidth, jdx*gridHeight, gridWidth, gridHeight, plot);
// add plot to group
root.getChildren().add(node);
}
}
//Scene myScene = myController.getScene();
//VBox mapVBox = (VBox)myScene.lookup("#mapVBox");
mapVBox.getChildren().add(root);
}
#Override
public void setScreenParent(ScreensController screenParent) {
myController = screenParent;
}
Is there something missing from the initialize() method? I'm wondering if I am not setting something correctly when I change screens. I am able to access the initial screen's containers without problem from all screen 1 methods including the initialize class. When I attempt the same in screen 2, I get the message "screen hasn't been loaded !!!"
Thanks for any help you can provide.
You are using static fields as FXML injection targets which don't work in Java 8 and should not have been implemented to work in any other version, as static UI elements just don't make sense. Here's a bit more detailed answer. Having resorted to a static field most of the time means the software design of the application needs to be reconsidered.

Why won't a child window (child stage) contents auto-resize?

I'm working in NetBeans 8 with Java 8/JavaFX.
I have an application that starts a main stage based on an fxml file.
There is a menu option for a user to bring up a second stage on request. The function that opens the window looks like this:
#FXML
private void openChildWindow(ActionEvent event) throws Exception {
Group root = new Group();
Stage stage = new Stage();
AnchorPane frame = FXMLLoader.load(getClass().getResource("second.fxml"));
root.getChildren().add(frame);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
The content of second.fxml looks like this (after all the import statements):
<AnchorPane id="AnchorPane" prefHeight="680.0" prefWidth="1020.0" xmlns:fx="http://javafx.com/fxml/1" >
<stylesheets><URL value="#css/mycss.css" /></stylesheets>
<children>
<TabPane AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="10.0"
AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="0.0" >
<Tab text="A" closable="false"></Tab>
</TabPane>
</children>
</AnchorPane>
The problem: when I click on the corner of the second window to drag and resize it, the contents are not resizing. What am I missing or what do I need so that it will auto-resize?
Group is not directly resizable as stated in its javadoc. So it does not being resized by the scene while the window is resized from the corner. You can use the subclasses of Pane instead of. For example try with
VBox root = new VBox();
Instead of using a Group, please put your AnchorPane directly into your Scene
#FXML
private void openChildWindow(ActionEvent event) throws Exception {
Stage stage = new Stage();
AnchorPane frame = FXMLLoader.load(getClass().getResource("second.fxml"));
Scene scene = new Scene(frame );
stage.setScene(scene);
stage.show();
}
A Group cannot be resized, "A Group will take on the collective bounds of its children and is not directly resizable." http://docs.oracle.com/javafx/2/api/javafx/scene/Group.html
Replace Group with a different of layout such as StackPane, or BorderPane.
I struggled with the same thing, if we only had read the documentation. :D

Resources