I was practicing for javafx for doing pie chart. Following are the codes for developing pie chart. If i do with the Group and with the StackPane,I find no difference in the output.I have commented the Group part.Just wandering the difference between the two.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.PieChart.Data;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ChartApp1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
PieChart pieChart = new PieChart();
//Group p=new Group();
pieChart.setData(getChartData());
primaryStage.setTitle("PieChart");
StackPane root = new StackPane();
root.getChildren().add(pieChart);
//p.getChildren().add(pieChart);
primaryStage.setScene(new Scene(root, 400, 250));
primaryStage.show();
}
private ObservableList<PieChart.Data> getChartData() {
ObservableList<PieChart.Data> answer = FXCollections.observableArrayList();
answer.addAll(new PieChart.Data("java", 17.56),
new PieChart.Data("C", 17.06),
new PieChart.Data("C++", 8.25),
new PieChart.Data("C#", 8.20),
new PieChart.Data("ObjectiveC", 6.8),
new PieChart.Data("PHP", 6.0),
new PieChart.Data("(Visual)Basic", 4.76),
new PieChart.Data("Other", 31.37));
return answer;
}
}
According to the official documentation,
StackPane lays out its children in a back-to-front stack. The z-order of the children is defined by the order of the children list
with the 0th child being the bottom and last child on top. If a border
and/or padding have been set, the children will be layed out within
those insets.
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
While the official documentation for the Group class states that
A Group node contains an ObservableList of children that are
rendered in order whenever this node is rendered. A Group will take on the collective bounds of its children and is not directly
resizable.
Any transform, effect, or state applied to a Group will be applied to
all children of that group. Such transforms and effects will NOT be
included in this Group's layout bounds, however if transforms and
effects are set directly on children of this Group, those will be
included in this Group's layout bounds.
By default, a Group will "auto-size" its managed resizable children to
their preferred sizes during the layout pass to ensure that Regions
and Controls are sized properly as their state changes. If an
application needs to disable this auto-sizing behavior, then it should
set autoSizeChildren to false and understand that if the preferred
size of the children change, they will not automatically resize.
StackPane is a container object; it is used to lay out nodes in a specific manner (back-to-front, according to z-order). Functionally, StackPane is a layout manager. The term root or root node refers to the top-most node in the scene graph of a JavaFX application. The root node is passed to the constructor of the Scene; it is the only node that does not have a parent. Group is a container too. It is not a layout manager; it places its children in absolute coordinates. StackPane and Group can be root nodes.
You would see a difference between Group and StackPane if you wanted to place your chart at a specific location in the application's client area. Placing a chart at x=10 and y=10 would work with Group but would not work with StackPane.
Related
Let us have a JavaFX program with the scene graph Group -> Canvas. The root (Group) is put inside a Scene, and the Scene is attached to a Window, specifically a Stage.
Once the Window is displayed on screen, the user may resize the Window. The height and the width may be changed. However, there are usually some restrictions to how small the Window can be made. Notably the Window has a titlebar, and it has an associated minimum width. See also the picture below.
I suspect that the minimum width of the titlebar is platform-dependent and further depends on user settings of the platform. So a more-less general way of accessing the parameter is desirable.
Is it possible to generally access the minimum (stable) width of the titlebar of a Window? If so, how?
A picture to explain concisely which length I am looking for:
(In the picture, the Window could not be made any smaller in the horisontal dimension).
Here is a MWE for testing (please try to decrease the horisontal width as far as possible):
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.stage.Stage;
public class HelloApplication extends Application {
#Override
public void start(Stage stage) {
Group group = new Group();
Canvas canvas = new Canvas(200, 200);
group.getChildren().add(canvas);
Scene scene = new Scene(group);
stage.setTitle("Title");
stage.setScene(scene);
stage.show();
//System.out.println(stage.getMinWidth()); // default is also 0.0
//stage.setMinWidth(0); // we can see that a lower value than the sought-for minimum value will not have an effect
}
public static void main(String[] args) {
launch();
}
}
I need to visualize some data very compact. Due to limited hight of each data container, I decided to move the heading of each container to the side and rotate it vertically. When rotating the label, it sticks to its parent's dimensions. The maximum length of the label is therefore limited by the width of the parent. How can I accomplish that the label's maxWidth is the actual maxHeight of the parent pane?
For each container, I use a GridPane. The label is inside a StackPane to set a border or to change the background color.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class Test extends Application {
public void start(Stage primaryStage) throws Exception {
// Part of the code
GridPane gridPane = new GridPane();
StackPane namePane;
Label nameLabel;
// ...
// Header
gridPane.getColumnConstraints().add(new ColumnConstraints(40.0));
// Index
gridPane.getColumnConstraints().add(new ColumnConstraints(50.0));
// Name
gridPane.getColumnConstraints().add(new ColumnConstraints(100.0,150.0,400));
// int rows = ...; // Any integer between 1 and 6
int rows = 5;
for(int i = 0; i < rows; i++) {
gridPane.getRowConstraints().add(new RowConstraints(30));
}
namePane = new StackPane();
nameLabel = new Label("Name-123456789");
nameLabel.setStyle("-fx-rotate: -90;");
namePane.getChildren().add(nameLabel);
gridPane.add(namePane,0,0,1,rows);
// ...
// Debug only
gridPane.setGridLinesVisible(true);
// just for running the example
Scene scene = new Scene(gridPane,700,700);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Below two images representing how I would expect the label to look and how it actually looks.
I've already tried to change the maxWidth of the Lable to Double.MAX_VALUE without success.
nameLabel.setMaxWidth(Double.MAX_VALUE);
StackPane treats the Label as if the position was not modified by transforms. This also affects the computed sizes of the StackPane.
To fix this you could wrap the Label in a Parent that does consider transformations when calculating it's size: Group
namePane.getChildren().add(new Group(nameLabel));
Note: This does not resize the Label, if the height of namePane becomes too small to contain it. To achieve that effect, you'd need to implement your own layout.
So goal here is to use custom ScrollBar for ScrollPane without having trouble with layout when maximizing/minimizing window.
Consider example program:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Layout extends Application
{
#Override
public void start(Stage stage)
{
BorderPane main = new BorderPane();
main.setPrefSize(800, 600);
BorderPane center = new BorderPane(); // begin center
center.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)));
main.setCenter(center); // end center
BorderPane left = new BorderPane(); // begin left
ScrollPane pane = new ScrollPane();
pane.setFitToWidth(true);
Pane p1 = new Pane(); // child 1
p1.setPrefSize(200, 100);
p1.setBackground(new Background(new BackgroundFill(Color.YELLOW, CornerRadii.EMPTY, Insets.EMPTY)));
this.makeResizable(p1);
Pane p2 = new Pane(); // child 2
p2.setPrefSize(200, 100);
p2.setBackground(new Background(new BackgroundFill(Color.BLUE, CornerRadii.EMPTY, Insets.EMPTY)));
this.makeResizable(p2);
VBox content = new VBox(10, p1, p2); // content in scroll pane
pane.setContent(content);
// replace normal bars
pane.setHbarPolicy(ScrollBarPolicy.NEVER);
pane.setVbarPolicy(ScrollBarPolicy.NEVER);
// with custom
ScrollBar sb = new ScrollBar();
sb.setOrientation(Orientation.VERTICAL);
sb.minProperty().bind(pane.vminProperty());
sb.maxProperty().bind(pane.vmaxProperty());
sb.visibleAmountProperty().bind(pane.heightProperty().divide(content.heightProperty()));
sb.managedProperty().bind(sb.visibleAmountProperty().lessThan(1.0)); // bar should be managed when it is needed (content too long)
sb.visibleProperty().bind(sb.managedProperty()); // and also visible only when managed
sb.valueProperty().bindBidirectional(pane.vvalueProperty());
left.setCenter(pane); // content
left.setRight(sb); // scroll bar
main.setLeft(left); //end left
Scene scene = new Scene(main);
stage.setScene(scene);
stage.show();
}
// Simple for testing
double prevY;
boolean dragging;
// Makes node resizable on drag.
private void makeResizable(Region region)
{
region.addEventFilter(MouseEvent.MOUSE_PRESSED, e ->
{
this.dragging = true;
region.setPrefHeight(region.getHeight());
this.prevY = e.getSceneY();
});
region.addEventFilter(MouseEvent.MOUSE_DRAGGED, e ->
{
if (!this.dragging) return;
region.setPrefHeight(region.getPrefHeight() + (e.getSceneY() - this.prevY));
this.prevY = e.getSceneY();
});
region.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> this.dragging = false);
}
public static void main(String[] args)
{
launch(args);
}
}
It will produce GUI with ScrollPane on the left with a custom ScrollBar that appears when content (2 nodes p1 and p2) exceed bounds. In order to make it easy to test - both p1 and p2 are made resizable when dragging them with mouse (try it). While the ScrollBar appears and works as expected, there is a flaw in layout if we would start maximizing and minimizing window.
For example:
Start program
Resize content so ScrollBar appears, but not too
much (make it so that when you maximize it, it will be in bounds)
Maximize window - you will notice that bar might have disappeard, but "empty space" appears.
Now, if you would somehow make refresh (e.g by resizing content again), bar would disappear due to layout pass update.
Other bug:
Start program and maximize.
Resize content so it is still contained in maximized window, but is big enough that when you minimize, it will exceed bounds.
Minimize
Notice how ScrollBar is misplaced.
There are few other bugs if you would try other stuff, but all originate from the fact that when you maximize/minimize this happens (using 1st example):
ScrollBar is managed and visible (considering content exceeds).
Maximize
Window is resized layout is calculated (using old values - managed=true && visible=true)
Layout happens, everything is in place, all properties receive update, including sb.visibleAmountProperty() making ScrollBar set managed and visible to false (since they are bound, see code).
ScrollBar becomes invisible and unmanaged, but layout already happened, and will not re-run.
How do I make it work with window maximizing? How else could I bind ScrollBar so it doesn't break when maximizing? Please note that we talk about maximizing, not resizing (which works).
I found one workaround, but it doesn't seem like absolutely best thing to do, if there is more proper one - please share.
sb.managedProperty().addListener(e -> Platform.runLater(() -> content.requestLayout()));
Since this will be ran "later" (and since also on main thread which 1st should finish previous layout), this will cause relayout to happen.
Why is it not best? Well for one it would be called also when not really needed (while not maximizing) causing double layout (and we can't really resove this with some if statement).
I want to change the size of the Stage and WebView of a JavaFx application.
I can change the windows size but the browser size does not increases from 800px so the display of the html page does not fit all the windows.
It looks like this (note the light grey zone on the right):
This is some code of the page:
public class EfadosApp extends Application {
private Scene scene;
#Override public void start(Stage stage) {
scene = new Scene(new Browser(), 1000,500);
stage.setScene(scene);
stage.show();
}
}
class Browser extends Region {
final WebView browser = new WebView();
final WebEngine webEngine = browser.getEngine();
public Browser() {
webEngine.load("www.oracle.com");
getChildren().add(browser);
}
}
You can simply fix this with extending for example StackPane rather than Region:
class Browser extends StackPane {
...
}
The difference is that Region resizes the children to their preferred size :
By default a Region inherits the layout behavior of its superclass,
Parent, which means that it will resize any resizable child nodes to
their preferred size, but will not reposition them. If an application
needs more specific layout behavior, then it should use one of the
Region subclasses: StackPane, HBox, VBox, TilePane, FlowPane,
BorderPane, GridPane, or AnchorPane.
While StackPane tries to resize its children to fit the content area:
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.
This of course also means, if you use Region but either you set the preferred size of the WebView to "something big" like
browser.setPrefSize(5000, 5000);
or you bind the heightProperty and widthProperty of the WebView to the corresponding properties of the Stage,
Browser browser = new Browser();
browser.browser.prefHeightProperty().bind(stage.heightProperty());
browser.browser.prefWidthProperty().bind(stage.widthProperty());
scene = new Scene(browser, 1000, 500);
it will also work as you expect.
When I have clicked a button, it changes its position.
But when I move the mouse, the button comes back to the center of the scene, why?
I have the following code:
public class HolaMundo extends Application {
Button btn;
Scene scene;
#Override
public void start(Stage primaryStage) {
btn = new Button();
btn.setText("Hola Mundo");
StackPane root = new StackPane();
root.getChildren().add(btn);
scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnMouseMoved(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent t) {
btn.setText(String.valueOf(t.getX() ));
}
});
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
btn.setLayoutX(Math.random() * (300 - btn.getWidth()));
btn.setLayoutY(Math.random() * (250 - btn.getHeight()));
}
});
}
public static void main(String[] args) {
launch(args);
}
}
Suggested approach
For your particular code snippet, perhaps using a Pane instead of a StackPane is the best approach.
Why your code doesn't do what you want
If you are going to manually set layout values, don't use a layout pane (such as a StackPane) which automatically sets layout values for you. If you use a layout pane, then the layout values you explicitly set will be overridden automatically the next time layout pane performs a layout pass (e.g. it is resized or some its content or location in the scene graph becomes dirty).
Options for explicitly laying out nodes
If you want to explicitly layout items in JavaFX do one of the following:
Subclass Region and override layoutChildren.
Place your content in a Pane if you need the container to be styled with CSS or implement resizing functions.
Place your content in a Group if you don't need the container to be styled with CSS or implement resizing functions.
Related advice on node positioning
If you want to have automatically laid out components using the predefined layout managers but you want to adjust or temporarily modify the locations of some of the components from their default positions, you can adjust the translateX/Y values rather than layoutX/Y values. According to the definition of translateX:
The node's final translation will be computed as layoutX + translateX, where layoutX establishes the node's stable position and translateX optionally makes dynamic adjustments to that position.
This variable can be used to alter the location of a node without disturbing its layoutBounds, which makes it useful for animating a node's location.
This means that the layout manager can compute a child's default position using layoutX, and you can adjust the position from the default using translateX.
Not having deeply investigated the current case, I see a difference when I use an AnchorPane instead of the StackPane to place the Button on.
By changing the label text of the Button by the mouseMoved-Event the Pane is rendered (layout is requested). With a StackPane placing all it's children in the center of itself the Button's position is reset to the center of the Pane. When you have a look into the layoutChildren method of StackPane you'll see a call to resizeRelocate. So layoutX and layoutY are reset and the button moves back to the center position (or whatever you set the alignment of the StackPane to).
So I think this is a correct behavior of the StackPane and I recommend to use another Pane, e.g. AnchorPane.