How to make a VBox with animated "close up"? - animation

I have built a form which has a VBox inside of a VBox. I want to make the internal VBox "close up" from the bottom to the top transitionally.
Afterwards, the next elements from the outer VBox should move up to fill the empty space, like when you remove items from a VBox.
How can I achieve this?

You can try next approach: use clipping to hide "disappearing" content and control height of the inner VBox during animation:
public class ShrinkingVBox extends Application {
private static class SmartVBox extends Region {
private Rectangle clipRect = new Rectangle();
private VBox content = new VBox();
public SmartVBox() {
setClip(clipRect);
getChildren().add(content);
}
// we need next methods to adjust our clipping to inner vbox content
#Override
protected void setWidth(double value) {
super.setWidth(value);
clipRect.setWidth(value);
}
#Override
protected void setHeight(double value) {
super.setHeight(value);
clipRect.setHeight(value);
}
// here we will do all animation
public void closeup() {
setMaxHeight(getHeight());
// animation hides content by moving it out of clipped area
// and reduces control height simultaneously
Timeline animation = TimelineBuilder.create().cycleCount(1).keyFrames(
new KeyFrame(Duration.seconds(1),
new KeyValue(content.translateYProperty(), -getHeight()),
new KeyValue(maxHeightProperty(), 0))).build();
animation.play();
}
protected ObservableList<Node> getContent() {
return content.getChildren();
}
}
#Override
public void start(Stage primaryStage) {
VBox outer = new VBox();
final SmartVBox inner = new SmartVBox();
//some random content for inner vbox
inner.getContent().addAll(
CircleBuilder.create().radius(25).fill(Color.YELLOW).build(),
CircleBuilder.create().radius(25).fill(Color.PINK).build());
// content for outer vbox, including inner vbox and button to run animation
outer.getChildren().addAll(
RectangleBuilder.create().width(50).height(50).fill(Color.GRAY).build(),
inner,
RectangleBuilder.create().width(50).height(50).fill(Color.RED).build(),
RectangleBuilder.create().width(50).height(50).fill(Color.BLUE).build(),
ButtonBuilder.create().text("go").onAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
inner.closeup();
}
}).build());
primaryStage.setScene(new Scene(new Group(outer)));
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}

Related

JavaFX more Scenes

