How to add child node without affecting layout of GridPane parent - javafx

I want create a app using javafx. It looks like this:
I want to add the zoom function for the chart. When I click the button "Zoom in", the app will become fig2. However, I have no idea to achieve it. When I change the size of pane included the chart, it will change grid pane size, looks like this:

You do not want the zoom to be considered for the gridpane layout. In this can be achieved by applying transforms to the child of the gridpane you want to modify.
The following example demonstrates how to zoom a node while the mouse is hovering over it:
private static Region createRegion(String background) {
Region region = new Region();
region.setStyle("-fx-background-color:"+background);
region.setPrefSize(300, 100);
return region;
}
#Override
public void start(Stage primaryStage) {
GridPane gp = new GridPane();
// create background
gp.add(createRegion("green"), 0, 0);
gp.add(createRegion("dodgerblue"), 0, 1);
// create region to be zoomed
Region zoomRegion = createRegion("red");
GridPane.setFillWidth(zoomRegion, Boolean.FALSE);
GridPane.setFillHeight(zoomRegion, Boolean.FALSE);
zoomRegion.setPrefWidth(100);
Scale scale = new Scale();
zoomRegion.getTransforms().add(scale);
// keep pivot at bottom left corner
scale.pivotYProperty().bind(zoomRegion.heightProperty());
zoomRegion.hoverProperty().addListener((observable, oldValue, newValue) -> {
// adjust scale when hover state is changed
double scaleFactor = newValue ? 1.5 : 1;
scale.setX(scaleFactor);
scale.setY(scaleFactor);
});
gp.add(zoomRegion, 0, 1);
Scene scene = new Scene(gp);
primaryStage.setScene(scene);
primaryStage.show();
}

Related

How to put multiple TextField in a circle in JavaFx

I am trying to put multiple textfields in a circle in JavaFX. I could add a field in the centre using StackPane as explained in the below-mentioned post but unable to add multiple textfields. I tried using different panes for that but it didn't work.
Added the code that doesn't work.I want to add two text fields at any place inside a circle. Using gridpane for it didn't work. Moreover, I want to create x number of circle dynamically at any place in a gridpane and add multiple text fields to the circle, is it possible to do that using JavaFX?
Hope I am able to explain the problem statement correctly. Any response is appreciated :)
#Override
public void start(Stage arg0) throws Exception {
arg0.setTitle("Text Boxes In circle");
arg0.setMaxWidth(500);
Circle circle = createCircle(); // This function is to form a circle.
Text text = new Text("42");
Text text1 = new Text("36");
text.setBoundsType(TextBoundsType.VISUAL);
text1.setBoundsType(TextBoundsType.VISUAL);
GridPane box = new GridPane();
// box.setConstraints(text, 2, 0); commented this out to check if it was not
// causing problem but still didn't work
// box.setConstraints(text1, 2, 1);
// box.setAlignment(Pos.CENTER); Even used this to center the gridPane didn't
// work either.
StackPane stack = new StackPane();
box.getChildren().addAll(text, text1);
stack.getChildren().addAll(box, circle);
Scene scene = new Scene(stack);
arg0.setScene(scene);
arg0.show();
}
public static void main(String[] args) {
launch(args);
}
private static Circle createCircle() {
final Circle circle = new Circle(100);
circle.setStroke(Color.FORESTGREEN);
circle.setStrokeWidth(10);
circle.setStrokeType(StrokeType.INSIDE);
circle.setFill(Color.AZURE);
return circle;
}
how to put a text into a circle object to display it from circle's center?

JavaFX trouble with fade transitions

