Duplicate the view of a component in javafx - javafx

I am looking for a way to duplicate just the view of a component in javafx. This duplicate will not have any of the mouse or key events, and will basically be like a canvas.
As long as this duplicate doesn't change, I can use the snapshot(...) method that nodes have. My problem is that I want this to be dynamic. For example, I have one pane which displays some sort of animation (which can depend on user input), and I want a second pane which show the exact same image (but cannot have in itself user input like mouse presses).
The reflection effect is very specific case of what I need. Is there any way to do it in general?
One way to do it (which I used so far) is to just create a second duplicate component and connect all its input to the first. The problem is that this is a lot of work for every component that I want to copy, and it cannot be done generically.
A second way is just to take a snapshot every time the original component changes and copy it to the duplicate. This should be the solution, but I think that there is supposed to be a more low level solution instead of listening to changes, create an Image duplicate and then update an ImageView.

You could use some kind of binding. This is a basic example
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.control.Separator;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
public class JavaFXTestingGround extends Application
{
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
Circle circle = new Circle(0, 200, 25, Color.BLUE);
Circle duplicateCircle = new Circle(25, Color.BLUE);
#Override
public void start(Stage primaryStage)
{
duplicateCircle.centerXProperty().bind(circle.centerXProperty());
duplicateCircle.centerYProperty().bind(circle.centerYProperty());
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1.0 / 40.0), (ActionEvent t) -> {
circle.setCenterX(circle.getCenterX() + 1);
}));
timeline.setCycleCount(Timeline.INDEFINITE);
circle.setOnMouseClicked((t) -> {
switch (timeline.getStatus()) {
case STOPPED:
timeline.play();
break;
case RUNNING:
timeline.stop();
break;
}
});
Pane root = new Pane(circle);
primaryStage.setTitle("Test");
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
Pane duplicateRoot = new Pane(duplicateCircle);
Stage duplicatStage = new Stage();
duplicatStage.setScene(new Scene(duplicateRoot, 400, 400));
duplicatStage.setX(primaryStage.getX() + primaryStage.getWidth() + 10);
duplicatStage.setY(primaryStage.getY());
duplicatStage.show();
}
}

Related

JavaFX Show second window after first window

I want open main window, and, after it, in moment, when it opened, open dialouge window (in which I choice some parameters), whithout clicking or typing anything. Dialouge window must open as such. Where I should write code, which open dialouge window?
You can use the Window.onShown property. The EventHandler is invoked for WINDOW_SHOWN events which, as one would expect, are fired once the Window has been shown. Here's a small example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.Window;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setOnShown(event -> showDialog(primaryStage));
primaryStage.setScene(new Scene(new StackPane(new Label("Hello, World!")), 600, 400));
primaryStage.setTitle("JavaFX Application");
primaryStage.show();
}
private void showDialog(Window owner) {
Alert alert = new Alert(AlertType.INFORMATION);
alert.initOwner(owner);
alert.setContentText("This is a dialog shown immediately after the window was shown.");
alert.show();
}
}

How to vertically center content of Alert/DialogPane?

I created an Alert that is undecorated and I want the content to be centered in the alert. However, there seems to be some padding at the bottom that I can not get rid of.
Here is an MCVE:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Alert alert = new Alert(Alert.AlertType.NONE);
DialogPane pane = new DialogPane();
HBox contentPane = new HBox(10);
contentPane.setPadding(new Insets(10));
contentPane.setAlignment(Pos.CENTER);
// Add progress indicator and label to contentPane
contentPane.getChildren().addAll(
new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS) {{
setPrefSize(30, 30);
}},
new Label("Loading ...")
);
// Add border to demonstate the lower padding
contentPane.setStyle("-fx-border-color: black");
pane.setPadding(new Insets(10));
pane.setStyle("-fx-border-color: black");
pane.setContent(contentPane);
alert.setDialogPane(pane);
alert.initStyle(StageStyle.UNDECORATED);
alert.showAndWait();
}
}
The result is this window:
How do I create an Alert or DialogPane without that gap at the bottom?
Using:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
private static Border createBorder(Paint stroke) {
return new Border(new BorderStroke(stroke, BorderStrokeStyle.SOLID,
null, new BorderWidths(2)));
}
#Override
public void start(Stage primaryStage) throws Exception {
ProgressIndicator indicator = new ProgressIndicator();
indicator.setPrefSize(30, 30);
Label label = new Label("Loading...");
label.setMinWidth(Label.USE_PREF_SIZE);
HBox content = new HBox(10, indicator, label);
content.setBorder(createBorder(Color.BLUE));
content.setPadding(new Insets(10));
content.setAlignment(Pos.CENTER);
Alert alert = new Alert(Alert.AlertType.NONE);
alert.initStyle(StageStyle.UNDECORATED);
alert.getDialogPane().getStylesheets()
.add(getClass().getResource("Main.css").toExternalForm());
alert.getDialogPane().setPadding(new Insets(10));
alert.getDialogPane().setContent(content);
alert.getDialogPane().setBorder(createBorder(Color.RED));
alert.show();
}
}
Where this is Main.css:
.dialog-pane .button-bar .container {
-fx-padding: 0px;
}
.dialog-pane:no-header .graphic-container {
-fx-padding: 0px;
}
Resulted in the following:
Some of the needed style-classes I got from here (JavaFX CSS Reference). However, I mostly figured this out from looking at modena.css (where they have the styles for dialog-pane).
If you don't want to use external CSS you can replace alert.getDialogPane().getStylesheets().add(...) with:
alert.getDialogPane().applyCss(); // Seems to stop the lookup calls
// from returning null
alert.getDialogPane()
.lookup(".button-bar")
.lookup(".container")
.setStyle("-fx-padding: 0px;");
alert.getDialogPane().lookup(".graphic-container").setStyle("-fx-padding: 0px;");
alert.show();
Update following your comments.
I initially tried this using Java 10.0.2 but I just tried using Java 8u181 and it still worked for me. I notice in your MCVE you are using a new DialogPane whereas I am using the DialogPane that initially comes with the Alert. Note that the applyCss() method will only work if the Node is a part of a Scene. Apparently the DialogPane is made part of a Scene as soon as it's part of the Alert. So one way you could avoid the NullPointerExceptions is to use the DialogPane that comes with the Alert, as I do.
If you still want to use your own DialogPane you just have to call alert.setDialogPane before calling applyCss(). Note that when I tried this, however, the call lookup(".graphic-container") still returned null. I guess that Node is only present on the original DialogPane. Removing that lookup call fixed it and the other lookup call (for .button-bar > .container) still worked as expected.
All this seems like implementation behavior that should not be relied upon but it works for me on both Java 8 and Java 10. I'd watch this code when changing Java versions just in case though.

