Resources and examples for this Popup widget are vague.
Suppose I have a random Node somewhere on the stage. How do I open a Popup exactly under it (e.g. like a dropdown menu, but with other nodes inside it).
I'm trying to avoid boilerplate code (i.e. fine-tuning the position myself).
Update 1:
Either Point2D point = node.localToScene(0.0, 0.0); does not work as I imagine it should, or I'm using it wrong.
Update 2:
See here a simple example, but lacking the functionality I'm needing
Let's say you have the node node
you can get its position by
Point2D point = node.localToScene(0.0, 0.0);
// now get point.getX() and point.getY() here
Considering the example that you have given (in Update 2):
I removed this bit:
popup.setX(300);
popup.setY(200);
and modified this code:
show.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
popup.show(primaryStage);
Point2D point = show.localToScene(0.0, 0.0);
popup.setX(primaryStage.getX() + point.getX());
popup.setY(primaryStage.getY() + point.getY() + 40);
// this 40 could be show.getPrefHeight() if height of button is set
}
});
Since Popup is a separate window, you need to set its position by adding the offset of the Stage.
Related
I'm trying to make app that spawns new draggable nodes on pretty big pane(which is child of scrollpane), but this node should be spawned in the center of the screen.
Q is: Are there any methods to pre-set X,Y coordinates of these new imageviews?
For example:
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Bounds bounds = scrollPane.getBoundsInLocal();
Bounds screenBounds = scrollPane.localToScreen(bounds);
int mX = (int) screenBounds.getMinX();
int mY = (int) screenBounds.getMinY();
Rectangle2D primScreenBounds = Screen.getPrimary().getVisualBounds();
int x = (int) ((primScreenBounds.getWidth() - mX) /4);
int y = (int) ((primScreenBounds.getHeight() - mY) /4);
/*
System.out.println("X coords:" +x);
System.out.println("Y coords:" +y);
*/
pane.getChildren().addAll(new ImageView(imgvw.getImage()));
//somehow set coordinates of new ImageView
}
});
The way you'd set the position deĀ“pends on the layout you use. However assuming you use Pane, you could use
layoutX and layoutY or
translateX and translateY
ImageView iv = new ImageView(imgvw.getImage());
iv.setLayoutX(x);
iv.setLayoutY(y);
pane.getChildren().add(iv);
Other layouts, e.g. StackPane automatically set layoutX and layoutY. In this case you could set managed to false
iv.setManaged(false);
or use the appropriate parameters for the layout type.
This might depend on the parent the ImageViews are placed in, though in general you should be able to position Nodes with Region#positionInArea() (Region is superclass of Parent). Note that this is a protected method, meaning you might want to create your own parent (e.g. by extending StackPane for example).
That being said, there are plenty of Parent Nodes, each providing their own unique behavior. Try to make sure that the desired behavior cannot be achieved using any of those before creating your own implementation. (And since you dont specify any positioning behavior, its hard to make recommendations)
I just added the animation part and now whenever I click on the duck it does nothing to the score, but when I comment out the animation code and the duck stays still, it increments just fine.
Full Code
Animation:
KeyValue start = new KeyValue(duck.translateXProperty(), 10);
KeyValue end = new KeyValue(duck.translateXProperty(), 400);
KeyFrame startF = new KeyFrame(Duration.ZERO, start);
KeyFrame endF = new KeyFrame(Duration.seconds(10), end);
Timeline tl = new Timeline(startF, endF);
tl.setCycleCount(Timeline.INDEFINITE);
tl.setAutoReverse(true);
tl.play();
Event Handler for Clicks:
scene.setOnMouseClicked(event -> {
if (duck.contains(event.getX(), event.getY())){
//increment score
incrementScore();
scoreDisplay.setText(""+score+"00");
}
});
Based on what you've said in the question and comments:
when I comment out the animation code and the duck stays still, it increments just fine
Whenever I click the ducks initial position, it increments the score
I'm guessing that your ImageView (the duck)'s bounding box is not getting updated.
From the ImageView docs:
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 image itself moves, and the duck is still considered to be at its initial position, so the contains() method wouldn't work. You'll have to find a different way of figuring out clicks
Perhaps what you could do is use the getTranslateX() property for your contains(), it seems like it'll get the current offset of the visual duck from the geometry duck:
scene.setOnMouseClicked(event -> {
double rawMouseX = event.getX();
double rawMouseY = event.getY();
double normalizedMouseX = rawMouseX - duck.getTranslateX();
double normalizedMouseY = rawMouseY - duck.getTranslateY();
if (duck.contains(normalizedMouseX, normalizedMouseY)){
//increment score
incrementScore();
scoreDisplay.setText(""+score+"00");
}
});
Disclaimer: I have no experience with JavaFX, I'm going mainly off of vanilla Java experience
You needn't calculate the normalized position or the mouse X/Y coordinates. You need to use the getBoundsInParent() method:
if (duck.getBoundsInParent().contains(event.getX(), event.getY())){
//increment score
incrementScore();
scoreDisplay.setText(""+score+"00");
}
Set your mouse handler on the duck, not the scene.
Also, use pickOnBounds to only react when you click on the duck's pixels, not it's bounding box.
duck.setPickOnBounds(false);
duck.setOnMouseClicked(event -> {
incrementScore();
scoreDisplay.setText(score + "00");
});
I'm recently building a javafx program which will work as a school database. I've made a SceneManager class which handles all on screen transitions and other gui tasks at runtime. Now i'm intending to add a back button to every visible window so that the user can get back at the previous window anytime. For that I've added the following method to SceneManager :
public static void addBackButton(Pane from,Pane to){
Node n1 = to.getChildren().get(0);
to.getChildren().remove(n1);
BorderPane fp = new BorderPane();
fp.setLeft(createBackButton(from));
fp.setCenter(n1);
to.getChildren().add(0, fp);
}
public static Button createBackButton(Pane p){
Button bk = new Button("",new ImageView("icons/appbar.arrow.left2.png"));
bk.setOnAction((ActionEvent evt)->{
swapToNext(p);
});
return bk;
}
However the above is not working. Only the button is being shown without the node 0, which is however generally a heading label in my program. I'm unable to find any general solution to this problem, like the method will be valid for anytype of node in 0. Any ideas?
Edit: I removed my old answer, since it wasn't what you were looking for.
Edit 2: Inspiration in the form of headaches.
The red color is the pane.
After a nice long weekend, I realized we could just use an anchor pane.
AnchorPane ap = new AnchorPane(p1,back); //<--- order matters.
AnchorPane.setBottomAnchor(back, 0.0); //<--- 0.0 is the distance from that edge
AnchorPane.setLeftAnchor(back, 0.0);
AnchorPane.setLeftAnchor(p1, 0.0); //<--- we do this to 'bind' the entire pane
AnchorPane.setBottomAnchor(p1, 0.0); //<--- to the anchorpane
AnchorPane.setRightAnchor(p1, 0.0); //<--- this ensures that it will
AnchorPane.setTopAnchor(p1, 0.0); //<--- even show behind the button
p1 is the pane, back is the button itself, and the button contains the link
back to the previous pane.
Here you simply need to return the anchorpane from that addBackButton function. You'll end up with an Anchorpane that contains a button and the previous pane.
I'm going to get more caffeine; I hope I helped. :)
I am trying to achieve effect similar to marquee - line of long (in my case) text which is moved in horizontal axis. I managed to get it work, but I can't call it satisfactory.
My Controller class looks as below:
#FXML
private Text newsFeedText;
(...)
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
TranslateTransition transition = TranslateTransitionBuilder.create()
.duration(new Duration(7500))
.node(newsFeedText)
.interpolator(Interpolator.LINEAR)
.cycleCount(Timeline.INDEFINITE)
.build();
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
int width = gd.getDisplayMode().getWidth();
transition.setFromX(width);
transition.setToX(-width);
transition.play();
}
newsFeedText is binded to some text source which is dynamically updated, so it contains various amount of text.
My code has at least two drawbacks:
Transition goes from -width to +width; width is monitor's resolution width
There will be moments when text will not be visible at all if window is not full-screened.
If text will be longer and newsFeedText width will be greater than monitor's resolution width then transition will disappear "in half" (still being on a screen).
Currently Duration is not dependent on a width of newsFeedText.
Now, it's nothing worng, but if transition's fromX and toX were be dynamically calculated then it will result in various speeds of marquee.
How to get rid of these drawbacks?
I have managed it to work, any recalculations can happen only after transition is stopped so we cannot set its cycleCount to Timeline.INDEFINITE. My requirement was that I could change text inside component so there are fxml wirings:
#FXML
private Text node; // text to marquee
#FXML
private Pane parentPane; // pane on which text is placed
The code which works is:
transition = TranslateTransitionBuilder.create()
.duration(new Duration(10))
.node(node)
.interpolator(Interpolator.LINEAR)
.cycleCount(1)
.build();
transition.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
rerunAnimation();
}
});
rerunAnimation();
where rerunAnimation() is:
private void rerunAnimation() {
transition.stop();
// if needed set different text on "node"
recalculateTransition();
transition.playFromStart();
}
and recalculateTransition() is:
private void recalculateTransition() {
transition.setToX(node.getBoundsInLocal().getMaxX() * -1 - 100);
transition.setFromX(parentPane.widthProperty().get() + 100);
double distance = parentPane.widthProperty().get() + 2 * node.getBoundsInLocal().getMaxX();
transition.setDuration(new Duration(distance / SPEED_FACTOR));
}
You should be able to do this by listening to your scene's widthProperty. You can either access this via newsFeedText.getScene().widthProperty() or get a reference from your main class and expose it from there or pass it to a method or constructor to access within your class that declares newsFeedText.
The benefit of this approach is that now your logic is dependent upon the width of your scene (a dynamic dependency) rather than the width of your monitor (a static dependency). Note that I have not tested this approach but at the moment see no reason (perhaps naively) it shouldn't work.
As for your duration dependency, you can solve that by performing some sort of calculation based on the length of the text in newsFeedText. Something like Duration.seconds(newsFeedText.get Text().length()/denominator) where denominator is some value you specify (such as 7500, as in your code). This will make your duration dynamically computed based on the length of your text.
If you want to operate with the width of newsFeedText itself, rather than the length of its text, then simply replace newsFeedText.getText().length() with newsFeedText.getWidth(). Ensure you perform this computation after newsFeedText has been laid out so a call to get its width returns the actual width. You can also replace the call with any of getPrefWidth(), getMinWidth(), or getMaxWidth().
I am trying to achieve effect similar to marquee - line of long (in my case) text which is moved in horizontal axis. I managed to get it work, but I can't call it satisfactory.
My Controller class looks as below:
#FXML
private Text newsFeedText;
(...)
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
TranslateTransition transition = TranslateTransitionBuilder.create()
.duration(new Duration(7500))
.node(newsFeedText)
.interpolator(Interpolator.LINEAR)
.cycleCount(Timeline.INDEFINITE)
.build();
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
int width = gd.getDisplayMode().getWidth();
transition.setFromX(width);
transition.setToX(-width);
transition.play();
}
newsFeedText is binded to some text source which is dynamically updated, so it contains various amount of text.
My code has at least two drawbacks:
Transition goes from -width to +width; width is monitor's resolution width
There will be moments when text will not be visible at all if window is not full-screened.
If text will be longer and newsFeedText width will be greater than monitor's resolution width then transition will disappear "in half" (still being on a screen).
Currently Duration is not dependent on a width of newsFeedText.
Now, it's nothing worng, but if transition's fromX and toX were be dynamically calculated then it will result in various speeds of marquee.
How to get rid of these drawbacks?
I have managed it to work, any recalculations can happen only after transition is stopped so we cannot set its cycleCount to Timeline.INDEFINITE. My requirement was that I could change text inside component so there are fxml wirings:
#FXML
private Text node; // text to marquee
#FXML
private Pane parentPane; // pane on which text is placed
The code which works is:
transition = TranslateTransitionBuilder.create()
.duration(new Duration(10))
.node(node)
.interpolator(Interpolator.LINEAR)
.cycleCount(1)
.build();
transition.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
rerunAnimation();
}
});
rerunAnimation();
where rerunAnimation() is:
private void rerunAnimation() {
transition.stop();
// if needed set different text on "node"
recalculateTransition();
transition.playFromStart();
}
and recalculateTransition() is:
private void recalculateTransition() {
transition.setToX(node.getBoundsInLocal().getMaxX() * -1 - 100);
transition.setFromX(parentPane.widthProperty().get() + 100);
double distance = parentPane.widthProperty().get() + 2 * node.getBoundsInLocal().getMaxX();
transition.setDuration(new Duration(distance / SPEED_FACTOR));
}
You should be able to do this by listening to your scene's widthProperty. You can either access this via newsFeedText.getScene().widthProperty() or get a reference from your main class and expose it from there or pass it to a method or constructor to access within your class that declares newsFeedText.
The benefit of this approach is that now your logic is dependent upon the width of your scene (a dynamic dependency) rather than the width of your monitor (a static dependency). Note that I have not tested this approach but at the moment see no reason (perhaps naively) it shouldn't work.
As for your duration dependency, you can solve that by performing some sort of calculation based on the length of the text in newsFeedText. Something like Duration.seconds(newsFeedText.get Text().length()/denominator) where denominator is some value you specify (such as 7500, as in your code). This will make your duration dynamically computed based on the length of your text.
If you want to operate with the width of newsFeedText itself, rather than the length of its text, then simply replace newsFeedText.getText().length() with newsFeedText.getWidth(). Ensure you perform this computation after newsFeedText has been laid out so a call to get its width returns the actual width. You can also replace the call with any of getPrefWidth(), getMinWidth(), or getMaxWidth().