I use a Pane to print some Label by adding them to its children list. For some reason, I need to bind a label width, but when I do something like :
public class LabelTestApp extends Application
{
private Pane root = new Pane();
private Label myLabel = new Label("test");
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
root.getChildren.add(myLabel);
System.out.println(myLabel.widthProperty());
stage.setScene(new Scene(root, 500, 500);
stage.show();
}
}
It seems like the Label doesn't update its width. When I print it with :
System.out.println(myLabel.widthProperty());
I get :
ReadOnlyDoubleProperty [bean : Label#xxxxxxxx[styleClass = label]'test', name:width, value: 0.0]
So I suppose that the width of a Label is calculated during Layout, and since a Pane doesn't have any Layout manager, it is never calculated, am I right ?
If so, how am I supposed to get this width ?
If not, the problem is somewhere else, and I can't figure out where.
For informations : I need to use a Pane to draw some Shape, and control their placement. The purpose of the Label is to print some informations on the shapes. I need the width of the Label because they are some Line on the shapes, and I don't want them to cross the Label.
Don't hesitate to ask for further informations.
JavaFX only lays out components which are visible.
You can see this if you try something like this:
final Label myLabel = new Label("test");
final Pane myPane = new Pane(myLabel);
System.out.println(myLabel.getWidth());
stage.setScene(new Scene(myPane, 640, 480));
stage.show();
System.out.println(myLabel.getWidth());
which prints
0.0
19.5
As you can see here, the label's size is not set until stage.show() is called. You can also override Pane to see layoutChildren() is called for the first time at this point.
As a sidenote, if you want to find out the width of some text without showing it in a Stage, you can make a Text node (instead of a label) and get its preferred width, e.g.
new Text("test").prefWidth(0)
Hope this helps!
Related
I am new to java fx. I am creating a basic GUI which is meant to look like this:
However, when I try my image looks like this:
I am unsure as to why there is a large gap between the two buttons. I am using the grid pane format.
Here is my code:
public class Main extends Application {
#Override
public void start(Stage stage) {
//Creating Text field
TextField textField1 = new TextField();
//Creating Buttons
Button submit = new Button("Submit");
Button cancel = new Button("Cancel");
//Creating a Grid Pane
GridPane gridPane = new GridPane();
//Setting size for the pane
gridPane.setMinSize(200, 100);
//Setting the padding
gridPane.setPadding(new Insets(10, 10, 10, 10));
//Setting the vertical and horizontal gaps between the columns
gridPane.setVgap(5);
gridPane.setHgap(1);
//Setting the Grid alignment
gridPane.setAlignment(Pos.CENTER);
//Arranging all the nodes in the grid
gridPane.add(textField1, 0, 0);
gridPane.add(submit, 0, 1);
gridPane.add(cancel, 1,1);
//Creating a scene object
Scene scene = new Scene(gridPane);
//Setting title to the Stage
stage.setTitle("Simple Form");
//Adding scene to the stage
stage.setScene(scene);
//Displaying the contents of the stage
stage.show();
}
public static void main(String args[]){
launch(args);
}
}
Help would be appreciated :-)
What happens here is that your TextField is set in the top-left corner (0,0), but only spans a single column. You can see that the cancel Button, which is in the bottom right corner (1,1), starts where the TextField stops (if you look only at the x-position).
There is a debug option for GridPanes so you can visualize more what happens. Just call
gridPane.setGridLinesVisible(true):
The solution for this is to set the TextField to obtain/span 2 columns. You can do so by either calling:
GridPane.setColumnSpan(textField1, 2);
Or use a different add method:
gridPane.add(textField1, 0, 0, 2, 1);
See https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/GridPane.html for more information about how the GridPane works.
Problem: I have Swing content that needs to be displayed using a JavaFX stage. The Swing content needs to be wrapped within a ScrollPane to preserve normal Swing sizing when the stage is resized.
Expected Behavior: When the stage is first shown, it needs to be sized automatically to fit the Swing content (i.e. without needing scrolling to view).
Actual Behavior: Either the stage shows the Swing content partially (if the stage is shown before the node is populated) or not shown at all (if the stage is shown after the node is populated).
If stage is shown before adding Swing-node content:
If stage is shown after adding Swing-node content and then resized:
Code: This may include a few redundant statements as part of my various attempts.
public static void main(final String[] args) {
Platform.startup(() -> {});
Platform.runLater(() -> doSwingNodeTest());
}
protected static void doSwingNodeTest() {
final Stage stage = new Stage();
final ScrollPane root = new ScrollPane();
final Scene scene = new Scene(root);
final SwingNode node = new SwingNode();
stage.setScene(scene);
stage.show(); // Required here for any content to
//display without resizing.
stage.sizeToScene();
final JPanel j = new JPanel();
j.setLayout(new BorderLayout());
j.add(new JLabel("Some text"), BorderLayout.NORTH);
j.add(new JButton("Some button"), BorderLayout.CENTER);
j.add(new JButton("East button"), BorderLayout.EAST);
node.setContent(j);
j.revalidate();
node.autosize();
root.setContent(node);
stage.setOnShown(e -> {
j.doLayout();
stage.sizeToScene();
node.autosize();
root.autosize();
});
// stage.show(); // Causes an 0-sized window.
}
Anyone have any clues as to what is going on and how to resolve this?
Thanks!
ScrollPane, together with SwingNode, can be quite troublesome to control. This is what probably happened:
The ScrollPane is the root node, so the Scene needs to tell the ScrollPane how much space it can give to the ScrollPane.
The Scene tells the ScrollPane that it can use any amount of space (Scene will try to resize itself during the initial layout).
The ScrollPane needs to tell its children to do its layout. Since the ScrollPane can spawn scroll bars when needed, it will simply tell the children to use as much space as it wants.
The children, which is the SwingNode, has no content to render in the layout phase, and it will tell ScrollPane that it needs to use X amount of space, which is some kind of default.
The ScrollPane resizes itself to that size.
The ScrollPane tells the Scene that it needs that much space.
The Scene and Stage sets their sizes accordingly.
Stage becomes visible, and all contents start to render.
SwingNode starts rendering the Swing components.
The Swing components need bigger space and request from SwingNode.
SwingNode requests for space from ScrollPane.
ScrollPane creates the scroll bars and continues to render its children in the small space.
As for solution, I did not manage to find any decent ones. One way is to set a fixed size somewhere (Scrollpane, Scene or SwingNode). The other way is to move the scrolling into Swing.
An alternative solution would be to simply ask the SwingNode how big its content should be via getPreferredSize() and then use that value to set the size of the stage. I'm currently investigating why fudge variable is needed.
protected static void doSwingNodeTest() {
final Stage stage = new Stage();
final SwingNode node = new SwingNode();
stage.setScene(new Scene(new ScrollPane(node)));
final JPanel j = new JPanel();
j.setLayout(new BorderLayout());
j.add(new JLabel("Some text"), BorderLayout.NORTH);
j.add(new JButton("Some button"), BorderLayout.CENTER);
j.add(new JButton("East button"), BorderLayout.EAST);
node.setContent(j);
int fudge = 10;
stage.setWidth(fudge + node.getContent().getPreferredSize().getWidth());
stage.setHeight(fudge + node.getContent().getPreferredSize().getHeight());
stage.show();
}
Can a set of lines be drawn on a text area component which would look like this .
I then need to be able to type in text over them . These lines also need to be erased and redrawn
Consider drawing lines on a Pane like so:
public class StageTest extends Application{
private static final double WIDTH = 100, HEIGHT = 60;
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Test Stage");
Label label = new Label("Some text ");
label.setStyle("-fx-background-color:TRANSPARENT");
label.setAlignment(Pos.CENTER);
label.setPrefSize(WIDTH, HEIGHT);
Pane linesPane = getPane(label);
StackPane root = new StackPane(linesPane, label);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private Pane getPane(Label label) {
Pane pane = new Pane();
pane.setStyle("-fx-background-color:WHITE");
Line blueLine = new Line();
blueLine.setStroke(Color.BLUE);
blueLine.startXProperty().bind(label.layoutXProperty());
blueLine.startYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.333)));
blueLine.endXProperty().bind(label.layoutXProperty().add(label.widthProperty()));
blueLine.endYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.333)));
Line redLine = new Line();
redLine.setStroke(Color.RED);
redLine.startXProperty().bind(label.layoutXProperty());
redLine.startYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.666)));
redLine.endXProperty().bind(label.layoutXProperty().add(label.widthProperty()));
redLine.endYProperty().bind(label.layoutYProperty().add(label.heightProperty().multiply(.666)));
pane.getChildren().addAll(blueLine, redLine);
return pane;
}
public static void main(String[] args) {
launch(args);
}
}
You may want to look at using the background property of TextArea.
new TextArea().setBackground(new Background(new BackgroundImage(myImage,BackgroundRepeat.NO_REPEAT,BackgroundRepeat.NO_REPEAT,BackgroundPosition.CENTER,BackgroundSize.DEFAULT)));
This code here is assuming you can get those lines as an image.
You can find more info for backgrounds here: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Background.html
If you want the background image to change and be dynamic for your current needs, you have two options.
Just use a Canvas for the whole project. Paint the lines onto the canvas first, and then paint the letters on top of this. This could be better as it would allow you to customize your project to however you would like it, but it would take a bit more code and thinking to do.
Use the TextArea, and for the BackgroundImage, use a Snapshot of another Canvas. You can use a Canvas to draw the lines however you would like, and then convert it to an image using Snapshot.
WritableImage i = canvas.snapshot(new SnapshotParameters(), null);
Then, using this image, you can use that as the background of the TextArea by using BackgroundImage.
My title is badly worded because my problem is very hard to describe, so I drew an image for it:
I have an ImageView object which represents a pile of cards (not poker cards but just used them as an example). When this image view is clicked, I need a window to popup that features a ScrollPane and shows them all the card objects that are in the linked list. When the user clicks anywhere outside of the window (and later on, any right mouse button click) the scrollpane window needs to close.
Ways that I have already tried:
Scene with APPLICATION_MODAL. Then did Scene.showAndWait(). I didn't like this method because it made another application on the user's taskbar. It also felt clunky and just bad.
Changed my root pane to a StackPane, then added this Scrollpane to the stackpane when the user clicked on the deck. This for some reason was really slow and seemed really obtrusive. It was also annoying because my alternate class needed to have access to the root pane (since when it closes, it needs to go to the root StackPane and call .remove() on itself).
Are there any other better ways to accomplish this? My application is going to have many of these piles and so this framework needs to work very well.
I would still propose to open a new Stage with some restrictions to solve your issues with this approach.
You can use the initOwner property of the Stage to have another Stage as owner, therefore no other icon will appear on the taskbar.
You can use the initStyle property with TRANSPARENT or UNDECORATED StageStlye, this will ensure that only the content is visible.
And in the end you can use the focusedProperty to check whether the Stage lost focus to close it.
Example
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
Button b = new Button("Open deck");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Stage popupStage = new Stage();
popupStage.initOwner(primaryStage);
popupStage.initStyle(StageStyle.UNDECORATED);
Scene sc = new Scene(new ScrollPane(), 300, 300);
popupStage.setScene(sc);
popupStage.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
if(!newValue)
popupStage.close();
}
});
popupStage.show();
}
});
root.setCenter(b);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
It is also possible of course to not open it in a new Stage, but draw a ScrollPane inside the current Stage which overlaps the content of the Stage using for example an AnchorPane or Group as root, but the first solution has the advantage that you are not bound to the dimensions of main Stage (the popup can have any size that you want).
You can achieve this with a low level system hook that catches the mouse events.
http://kra.lc/blog/2016/02/java-global-system-hook/ or https://github.com/kwhat/jnativehook/releases
I hope that is what you needed, otherwise i got your question wrong.
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.