Hi Guys i build a GUI and on this GUI is a Button and when I press the Button a second GUI appears, on the second GUI is also a Button and when i press the Button it goes back
GU1
btn.setOnAction(new EventHandler <ActionEvent>(){
public void handle(ActionEvent arg0) {
try {
new GUI2().start(primaryStage);
} catch (Exception e) {
e.printStackTrace();
}
}
});
My Questions!
Is GUI1 still running when i press the Button?
GUI2
btn.setOnAction(new EventHandler <ActionEvent>(){
public void handle(ActionEvent arg0) {
try {
//back to the main menu
new GUI1().start(primaryStage);
} catch (Exception e) {
e.printStackTrace();
}
}
});
When i press the Button, does it go back to the same instance when beginning the program? Or make it a new Instance witch has the same look, and use it more RAM;
How should it works, when i want to open the second GUI in a external Window
When i press the Button, does it go back to the same instance when beginning the program?
No, a new instance is created based on your code new GUI2().start(primaryStage);. Always remember that thenew keyword ALWAYS creates a new object.
How should it works, when i want to open the second GUI in a external Window?
There are lots of ways to do this.
Method 1
If it happen that you created two applications, both extending the Application class, this method should work.
public class MultiWindowFX {
private static final Logger logger = Logger.getGlobal();
public static class GUI1 extends Application {
private final Button buttonShowGUI2;
private final GUI2 gui2;
public GUI1() {
buttonShowGUI2 = new Button("Show GUI 2");
gui2 = new GUI2();
}
public Button getButtonShowGUI2() {
return buttonShowGUI2;
}
#Override
public void start(Stage primaryStage) throws Exception {
//add an action event on GUI2's buttonShowGUI1 to send front GUI1
gui2.getButtonShowGUI1().setOnAction(gui2ButtonEvent -> {
if (primaryStage.isShowing()) primaryStage.toFront();
else primaryStage.show();
});
//button with action to show GUI 2
buttonShowGUI2.setOnAction(actionEvent -> {
try {
if (gui2.getPrimaryStage() == null) gui2.start(new Stage());
else gui2.getPrimaryStage().toFront();
} catch (Exception ex) {
logger.log(Level.SEVERE, null, ex);
}
});
//set scene and its root
Pane root = new StackPane(buttonShowGUI2);
Scene stageScene = new Scene(root, 400, 250);
//set stage
primaryStage.setScene(stageScene);
primaryStage.centerOnScreen();
primaryStage.setTitle("GUI 1");
primaryStage.show();
}
public static void launchApp(String... args) {
GUI1.launch(args);
}
}
public static class GUI2 extends Application {
private Stage primaryStage;
private final Button buttonShowGUI1;
public GUI2() {
buttonShowGUI1 = new Button("Show GUI 1");
}
public Button getButtonShowGUI1() {
return buttonShowGUI1;
}
public Stage getPrimaryStage() {
return primaryStage;
}
#Override
public void start(Stage primaryStage) throws Exception {
//get stage reference
this.primaryStage = primaryStage;
//set scene and its root
Pane root = new StackPane(buttonShowGUI1);
Scene stageScene = new Scene(root, 400, 250);
//set stage
primaryStage.setScene(stageScene);
primaryStage.centerOnScreen();
primaryStage.setTitle("GUI 2");
primaryStage.show();
}
public static void launchApp(String... args) {
GUI2.launch(args);
}
}
public static void main(String... args) {
GUI1.launchApp(args);
}
}
Method 2
For me, this is the best approach especially if you want window ownership and modality works.
public class GUI1 extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Show GUI2");
btn.setOnAction(actionEvent -> {
//prepare gui2
Stage gui2Stage = createGUI2();
//set window modality and ownership
gui2Stage.initModality(Modality.APPLICATION_MODAL);
gui2Stage.initOwner(primaryStage);
//show
gui2Stage.show();
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 400, 250);
primaryStage.setTitle("GUI 1");
primaryStage.setScene(scene);
primaryStage.show();
}
private Stage createGUI2() {
Button btn = new Button();
btn.setText("Show GUI1");
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 150);
Stage gui2Stage = new Stage();
gui2Stage.setTitle("GUI 2");
gui2Stage.setScene(scene);
//add an action event to GUI2's button, which hides GUI2 and refocuses to GUI1
btn.setOnAction(actionEvent -> gui2Stage.hide());
return gui2Stage;
}
public static void main(String[] args) {
launch(args);
}
}
...and among other methods. Choose the approach that fits to your requirements.

Detect if object is over pane during transition

I'm trying to detect if an elements that I move using PathTransition are entering the space of Pane.
This is my code:
public class Demo extends Application {
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, 800, 600));
Rectangle rect = new Rectangle(5, 5, Color.BLACK);
root.getChildren().add(rect);
Path path = new Path();
path.getElements().add (new MoveTo (0, 350));
path.getElements().add (new LineTo(400, 350));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(5000));
pathTransition.setPath(path);
pathTransition.setNode(rect);
pathTransition.setAutoReverse(false);
AnchorPane pane = new AnchorPane();
pane.setPrefSize(250, 200);
pane.relocate(300, 300);
pane.setStyle("-fx-border-color: #000000;");
root.getChildren().add(pane);
pathTransition.play();
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Is there a way to bind an event to the pane object and when the rect goes over, it will detect it?
Try
BooleanBinding intersects = new BooleanBinding() {
{
this.bind(pane.boundsInParentProperty(), rect.boundsInParentProperty());
}
#Override
protected boolean computeValue() {
return pane.getBoundsInParent().intersects(rect.getBoundsInParent());
}
};
intersects.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
if (newValue) {
System.out.println("Intersecting");
} else {
System.out.println("Not intersecting");
}
}
});
If you are still using old (pre-Java 8) versions of Java, you will need to declare pane and rect as final.

Using one event handler for multiple actions