I'm currently developing an app. It's visual structure is the following:
Only one Stage.
Only one Scene which has an ApplicationContainer's (my own class which
is basically a StackPane with a BorderPane inside of it with a
MenuBar on top, and the current page in it's center).
Multiple ApplicationLayout's
The ApplicationLayout has a Header and a Footer (footer not implemented yet) and looks like this:
I've managed to implement fadeIn / fadeOut transitions between the pages by setting a StackPane as the BorderPane's center, adding the page to it, and on top of that, a white VBox. So before I make the page switch I work with FadeTransitions of this white VBox.
I had to do it this way because setOpacity() wouldn't change the textfields or button opacities for some reason.
Now I'm trying to do the exact same thing for the header. So I setted a StackPane to the top, and added to it the header and a on top of it a "header coverer" which supposedly should do the trick just as before (can't modify the opacity property of the title, arrow or description because of CSS overriding).
But this time it's not working, if I set the opacity of the header coverer to anything but 0, the stuff in the header doesn't show.
What I want to acomplish is to fadeOut / FadeIn the components of the header but not the orange HBox.
EDIT: Added a minimal example where this doesn't work for me
public class Main extends Application {
private Boolean buttonPressed = false;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
BorderPane appWindow = new BorderPane();
appWindow.setStyle("-fx-alignment: center; -fx-padding: 30 0 0 30");
appWindow.setBackground(new Background(new BackgroundFill(Color.PERU, null, null)));
GridPane loginContainer = new GridPane();
appWindow.setCenter(loginContainer);
TextField username = new TextField();
PasswordField password = new PasswordField();
Label userNameDesc = new Label("Username");
Label passwordDesc = new Label("Password");
Button logInBtn = new Button("Log In");
logInBtn.setTranslateX(100);
logInBtn.setTranslateY(20);
logInBtn.setOnAction(event -> {
if (!buttonPressed) {
appWindow.getCenter().setOpacity(30);
buttonPressed = true;
System.out.println("Opacity set to " + appWindow.getCenter().getOpacity());
}
else {
appWindow.getCenter().setOpacity(100);
buttonPressed = false;
System.out.println("Opacity set to " + appWindow.getCenter().getOpacity());
}
});
loginContainer.addColumn(0, userNameDesc, passwordDesc);
loginContainer.addColumn(1, username, password);
loginContainer.add(logInBtn, 1, 2);
Scene scene = new Scene(appWindow, 300, 250);
stage.setScene(scene);
stage.show();
}
}
Pressing the "Log In" button should affect the Gridpane and Gridpane childs visual opacity, but it doesn't. It just prints the correct opacity values.
According to the documentation:
Opacity is specified as a value between 0 and 1. Values less than 0 are treated as 0, values greater than 1 are treated as 1.
So setting the value to 30 or to 100 has no effect: both are treated as fully opaque (i.e. they are clamped at 1).
Replacing
appWindow.getCenter().setOpacity(30);
with
appWindow.getCenter().setOpacity(0.3);
will make the center content partially transparent.

Responsive UI-Design with a TilePane in a ScrollPane