Add a JavaFX Group a Max Width and Height

I have a JavaFX Group with a set of SVGPath's and the problem occurs when i try put a specific size for the Group. The group doesn't response me and put the original size of the file. I try put the group into a AnchorPane and them put a size in the AnchorPane and doesn't work.
package es.raulgf.mapfrance2;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.SVGPath;
public class MapFrance2 extends AnchorPane {
public double originalWeight=450;
public double originalHeight=650;
Group map;
public MapFrance2(double height,double width){
setMaxHeight(height);
setPrefHeight(height);
setPrefWidth(width);
setMaxWidth(width);
try {
map=FXMLLoader.load(getClass().getResource("MapFranceEmpty.fxml"));
map.setScaleX(height/originalHeight);
map.setScaleY(width/originalWeight);
getChildren().add(map);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
other questions is how can resize my Group of SVGPath's and the SVG draw not exceed the AnchorPane border?
Put the group with the SVGPath inside StackPane that will fix your problem.

Animate splitpane divider

I have a horizontal split pane, and i would like to on button click, change divider position, so that i create sort of "slide" animation.
divider would start at 0 (complete left) and on my click it would open till 0.2, when i click again, it would go back to 0;
now i achived this, i just use
spane.setdividerPositions(0.2);
and divider position changes, but i cant manage to do this slowly i would really like that slide feeling when changing divider position.
Could anyone help me ? all examples i found on google, show some DoubleTransition, but that does not exist anymore in java 8, at least i dont have import for that.
You can call getDividers().get(0) to get the first divider. It has a positionProperty() that you can animate using a timeline:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AnimatedSplitPane extends Application {
#Override
public void start(Stage primaryStage) {
SplitPane splitPane = new SplitPane(new Pane(), new Pane());
splitPane.setDividerPositions(0);
BooleanProperty collapsed = new SimpleBooleanProperty();
collapsed.bind(splitPane.getDividers().get(0).positionProperty().isEqualTo(0, 0.01));
Button button = new Button();
button.textProperty().bind(Bindings.when(collapsed).then("Expand").otherwise("Collapse"));
button.setOnAction(e -> {
double target = collapsed.get() ? 0.2 : 0.0 ;
KeyValue keyValue = new KeyValue(splitPane.getDividers().get(0).positionProperty(), target);
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(500), keyValue));
timeline.play();
});
HBox controls = new HBox(button);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
BorderPane root = new BorderPane(splitPane);
root.setBottom(controls);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Why my Stage.close is not Working

Stage.close() is not working for me.
I've checked on:
JavaFX 2.0: Closing a stage (window)
Here is my codes:
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.stage.Modality;
public class MsgBox {
public Stage MessageBox(String Title, String Message){
VBox Pnl = new VBox();
Pnl.setPadding(new Insets(10,10,10,10));
Pnl.setSpacing(10);
Pnl.setAlignment(Pos.CENTER);
Label LblMsg = new Label(Message);
Button CmdOK = new Button("OK");
Pnl.getChildren().addAll(LblMsg, CmdOK);
Scene SCN = new Scene(Pnl);
Stage Window = new Stage();
Window.initModality(Modality.APPLICATION_MODAL);
Window.setTitle(Title);
Window.setScene(SCN);
Window.showAndWait();
CmdOK.setOnAction(new EventHandler<ActionEvent>(){
public void handle(ActionEvent ev){
Window.close();
}
});
return Window;
}
}
Here is the code that calls the Message Box Class:
CmdUpdate.setOnAction(new EventHandler<ActionEvent>(){
public void handle(ActionEvent ev){
new MsgBox().MessageBox("Hello", "Hello World");
}
});
Calling Stage#showAndWait waits until the stage closes before returning, so in fact the next line never gets a chance to run.
Move the line
Window.showAndWait();
to be the last in the method (crucially - after you set the handler to allow closing the stage), or else just use Stage#show, and your problem should be solved.

Resources