I was doing some homework today and I've accomplished all of the goals of the assignment, which I'm sure will get me full points.
In an earlier class, however, we used the same Event Handler for more than one action (in this example, you either type a color in the text field, or click a button to change the background color of the box).
I can't figure out how I would do that in this case... do I have to choose a Type in the constructor? If the first parameter could be a button or a textfield then I think that would help.
I'm just trying to figure out how to apply DRY (Don't Repeat Yourself), where ever I can.
public class ColorChooserApplication extends Application
{
#Override
public void start(Stage stage)
{
// Create all UI components
VBox backgroundBox = new VBox(10);
backgroundBox.setPadding(new Insets(10));
HBox topBox = new HBox(10);
HBox bottomBox = new HBox(10);
TextField colorPrompt = new TextField();
colorPrompt.setOnAction(new ColorHandler(colorPrompt, backgroundBox));
Button redButton = new Button("Red");
redButton.setOnAction(new ButtonHandler(redButton, backgroundBox));
Button whiteButton = new Button("White");
whiteButton.setOnAction(new ButtonHandler(whiteButton, backgroundBox));
Button blueButton = new Button("Blue");
blueButton.setOnAction(new ButtonHandler(blueButton, backgroundBox));
// Assemble
topBox.getChildren().add(colorPrompt);
bottomBox.getChildren().addAll(redButton, whiteButton, blueButton);
backgroundBox.getChildren().addAll(topBox, bottomBox);
backgroundBox.setAlignment(Pos.CENTER);
topBox.setAlignment(Pos.CENTER);
bottomBox.setAlignment(Pos.CENTER);
// Set scene and show
stage.setScene(new Scene(backgroundBox));
stage.show();
}
class ColorHandler implements EventHandler<ActionEvent>
{
TextField colorTf;
VBox bgVbox;
public ColorHandler(TextField colorTf, VBox bgVbox)
{
this.colorTf = colorTf;
this.bgVbox = bgVbox;
}
#Override
public void handle(ActionEvent event)
{
String color = colorTf.getText();
bgVbox.setStyle("-fx-background-color:" + color);
}
}
class ButtonHandler implements EventHandler<ActionEvent>
{
Button colorButton;
VBox bgVbox;
public ButtonHandler(Button colorButton, VBox bgVbox)
{
this.colorButton = colorButton;
this.bgVbox = bgVbox;
}
#Override
public void handle(ActionEvent event)
{
String color = colorButton.getText();
bgVbox.setStyle("-fx-background-color:" + color);
}
}
public static void main(String[] args)
{
launch(args);
}
}
If you're using Java 8, you can do
class ColorHandler implements EventHandler<ActionEvent> {
Supplier<String> colorSupplier ;
VBox bgVbox ;
public ColorHandler(Supplier<String> colorSupplier, VBox bgVbox) {
this.colorSupplier = colorSupplier ;
this.bgVbox = bgVbox ;
}
#Override
public void handle(ActionEvent event) {
String color = colorSupplier.get();
bgVbox.setStyle("-fx-background-color: "+color);
}
}
and then
colorPrompt.setOnAction(new ColorHandler(colorPrompt::getText, backgroundBox));
redButton.setOnAction(new ColorHandler(redButton::getText, backgroundBox));
Note that all you need to provide for the first parameter is some function that returns the correct string for use in the css. So you can do things like
whiteButton.setOnAction(new ColorHandler(() -> "#ffffff", backgroundBox));
blueButton.setOnAction(new ColorHandler(() -> "cornflowerblue", backgroundBox));
etc.

right click menu option in the tree root node