I am trying to wrap my head around Scroll- and Tilepanes atm, and I have come upon an issue I just cant solve without a dirty hack.
I have a horizontal TilePane that has 8 Tiles, and I set it to have 4 columns, resulting in 2 rows with 4 tiles.
That TilePane I put in an HBox, since if I put it in a StackPane it would stretch the size of the tilepane making my colum setting void. A bit weird that setting the prefColumns/Rows recalculates the size of the TilePane, rather than trying to set the actual amounts of columns/rows, feels more like a dirty hack.
Anyway, putting the HBox directly into the ScrollPane would not work either, since the Scrollbars would not appear even after the 2nd row of tiles would get cut off. Setting that HBox again in a Stackpane which I then put in a ScrollPane does the trick. Atleast until I resize the width of the window to be so small the tilepane has to align the tiles anew and a 3rd or more rows appear.
Here is the basic programm:
public class Main extends Application {
#Override
public void start(Stage stage) {
TilePane tilePane = new TilePane();
tilePane.setPadding(new Insets(5));
tilePane.setVgap(4);
tilePane.setHgap(4);
tilePane.setPrefColumns(4);
tilePane.setStyle("-fx-background-color: lightblue;");
HBox tiles[] = new HBox[8];
for (int i = 0; i < 8; i++) {
tiles[i] = new HBox(new Label("This is node #" + i));
tiles[i].setStyle("-fx-border-color: black;");
tiles[i].setPadding(new Insets(50));
tilePane.getChildren().add(tiles[i]);
}
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER);
hbox.setStyle("-fx-background-color: blue;");
hbox.getChildren().add(tilePane);
StackPane stack = new StackPane();
stack.getChildren().add(hbox);
ScrollPane sp = new ScrollPane();
sp.setFitToHeight(true);
sp.setFitToWidth(true);
sp.setContent(stack);
stage.setScene(new Scene(sp, 800, 600));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I managed to achieve my wanted behaviour, but its more of a really dirty hack. I added a listener to the height and width of my HBox containing the TilePane and assumed that when the height changes its because the width got so small that a column was removed and a new row added. To be able to do that I put the HBox in a VBox so that it would not grow withe the height of the ScrollPane. For the width I simply calculated if there is space to display another colum (up to 4), to do it.
Here are the changes:
public class Main extends Application {
private boolean notFirstPassHeight;
private boolean notFirstPassWidth;
#Override
public void start(Stage stage) {
TilePane tilePane = new TilePane();
tilePane.setPadding(new Insets(5));
tilePane.setVgap(4);
tilePane.setHgap(4);
tilePane.setPrefColumns(4);
tilePane.setStyle("-fx-background-color: lightblue;");
// I took the value from ScenicView
tilePane.prefTileWidthProperty().set(182);
HBox tiles[] = new HBox[8];
for (int i = 0; i < 8; i++) {
tiles[i] = new HBox(new Label("This is node #" + i));
tiles[i].setStyle("-fx-border-color: black;");
tiles[i].setPadding(new Insets(50));
tilePane.getChildren().add(tiles[i]);
}
ScrollPane sp = new ScrollPane();
sp.setFitToHeight(true);
sp.setFitToWidth(true);
StackPane stack = new StackPane();
VBox vbox = new VBox();
vbox.setStyle("-fx-background-color: red");
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER);
hbox.setStyle("-fx-background-color: blue;");
hbox.getChildren().add(tilePane);
notFirstPassHeight = false;
notFirstPassWidth = false;
hbox.heightProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue.doubleValue() < newValue.doubleValue() && notFirstPassHeight) {
tilePane.setPrefColumns(tilePane.getPrefColumns() - 1);
stack.requestLayout();
}
notFirstPassHeight = true;
});
hbox.widthProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue.doubleValue() < newValue.doubleValue() && notFirstPassWidth && tilePane.getPrefColumns() <= 3
&& (newValue.doubleValue() / (tilePane.getPrefColumns() + 1)) > tilePane.getPrefTileWidth()) {
tilePane.setPrefColumns(tilePane.getPrefColumns() + 1);
stack.requestLayout();
}
notFirstPassWidth = true;
});
vbox.getChildren().add(hbox);
stack.getChildren().add(vbox);
sp.setContent(stack);
stage.setScene(new Scene(sp, 800, 600));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
However this approach requires me to
1.Know the Width of the Tiles in the Tilepane.
2.Consider Padding and Gap between tiles for my calculation to be accurate, which I dont do in my example.
And its just not a good approach at any rate if you ask me. Too complicated a process for such a basic thing. There has to be a way better and simple way to accomplish complete resizability and the wanted behaviour with TilePanes in a ScrollPane.
Setting the preferred number of columns and/or rows in the TilePane determines the calculation for the prefWidth and prefHeight values for that tile pane. If you want to force a maximum number of columns, you just need to make the maxWidth equal to the computed prefWidth: you can do this with
tilePane.setMaxWidth(Region.USE_PREF_SIZE);
This means that (as long as the tile pane is placed in something that manages layout), it will never be wider than the pref width, which is computed to allow the preferred number of columns. It may, of course, be smaller than that. (Note you could use the same trick with setMinWidth if you needed a minimum number of columns, rather than a maximum number of columns.)
The scroll pane's fitToHeight and fitToWidth properties will, when true, attempt to resize the height (respectively width) of the content to be equal to the height (width) of the scroll pane's viewport. These operations will take precedence over the preferred height (width) of the content, but will attempt to respect the minimum height (width).
Consequently, it's usually a mistake to call both setFitToWidth(true) and setFitToHeight(true), as this will almost always turn off scrolling completely (just forcing the content to be the same size as the scroll pane's viewport).
So here you want to make the max width of the tile pane respect the pref width, and fix the width of the tile pane to be the width of the scroll pane's viewport (so that when you shrink the width of the window, it shrinks the width of the viewport and creates more columns). This will add a vertical scrollbar if the number of rows grows large enough, and only add a horizontal scrollbar if the viewport shrinks horizontally below the minimum width of the tile pane (which is computed as the minimum of the preferred widths of all the nodes it contains).
I think the following version of your original code does essentially what you are looking for:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
public class ScrollingTilePane extends Application {
#Override
public void start(Stage stage) {
TilePane tilePane = new TilePane();
tilePane.setPadding(new Insets(5));
tilePane.setVgap(4);
tilePane.setHgap(4);
tilePane.setPrefColumns(4);
tilePane.setStyle("-fx-background-color: lightblue;");
// dont grow more than the preferred number of columns:
tilePane.setMaxWidth(Region.USE_PREF_SIZE);
HBox tiles[] = new HBox[8];
for (int i = 0; i < 8; i++) {
tiles[i] = new HBox(new Label("This is node #" + i));
tiles[i].setStyle("-fx-border-color: black;");
tiles[i].setPadding(new Insets(50));
tilePane.getChildren().add(tiles[i]);
}
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER);
hbox.setStyle("-fx-background-color: blue;");
hbox.getChildren().add(tilePane);
// StackPane stack = new StackPane();
// stack.getChildren().add(tilePane);
// stack.setStyle("-fx-background-color: blue;");
ScrollPane sp = new ScrollPane();
// sp.setFitToHeight(true);
sp.setFitToWidth(true);
sp.setContent(hbox);
stage.setScene(new Scene(sp, 800, 600));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Note that if you need to change the background color of the space outside the scroll pane's content, you can use the following in an external style sheet:
.scroll-pane .viewport {
-fx-background-color: red ;
}

Aligning a Path within a Container, StackPane, VBox etc

If I try to align a Path in a StackPane, somehow it is aligned based on its internal structure rather than using the layout bounds. Is there anyway of changing this? I've tried all sorts of combinations of with/without Group wrapper, VBox, HBox, autosizeChildren property etc.
For example, I want the line to be inset by (50, 50), but it gets shown at (0, 0)
#Override
public void start(Stage primaryStage) {
MoveTo moveTo = new MoveTo();
moveTo.setX(50);
moveTo.setY(50);
LineTo lineTo = new LineTo();
lineTo.setX(100);
lineTo.setY(100);
Path path = new Path(moveTo, lineTo);
StackPane stack = new StackPane(new Group(path));
stack.setAlignment(Pos.TOP_LEFT);
primaryStage.setScene(new Scene(stack, 100, 100));
primaryStage.show();
}
Without using a StackPane it appears as I expect. the line starts at (50, 50)
#Override
public void start(Stage primaryStage) {
MoveTo moveTo = new MoveTo();
moveTo.setX(50);
moveTo.setY(50);
LineTo lineTo = new LineTo();
lineTo.setX(100);
lineTo.setY(100);
Path path = new Path(moveTo, lineTo);
primaryStage.setScene(new Scene(new Group(path), 100, 100));
primaryStage.show();
}
According to the StackPane Javadocs:
The stackpane will attempt to resize each child to fill its content
area. If the child could not be sized to fill the stackpane (either
because it was not resizable or its max size prevented it) then it
will be aligned within the area using the alignment property, which
defaults to Pos.CENTER.
Since the Path is not resizable, is doesn't get resized, and just gets positioned according to the alignment you set. Wrapping in a Group doesn't help because the Group takes on the bounds of its children.
Either use a regular Pane instead of the StackPane (probably the most convenient solution):
Pane stack = new Pane(path);
or wrap the path in a Pane (which gets resized, etc):
StackPane stack = new StackPane(new Pane(path));

JavaFX Right Coordinate of a CustomMenuItem

I have a Class that extends the CustomMenuItem. This MenuItems are added to a ContextMenu. Now i need to get the X-Coordinates from the right side of the CustomMenuItem.
The Problem is, that I have no idea how I can get the Coordinates.
The CustMenuItem has no function for getting the Coordinates like getX() or getY().
So how can I solve this problem?
This thing I would like to get:
Here we can see a Sample for a Context Menu (red lines). In the Context Menu are a lot of different CustomMenuItems implemented. Now I would like to get the right top corner Coordinate of the CustomMenuItem.
Thank you for your very nice help.
Before dealing with menu items, let's start saying that a ContextMenu is a popup window, so it has Windowproperties. You can ask for (x,y) left, top origin, and for (w,h).
But you have to take into account the effects, since by default it includes a dropshadow. And when it does, there's an extra space added of 24x24 pixels to the right and bottom.
.context-menu {
-fx-effect: dropshadow( gaussian , rgba(0,0,0,0.2) , 12, 0.0 , 0 , 8 );
}
Since this default dropshadow has a radius of 12px, and Y-offset to the bottom of 8px, the right and bottom coordinates of the context menu, including the 24x24 area, are given by:
X=t.getX()+cm.getWidth()-12-24;
Y=t.getY()+cm.getHeight()-(12-8)-24;
where t could be a MouseEvent relative to the scene, and values are hardcoded for simplicity.
Let's see this over an example. Since you don't say how your custom menu items are implemented, I'll just create a simple Menu Item with graphic and text:
private final Label labX = new Label("X: ");
private final Label labY = new Label("Y: ");
#Override
public void start(Stage primaryStage) {
final ContextMenu cm = new ContextMenu();
MenuItem cmItem1 = createMenuItem("mNext", "Next Long Option",t->System.out.println("next"));
MenuItem cmItem2 = createMenuItem("mBack", "Go Back", t->System.out.println("back"));
SeparatorMenuItem sm = new SeparatorMenuItem();
cm.getItems().addAll(cmItem1,cmItem2);
VBox root = new VBox(10,labX,labY);
Scene scene = new Scene(root, 300, 250);
scene.setOnMouseClicked(t->{
if(t.getButton()==MouseButton.SECONDARY || t.isControlDown()){
// t.getX,Y->scene based coordinates
cm.show(scene.getWindow(),t.getX()+scene.getWindow().getX()+scene.getX(),
t.getY()+scene.getWindow().getY()+scene.getY());
labX.setText("Right X: "+(t.getX()+cm.getWidth()-12-24));
labY.setText("Bottom Y: "+(t.getY()+cm.getHeight()-4-24));
}
});
scene.getStylesheets().add(getClass().getResource("root.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setTitle("Scene: "+scene.getWidth()+"x"+scene.getHeight());
}
private MenuItem createMenuItem(String symbol, String text, EventHandler<ActionEvent> t){
MenuItem m=new MenuItem(text);
StackPane g=new StackPane();
g.setPrefSize(24, 24);
g.setId(symbol);
m.setGraphic(g);
m.setOnAction(t);
return m;
}
If you remove the effect:
.context-menu {
-fx-effect: null;
}
then these coordinates are:
X=t.getX()+cm.getWidth();
Y=t.getY()+cm.getHeight();
Now that we have the window, let's go into the items.
MenuItem skin is derived from a (private) ContextMenuContent.MenuItemContainer class, which is a Region where the graphic and text are layed out.
When the context menu is built, all the items are wrapped in a VBox, and all are equally resized, as you can see if you set the border for the item:
.menu-item {
-fx-border-color: black;
-fx-border-width: 1;
}
This is how it looks like:
So the X coordinates of every item on the custom context menu are the same X from their parent (see above, with or without effect), minus 1 pixel of padding (by default).
Note that you could also go via private methods to get dimensions for the items:
ContextMenuContent cmc= (ContextMenuContent)cm.getSkin().getNode();
System.out.println("cmc: "+cmc.getItemsContainer().getBoundsInParent());
Though this is not recommended since private API can change in the future.
EDIT
By request, this is the same code removing lambdas and css.
private final Label labX = new Label("X: ");
private final Label labY = new Label("Y: ");
#Override
public void start(Stage primaryStage) {
final ContextMenu cm = new ContextMenu();
MenuItem cmItem1 = createMenuItem("mNext", "Next Long Option",action);
MenuItem cmItem2 = createMenuItem("mBack", "Go Back", action);
SeparatorMenuItem sm = new SeparatorMenuItem();
cm.getItems().addAll(cmItem1,cmItem2);
VBox root = new VBox(10,labX,labY);
Scene scene = new Scene(root, 300, 250);
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if(t.getButton()==MouseButton.SECONDARY || t.isControlDown()){
// t.getX,Y->scene based coordinates
cm.show(scene.getWindow(),t.getX()+scene.getWindow().getX()+scene.getX(),
t.getY()+scene.getWindow().getY()+scene.getY());
labX.setText("Right X: "+(t.getX()+cm.getWidth()-12-24));
labY.setText("Bottom Y: "+(t.getY()+cm.getHeight()-4-24));
}
}
});
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setTitle("Scene: "+scene.getWidth()+"x"+scene.getHeight());
}
private MenuItem createMenuItem(String symbol, String text, EventHandler<ActionEvent> t){
MenuItem m=new MenuItem(text);
StackPane g=new StackPane();
g.setPrefSize(24, 24);
g.setId(symbol);
SVGPath svg = new SVGPath();
svg.setContent("M0,5H2L4,8L8,0H10L5,10H3Z");
m.setGraphic(svg);
m.setOnAction(t);
return m;
}
private final EventHandler<ActionEvent> action = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("action");
}
};

Resources