I have a number of nodes in a group, whose visibility I would like to control using the visibleProperty(). I have observed that turning visibility of an element off affects the layout bounds of the group. They are recalculated as if the element wasn't there at all, not just wasn't visible. Is there a way to keep the presence of a node, but just turn off its visibility, so that the layout bounds were adjusted as if the node was there the whole time?
Example to ilustrate the problem, let's assume that the nodes are circles below.
Circle c1 = new Circle(0.0, 0.0, 3); // radius is 3
Circle c2 = new Circle(10.0, 10.0, 3);
Circle c3 = new Circle(20.0, 20.0, 3);
Group g = new Group();
g.getChildren().addAll(c1, c2, c3);
Bounds b1 = g.getLayoutBounds();
c3.visibleProperty().setValue(false);
Bounds b2 = g.getLayoutBounds();
Above bounds b and b2 will span:
b1 = BoundingBox [minX:-3.0, minY:-3.0, minZ:0.0, width:26.0, height:26.0, depth:0.0, maxX:23.0, maxY:23.0, maxZ:0.0]
b2 = BoundingBox [minX:-3.0, minY:-3.0, minZ:0.0, width:16.0, height:16.0, depth:0.0, maxX:13.0, maxY:13.0, maxZ:0.0]
Bounds b2 are calculated after turning off the visibility of circle c3, so they shrinked with respect to the original bounds b1. I would like to have the method of having the bounds expanded to b1, even though some elements in the group are not visible.
Edit
Minimal reproducible example:
package sample;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.HBox;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Circle c1 = new Circle(0.0, 0.0, 3); // radius is 3
Circle c2 = new Circle(10.0, 10.0, 3);
Circle c3 = new Circle(20.0, 20.0, 3); // radius is 3
Circle c4 = new Circle(30.0, 30.0, 3);
Group g1 = new Group();
Group g2 = new Group();
g1.getChildren().addAll(c1, c2);
g2.getChildren().addAll(c3, c4);
Group main = new Group(g1, g2);
CheckBox checkBox1 = new CheckBox("Show");
g2.visibleProperty().bind(checkBox1.selectedProperty());
Bounds b1 = main.getLayoutBounds();
System.out.println(b1);
checkBox1.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean t1) {
System.out.println(main.getLayoutBounds());
}
});
HBox hbox = new HBox(checkBox1, main);
Scene scene = new Scene(hbox, 400, 300);
primaryStage.setTitle("Hello Stackoverflow, are you happy now?");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Now, any time you select the checkbox, not only 2 of 4 circles appear, but you will also see that layout bounds of the group change. The problem is that I need to know all the time the layout bounds as if all the groups were visible. I can't store the bounds value when they are all visible, because for simplicity I included here only 4 circles, while in real application, they are in thousands and they are moving around also when not visible, and there are also other objects of different sizes bound to them, which may expand the layout bounds of the main group even more.
Updated solution.
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Shape;
public class ShapesGroup extends Group {
public final BooleanProperty transparentProperty = new SimpleBooleanProperty();
private final ObservableList<Shape> shapes = FXCollections.observableArrayList();
public ShapesGroup() {
Bindings.bindContent(getChildren(), shapes);
shapes.addListener((ListChangeListener<Shape>) change -> {
while (change.next()) {
if (change.wasAdded()) {
for (Shape shape : change.getAddedSubList()) {
shape.fillProperty().addListener((ChangeListener<Paint>) (observable, oldFill, newFill) -> {
transparentProperty.addListener((ChangeListener<Boolean>) (observable1, oldTransparent, newTransparent) -> {
if (!newTransparent) {
shape.setFill(oldFill);
}
});
});
transparentProperty.addListener((ChangeListener<Boolean>) (observable, oldValue, newValue) -> {
if (newValue) {
shape.setFill(Color.TRANSPARENT);
}
});
}
}
}
});
}
public ObservableList<Shape> getShapes() {
return shapes;
}
}
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class MainApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Rectangle rectangle = new Rectangle(40, 40, Color.YELLOW);
Circle circle = new Circle(20, Color.RED);
ShapesGroup group = new ShapesGroup();
group.getShapes().addAll(rectangle, circle);
StackPane stackPane = new StackPane(group);
Button button = new Button("Switch");
VBox vBox = new VBox(stackPane, button);
VBox.setVgrow(stackPane, Priority.ALWAYS);
Scene scene = new Scene(vBox, 400, 400);
stage.setScene(scene);
stage.show();
button.setOnAction(event -> {
group.transparentProperty.set(!group.transparentProperty.get());
});
}
}
Related
I found a little bug in JavaFX TabPane, and am looking for a workaround. I am running JavaFX 13.0.1.
How it happens:
The TabPane's DragPolicy must be set to TabPane.TabDragPolicy.REORDER.
You can navigate between tabs via keyboard shortcuts CTRL + TAB & CTRL + SHIFT + TAB.
However, if I drag, say, the last tab to the very left and release it back to the position it was in (so that nothing should change), these keyboard shortcuts get messed up - no longer pointing to proper next/previous tabs.
You should be able to reproduce it simply with the following code:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Test extends Application {
#Override
public void start(Stage primaryStage) {
TabPane tabPane = new TabPane();
tabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
tabPane.getTabs().add(new Tab("First"));
tabPane.getTabs().add(new Tab("Second"));
tabPane.getTabs().add(new Tab("Third"));
tabPane.getTabs().add(new Tab("Fourth"));
tabPane.getTabs().add(new Tab("Fifth"));
StackPane root = new StackPane();
root.getChildren().add(tabPane);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("TabPane bug");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
An interesting bug. It appears TabPane.getTabs() returns a List which may or may not reflect the visual ordering of the tabs. But the navigation keys always rely on the getTabs() order, not the visual order.
One workaround is to use a Label as a graphic for each Tab, while leaving the Tab’s text as null. You can then keep the Tabs sorted properly yourself, by checking the visual position of each such Label.
import java.util.List;
import java.util.ArrayList;
import javafx.beans.Observable;
import javafx.geometry.Bounds;
import javafx.geometry.NodeOrientation;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TabDragTest2 extends Application {
private static Tab createTab(String title) {
Tab tab = new Tab();
tab.setGraphic(new Label(title));
return tab;
}
#Override
public void start(Stage primaryStage) {
TabPane tabPane = new TabPane();
tabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
tabPane.getTabs().add(createTab("First"));
tabPane.getTabs().add(createTab("Second"));
tabPane.getTabs().add(createTab("Third"));
tabPane.getTabs().add(createTab("Fourth"));
tabPane.getTabs().add(createTab("Fifth"));
tabPane.getTabs().addListener((Observable o) -> {
List<Tab> tabs = new ArrayList<>(tabPane.getTabs());
NodeOrientation orientation = tabPane.getEffectiveNodeOrientation();
boolean ltr = orientation == NodeOrientation.LEFT_TO_RIGHT;
tabs.sort((t1, t2) -> {
Node title1 = t1.getGraphic();
Node title2 = t2.getGraphic();
Bounds title1Bounds =
title1.localToScene(title1.getLayoutBounds());
Bounds title2Bounds =
title2.localToScene(title2.getLayoutBounds());
if (tabPane.getSide().isHorizontal()) {
if (ltr) {
return Double.compare(
title1Bounds.getMinX(), title2Bounds.getMinX());
} else {
return Double.compare(
title2Bounds.getMaxX(), title1Bounds.getMaxX());
}
} else {
return Double.compare(
title1Bounds.getMinY(), title2Bounds.getMinY());
}
});
if (!tabPane.getTabs().equals(tabs)) {
Platform.runLater(() -> tabPane.getTabs().setAll(tabs));
}
});
StackPane root = new StackPane();
root.getChildren().add(tabPane);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("TabPane bug");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I need some help trying to get two things appearing on this borderpane. Currently I have the center set as a Gridpane. I need to add a mediaview and the text displayed in the class ButtonDemo both to the Gridpane. I also have to set the top of the borderpane to have a moving square. I can't figure out how to animate the square. But currently I am having trouble getting these two things to display. If anyone could help explain why these two objects won't display that would be very helpful. Thanks!
import javafx.application.Application;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.stage.Stage;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.CheckBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.util.Duration;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
//Up and Down Button Class
class ButtonDemo extends Application {
protected Text text = new Text(50, 50, "Assignment 7");
public GridPane grid = new GridPane();
protected BorderPane getPane() {
HBox paneForButtons = new HBox(20);
Button btUp = new Button("^ Up ");
Button btDown = new Button("v Down ");
paneForButtons.getChildren().addAll(btUp, btDown);
paneForButtons.setStyle("-fx-border-color: green");
BorderPane border = new BorderPane();
border.setBottom(paneForButtons);
Pane paneForText = new Pane();
paneForText.getChildren().add(text);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(0, 10, 0, 10));
border.setCenter(grid);
grid.add(paneForText, 1, 10);
btUp.setOnAction(e -> text.setY(text.getY() - 10));
btDown.setOnAction(e -> text.setY(text.getY() + 10));
return border;
}
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(getPane(), 800, 650);
primaryStage.setTitle("Assignment 7");
primaryStage.setScene(scene);
primaryStage.show();
}
}
//Font Type Class
class CheckBoxDemo extends ButtonDemo {
#Override
protected BorderPane getPane() {
BorderPane border = super.getPane();
Font fontBoldItalic = Font.font("Arial",
FontWeight.BOLD, FontPosture.ITALIC, 20);
Font fontBold = Font.font("Arial",
FontWeight.BOLD, FontPosture.REGULAR, 20);
Font fontItalic = Font.font("Arial",
FontWeight.NORMAL, FontPosture.ITALIC, 20);
Font fontNormal = Font.font("Arial",
FontWeight.NORMAL, FontPosture.REGULAR, 20);
text.setFont(fontNormal);
VBox paneForCheckBoxes = new VBox(20);
paneForCheckBoxes.setPadding(new Insets(5, 5, 5, 5));
paneForCheckBoxes.setStyle("-fx-border-color: green");
CheckBox chkBold = new CheckBox("Bold");
CheckBox chkItalic = new CheckBox("Italic");
paneForCheckBoxes.getChildren().addAll(chkBold, chkItalic);
border.setLeft(paneForCheckBoxes);
EventHandler<ActionEvent> handler = e -> {
if (chkBold.isSelected() && chkItalic.isSelected()) {
text.setFont(fontBoldItalic);
}
else if (chkBold.isSelected()) {
text.setFont(fontBold);
}
else if (chkItalic.isSelected()) {
text.setFont(fontItalic);
}
else {
text.setFont(fontNormal);
}
};
chkBold.setOnAction(handler);
chkItalic.setOnAction(handler);
return border;
}
}
//Color Button Class
class RadioButtonDemo extends CheckBoxDemo {
#Override
protected BorderPane getPane() {
BorderPane border = super.getPane();
VBox paneForRadioButtons = new VBox(20);
paneForRadioButtons.setPadding(new Insets(5, 5, 5, 5));
paneForRadioButtons.setStyle("-fx-border-color: black");
paneForRadioButtons.setStyle("-fx-border-color: green");
RadioButton rbYellow = new RadioButton("Yellow");
RadioButton rbOrange = new RadioButton("Orange");
RadioButton rbPurple = new RadioButton("Purple");
paneForRadioButtons.getChildren().addAll(rbYellow, rbOrange, rbPurple);
border.setRight(paneForRadioButtons);
ToggleGroup group = new ToggleGroup();
rbYellow.setToggleGroup(group);
rbOrange.setToggleGroup(group);
rbPurple.setToggleGroup(group);
rbYellow.setOnAction(e -> {
if (rbYellow.isSelected()) {
text.setFill(Color.YELLOW);
}
});
rbOrange.setOnAction(e -> {
if (rbOrange.isSelected()) {
text.setFill(Color.ORANGE);
}
});
rbPurple.setOnAction(e -> {
if (rbPurple.isSelected()) {
text.setFill(Color.PURPLE);
}
});
return border;
}
}
//Rectangle Bouncing Class
class BouncingRectangle extends RadioButtonDemo {
private Rectangle rectangle = new Rectangle( 0, 0, 10, 10);
private Timeline animation;
#Override
protected BorderPane getPane() {
BorderPane border = super.getPane();
Pane squarePane = new Pane();
rectangle.setFill(Color.BLUE);
squarePane.getChildren().add(rectangle);
border.setTop(squarePane);
return border;
}
}
//MP4 Class
class MediaDemo extends RadioButtonDemo {
private static final String MEDIA_URL =
"http://cs.armstrong.edu/liang/common/sample.mp4";
#Override
protected BorderPane getPane() {
BorderPane border = super.getPane();
Media media = new Media(MEDIA_URL);
MediaPlayer mediaPlayer = new MediaPlayer(media);
MediaView mediaView = new MediaView(mediaPlayer);
Button playButton = new Button(">");
playButton.setOnAction(e -> {
if (playButton.getText().equals(">")) {
mediaPlayer.play();
playButton.setText("||");
} else {
mediaPlayer.pause();
playButton.setText(">");
}
});
Button rewindButton = new Button("<<");
rewindButton.setOnAction(e -> mediaPlayer.seek(Duration.ZERO));
Slider slVolume = new Slider();
slVolume.setPrefWidth(150);
slVolume.setMaxWidth(Region.USE_PREF_SIZE);
slVolume.setMinWidth(30);
slVolume.setValue(50);
mediaPlayer.volumeProperty().bind(
slVolume.valueProperty().divide(100));
HBox hBox = new HBox(10);
hBox.setAlignment(Pos.CENTER);
hBox.getChildren().addAll(playButton, rewindButton,
new Label("Volume"), slVolume);
BorderPane paneForMedia = new BorderPane();
paneForMedia.setCenter(mediaView);
paneForMedia.setBottom(hBox);
grid.add(paneForMedia, 6, 10);
return border;
}
}
public class n00935124 extends RadioButtonDemo {
public static void main(String[] args) {
launch(args);
}
}
Thanks again
First, this is a really non-standard way to organize things. Not wrong in any sense(*), but just so non-standard that it took me a while to figure out what was going on.
The reason you are not seeing the last two additions of content is that your inheritance hierarchy is wrong. You want:
class MediaDemo extends BouncingRectangle
and
public class n00935124 extends MediaDemo
instead of both extending RadioButtonDemo (which I'm guessing is just a copy-and-paste error).
(*) I guess the issue is that you are using inheritance solely to add state here, which is something of an anti-pattern. Inheritance is best used to either add behavior, or to provide alternative implementations of behavior.
I am adding some drag and drop behavior to my application where the user can DnD stuff onto a canvas with custom logic and rendering code. I need to get the mouse's position relative to the canvas (i.e. 0,0 being top-left) but the DragEvent does not let me do this, and mouse events do not get delivered to the canvas during the DnD.
Is it possible to allow the mouse events to be delivered while a drag-and-drop is in progress?
You can do this in a dragOver handler, which is invoked any time the mouse moves on the node on which the handler is registered during the drag.
Simple example:
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class DragToCanvas extends Application {
private Color draggingColor ;
#Override
public void start(Stage primaryStage) {
Pane dragFromPane = new Pane();
Canvas canvas = new Canvas(600, 600);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.ANTIQUEWHITE);
gc.fillRect(0, 0, 600, 600);
Label coordinateLabel = new Label("[ ]");
coordinateLabel.setStyle("-fx-font-size: 24;");
Color[] colors = {Color.RED, Color.GREEN, Color.BLUE, Color.BLACK} ;
for (int i = 0; i < colors.length; i++) {
drawCircle(colors[i], dragFromPane, i);
}
canvas.setOnDragOver(e -> {
if (draggingColor != null
&& e.getDragboard().hasString()
&& e.getDragboard().getString().equals("circle")) {
e.acceptTransferModes(TransferMode.COPY);
}
coordinateLabel.setText(String.format("[%.1f, %.1f]", e.getX(), e.getY()));
});
canvas.setOnDragDropped(e -> {
if (e.getDragboard().hasString()
&& e.getDragboard().getString().equals("circle")) {
gc.setFill(draggingColor);
gc.fillOval(e.getX()-25, e.getY()-25, 50, 50);
draggingColor = null ;
e.setDropCompleted(true);
}
});
BorderPane root = new BorderPane();
root.setCenter(canvas);
root.setLeft(dragFromPane);
root.setBottom(coordinateLabel);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private void drawCircle(Color c, Pane dragFromPane, int index) {
Circle circle = new Circle(60, 60*(index+1), 25);
circle.setFill(c);
dragFromPane.getChildren().add(circle);
circle.setOnDragDetected(e -> {
Dragboard db = circle.startDragAndDrop(TransferMode.COPY);
db.setDragView(circle.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.putString("circle");
db.setContent(cc);
draggingColor = c ;
});
}
public static void main(String[] args) {
launch(args);
}
}
At http://docs.oracle.com/javafx/2/charts/pie-chart.htm Oracle suggests using
caption.setTranslateX(e.getSceneX());
caption.setTranslateY(e.getSceneY());
to place a Label where the mouse was clicked.. But this does not work at all. See this print screen for proof:
In the code for the example you cite, the PieChart and caption Label are both placed directly in a Group which is the root of the scene. The position of the Label before applying transformations is therefore (0,0) (the top left of the Scene), and so translating it by (e.getSceneX(), e.getSceneY()) moves it to the position of the mouse.
If your layout is different, then the same computation will not necessarily work. For a more general solution, put the chart and caption in a Group, and then call sceneToLocal(...) on the Group to translate the scene coordinates to the correct coordinates in the Group:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class PieChartSample extends Application {
#Override
public void start(Stage stage) {
BorderPane root = new BorderPane();
ObservableList<PieChart.Data> pieChartData =
FXCollections.observableArrayList(
new PieChart.Data("Grapefruit", 13),
new PieChart.Data("Oranges", 25),
new PieChart.Data("Plums", 10),
new PieChart.Data("Pears", 22),
new PieChart.Data("Apples", 30));
final PieChart chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
final Label caption = new Label("");
caption.setTextFill(Color.DARKORANGE);
caption.setStyle("-fx-font: 24 arial;");
Group chartWithCaption = new Group(chart, caption);
for (final PieChart.Data data : chart.getData()) {
data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
Point2D locationInScene = new Point2D(e.getSceneX(), e.getSceneY());
Point2D locationInParent = chartWithCaption.sceneToLocal(locationInScene);
caption.relocate(locationInParent.getX(), locationInParent.getY());
caption.setText(String.valueOf(data.getPieValue()) + "%");
}
});
}
root.setCenter(chartWithCaption);
// Just some stuff to change the overall layout:
HBox controls = new HBox(5);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
controls.getChildren().addAll(new Label("Some other stuff here"), new TextField(), new Button("OK"));
root.setTop(controls);
root.setPadding(new Insets(0, 0, 10, 40));
root.setLeft(new Circle(25, Color.SALMON));
Scene scene = new Scene(root);
stage.setTitle("Imported Fruits");
stage.setWidth(600);
stage.setHeight(500);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I want to create a refresh button for my webView that will be on top of the view (even if it hides part of the view), when I place the button on the Grid Pane it pushes the webView down or to the side (depends where I place the button)
How can I place my "refresh" button on top of the webView and not move it aside?
import java.util.List;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class webviewbrowser extends Application {
#Override public void start(Stage primaryStage) throws Exception {
Pane root = new WebViewPane();
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.setFullScreen(true);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
/**
* Create a resizable WebView pane
*/
public class WebViewPane extends Pane {
public WebViewPane() {
VBox.setVgrow(this, Priority.ALWAYS);
setMaxWidth(Double.MAX_VALUE);
setMaxHeight(Double.MAX_VALUE);
WebView view = new WebView();
view.setMinSize(500, 400);
view.setPrefSize(500, 400);
final WebEngine eng = view.getEngine();
eng.load("http://google.com");
//final TextField locationField = new TextField("http://www.google.com");
//locationField.setMaxHeight(Double.MAX_VALUE);
Button goButton = new Button("Refresh");
goButton.setDefaultButton(true);
EventHandler<ActionEvent> goAction = new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
eng.reload();
}
};
goButton.setOnAction(goAction);
GridPane grid = new GridPane();
grid.setVgap(0);
grid.setHgap(0);
GridPane.setConstraints(goButton,2,0,2,1, HPos.RIGHT, VPos.BOTTOM, Priority.ALWAYS, Priority.ALWAYS);
GridPane.setConstraints(view, 0, 0, 2, 1, HPos.CENTER, VPos.CENTER, Priority.SOMETIMES, Priority.SOMETIMES);
grid.getColumnConstraints().addAll(
new ColumnConstraints(100, 100, Double.MAX_VALUE, Priority.ALWAYS, HPos.CENTER, true),
new ColumnConstraints(40, 40, 40, Priority.NEVER, HPos.CENTER, true)
);
grid.getChildren().addAll(goButton, view);
getChildren().add(grid);
}
#Override protected void layoutChildren() {
List<Node> managed = getManagedChildren();
double width = getWidth();
double height = getHeight();
double top = getInsets().getTop();
double right = getInsets().getRight();
double left = getInsets().getLeft();
double bottom = getInsets().getBottom();
for (int i = 0; i < managed.size(); i++) {
Node child = managed.get(i);
layoutInArea(child, left, top,
width - left - right, height - top - bottom,
0, Insets.EMPTY, true, true, HPos.CENTER, VPos.CENTER);
}
}
}
}
If you want to stack one component on top of another, don't use a GridPane for layout, instead use a parent that allows layout components to be placed on top of one another. For example, a standard Pane, a StackPane, Group or Region. In these stacked style layouts, the components are rendered in order of the child component's position in the parent's child list.
In your sample code you are already extending Pane, so get rid of all of the grid code and just do:
getChildren().addAll(view, goButton);
instead of:
grid.getChildren().addAll(goButton, view);
Modify the layout properties of your goButton to position it within a parent which does not manage the layout position of it's children, e.g. you can call goButton.relocate(xPos, yPos).
You have some custom stuff in the layoutChildren method you override that may mess up the default Pane layout processing logic. Overriding layoutChildren is more of an advanced layout topic and I wouldn't advise it for beginners.
Here is an updated sample you could look at which uses some of the concepts mentioned in this answer.
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.web.*;
import javafx.stage.Stage;
public class WebViewBrowser extends Application {
#Override public void start(Stage stage) throws Exception {
stage.setScene(new Scene(new WebViewPane("http://google.com")));
stage.setFullScreen(true);
stage.show();
}
public static void main(String[] args) { launch(args); }
}
class WebViewPane extends Pane {
final WebView view = new WebView();
final Button goButton = createGoButton(view.getEngine());
public WebViewPane(String initURL) {
view.getEngine().load(initURL);
getChildren().addAll(
view,
goButton
);
initLayout();
}
private Button createGoButton(final WebEngine eng) {
Button go = new Button("Refresh");
go.setDefaultButton(true);
go.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
eng.reload();
}
});
return go;
}
private void initLayout() {
setMinSize(500, 400);
setPrefSize(1024, 768);
view.prefWidthProperty().bind(widthProperty());
view.prefHeightProperty().bind(heightProperty());
goButton.setLayoutX(10);
goButton.layoutYProperty().bind(
heightProperty().subtract(20).subtract(goButton.heightProperty())
);
}
}