I want right click menu option in the tree root node(JavaFX). Could any one help me on this.
TreeItem<String> root = new TreeItem<>(""+selectedDirectory);
root.setExpanded(true);
locationTreeView.setRoot(root);
root.getChildren().addAll(
new TreeItem<>("Item 1"),
new TreeItem<>("Item 2"),
new TreeItem<>("Item 3")
);
You can perform the desired behaviour in two steps:
Defining a custom TreeCell factory on your TreeView;
Attaching a context menu on the TreeCell of the root tree item.
The following code defines the custom TreeCell factory:
// defines a custom tree cell factory for the tree view
tree.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> arg0) {
// custom tree cell that defines a context menu for the root tree item
return new MyTreeCell();
}
});
And, here is the implementation of a custom tree cell that attaches a context menu for the root tree item:
class MyTreeCell extends TextFieldTreeCell<String> {
private ContextMenu rootContextMenu;
public MyTreeCell() {
// instantiate the root context menu
rootContextMenu =
ContextMenuBuilder.create()
.items(
MenuItemBuilder.create()
.text("Menu Item")
.onAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
System.out.println("Menu Item Clicked!");
}
}
)
.build()
)
.build();
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
// if the item is not empty and is a root...
if (!empty && getTreeItem().getParent() == null) {
setContextMenu(rootContextMenu);
}
}
}
The following example ilustrates the use of both, cell factory and custom cell, together:
public class TreeViewWithContextMenuOnRoot extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Tree with context menu on root");
TreeItem<String> rootItem = new TreeItem<String> ("Tree root");
rootItem.setExpanded(true);
for (int i = 1; i < 3; i++) {
TreeItem<String> item = new TreeItem<String> ("item" + i);
rootItem.getChildren().add(item);
}
final TreeView<String> tree = new TreeView<String> ();
tree.setRoot(rootItem);
// defines a custom tree cell factory for the tree view
tree.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> arg0) {
// custom tree cell that defines a context menu for the root tree item
return new MyTreeCell();
}
});
StackPane root = new StackPane();
root.getChildren().add(tree);
primaryStage.setScene(new Scene(root, 200, 100));
primaryStage.show();
}
private static class MyTreeCell extends TextFieldTreeCell<String> {
private ContextMenu rootContextMenu;
public MyTreeCell() {
// instantiate the root context menu
rootContextMenu =
ContextMenuBuilder.create()
.items(
MenuItemBuilder.create()
.text("Menu Item")
.onAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
System.out.println("Menu Item Clicked!");
}
}
)
.build()
)
.build();
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
// if the item is not empty and is a root...
if (!empty && getTreeItem().getParent() == null) {
setContextMenu(rootContextMenu);
}
}
}
}
You can take a look at the TreeView tutorial to see other uses and examples related to this JavaFX control.

JavaFX stop Timeline

I use FXML. I've created a button to stop/restart a live chart. For the animation I've used Timeline. I'would like to control it from the guiController (from an other class), but it is not working. How can I stop a Timeline from an other class?
Thank you!
FXML:
<Button id="button" layoutX="691.0" layoutY="305.0" mnemonicParsing="false" onAction="#btn_startmes" prefHeight="34.0" prefWidth="115.0" text="%start" />
guiController:
#FXML
private void btn_stopmes(ActionEvent event) {
MotionCFp Stopping = new MotionCFp();
Stopping.animation.stop();
}
MotionCFp.java:
#Override
public void start(final Stage stage) throws Exception {
else{
ResourceBundle motionCFp = ResourceBundle.getBundle("motionc.fp.Bundle", new Locale("en", "EN"));
AnchorPane root = (AnchorPane) FXMLLoader.load(MotionCFp.class.getResource("gui.fxml"), motionCFp);
final guiController gui = new guiController();
Scene scene = new Scene(root);
stage.setTitle(motionCFp.getString("title"));
stage.setResizable(false);
stage.setScene(scene);
root.getChildren().add(gui.createChart());
animation = new Timeline();
animation.getKeyFrames().add(new KeyFrame(Duration.millis(1000/60), new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
// 6 minutes data per frame
for(int count=0; count < 6; count++) {
gui.nextTime();
gui.plotTime();
animation.pause();
animation.play();
}
}
}));
animation.setCycleCount(Animation.INDEFINITE);
stage.show();
animation.play();
}
}
What you need is a reference in your controller to the original animation created in the start method of your application. This will allow you to code the button event handler in the controller to stop the animation.
The MotionCFp class can contain the code:
final FXMLLoader loader = new FXMLLoader(
getClass().getResource("gui.fxml"),
ResourceBundle.getBundle("motionc.fp.Bundle", new Locale("en", "EN"))
);
final Pane root = (Pane) loader.load();
final GuiController controller = loader.<GuiController>getController();
...
animation = new Timeline();
controller.setAnimation(animation);
And the GuiController class can contain the code:
private Timeline animation;
public void setAnimation(Timeline animation) {
this.animation = animation;
}
#FXML private void btn_stopmes(ActionEvent event) {
if (animation != null) {
animation.stop();
}
}
MotionCFp is your application class. You only need one instance of it. That instance is created by the the JavaFX launcher, you should never do new MotionCFp().
Please note that these kinds of questions are much easier to answer quickly and correctly if the code in the question is simple, complete, compilable and executable.

Resources