I would like to create a simple JavaFx class that shows the user a translucent rectangle (say an arbitrary 50% transparency) covering the users screen. It should simply allow me to get the Point of a mouse click event. This sounds trivial, but when I create transparent windows they always seem to be transparent to mouse events rather than just my requirement of semi-transparent visibility. The mouse event is never triggered.
I've used setMouseTransparent(false) on the rectangle and the root pane, but this makes no difference. I'd be really grateful if somebody could indicate any errors/misconceptions.
Here's the trivial test class I have created:
public class ClickScreen implements MouseListener {
private ClickScreenListener listener;
private Stage window;
private Point point;
public ClickScreen(ClickScreenListener listener) {
// Get screen size
Rectangle2D r = Screen.getPrimary().getBounds();
// Something to put stuff in
StackPane root = new StackPane();
// Translucent rectangle on the pane
Rectangle rectangle = new Rectangle(r.getWidth(), r.getHeight());
rectangle.setFill(Color.rgb(183, 183, 183, 0.5));
root.getChildren().add(rectangle);
Scene scene = new Scene(root, r.getWidth(), r.getHeight());
scene.setFill(null);
window = new Stage();
window.initStyle(StageStyle.TRANSPARENT);
window.setTitle("Click drop location");
window.setScene(scene);
this.listener = listener;
}
public Point getLocation(){
return point;
}
#Override
public void mouseClicked(MouseEvent e) {
point = e.getLocationOnScreen();
listener.screenClicked(point);
}
}
Edit:
A simpler example of the transparency issue I am experiencing is from this Hello World! example. When I mouse over the button, it's about 50:50 chance of clicking the button or just clicking "through" and giving focus to the underlying window (which is usually eclipse in my case). Would love you thoughts on this.
public class HelloWorld extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
scene.setFill(null);
primaryStage.setScene(scene);
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.show();
}
}
Check your Imports
You are using some kind of weird setup where you are mixing AWT/Swing classes and JavaFX classes, which really isn't advised (and doesn't work at all in the combination and manner you have used). Just be careful in your JavaFX programs not to import any java.awt.* or javax.swing.* classes unless you really know what you are doing in mixing code for two different toolkits.
Sample Solution
Here is a sample solution which imports only JavaFX classes and utilizes JavaFX events, but otherwise tries to stick to the coding/callback style of the sample code in your question. (The sample could be further simplified through use of Java 8 lambdas).
import javafx.application.Application;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.*;
public class ClickListenerSample
extends Application
implements ClickScreenListener {
private Label clickFeedbackLabel = new Label("");
#Override public void start(Stage stage) {
Button listen = new Button("listen");
listen.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
new ClickScreen(ClickListenerSample.this);
}
});
VBox layout = new VBox(10);
layout.getChildren().setAll(
listen,
clickFeedbackLabel
);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout, 100, 80));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#Override public void screenClicked(Point2D point) {
clickFeedbackLabel.setText(point.getX() + ", " + point.getY());
}
}
interface ClickScreenListener {
void screenClicked(Point2D point);
}
class ClickScreen {
private ClickScreenListener listener;
private Stage window;
private Point2D point;
public ClickScreen(ClickScreenListener listener) {
// Get screen size
Rectangle2D r = Screen.getPrimary().getBounds();
// Something to put stuff in
StackPane root = new StackPane();
root.setStyle("-fx-background-color: null;");
// Translucent rectangle on the pane
Rectangle rectangle = new Rectangle(r.getWidth(), r.getHeight());
rectangle.setFill(Color.rgb(183, 183, 183, 0.5));
root.getChildren().add(rectangle);
Scene scene = new Scene(root, r.getWidth(), r.getHeight());
scene.setFill(null);
window = new Stage();
window.initStyle(StageStyle.TRANSPARENT);
window.setTitle("Click drop location");
window.setScene(scene);
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
point = new Point2D(event.getScreenX(), event.getScreenY());
listener.screenClicked(point);
window.hide();
}
});
window.show();
this.listener = listener;
}
public Point2D getLocation(){
return point;
}
}
Related
I have this simple class:
Test.java:
import javafx.animation.FadeTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application {
#Override
public void start(Stage stage) throws Exception {
Pane pane = new Pane();
Button testButton = new Button("Test");
testButton.setStyle("-fx-background-color: green;");
pane.getChildren().add(testButton);
pane.setStyle("-fx-background-color: red;");
FadeTransition transition = new FadeTransition(Duration.millis(5000), pane);
transition.setFromValue(1.0);
transition.setToValue(0.0);
transition.setCycleCount(Timeline.INDEFINITE);
transition.setAutoReverse(true);
transition.play();
Scene scene = new Scene(pane, 500, 500);
stage.setMinWidth(500);
stage.setMinHeight(500);
stage.setTitle("Test");
stage.setResizable(false);
stage.setScene(scene);
stage.show();
}
}
It looks like this:
when it fades however it becomes this:
How do I make it so that the fade transition only affects the red background and doesn't affect the green button?
So that it looks like this:
using stackpane
You can use StackPane as root and both : Pane and Button children of stackpane . Button is not affected by transition since is no longer child of pane .
if you need different aligments for different nodes you can use static method setAligment from StackPane class , wich requires a child node and position as arguments
public class App extends Application {
#Override
public void start(Stage stage) throws Exception {
Pane pane = new Pane();
Button testButton = new Button("Test");
testButton.setStyle("-fx-background-color: green;");
StackPane stackPane = new StackPane(pane,testButton);
stackPane.setAlignment(Pos.TOP_LEFT);
pane.setStyle("-fx-background-color: red;");
FadeTransition transition = new FadeTransition(Duration.millis(5000), pane);
transition.setFromValue(1.0);
transition.setToValue(0.0);
transition.setCycleCount(Timeline.INDEFINITE);
transition.setAutoReverse(true);
transition.play();
Scene scene = new Scene(stackPane, 500, 500);
stage.setMinWidth(500);
stage.setMinHeight(500);
stage.setTitle("Test");
stage.setResizable(false);
stage.setScene(scene);
stage.show();
}
}
This uses the alternative approach suggested by Slaw in the comments:
you could also animate the background color. Though it's a little complicated, since you can't animate the background property directly. You have to animate the color and set the background yourself every time the color changes (Color implements Interpolatable).
I also would prefer Giovanni's solution over this. This solution is mainly offered as an example of how you might create a custom transition.
There are two solutions provided.
The key to these solutions is the same in both cases, extend Transition and override the interpolate method to set the required background fill for a given interpolation value.
Interpolates between two colors (the start color and a transparent color).
protected void interpolate(double frac) {
Color cur = from.interpolate(to, frac);
target.setBackground(BackgroundUtil.createBackground(cur));
}
OR
Interpolates just the opacity portion of a single color.
protected void interpolate(double frac) {
Color cur = Color.color(
color.getRed(),
color.getGreen(),
color.getBlue(),
color.getOpacity() * frac
);
target.setBackground(BackgroundUtil.createBackground(cur));
}
Executable Example
The output of the example is similar to the screenshot in Giovanni's solution.
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BackgroundFade extends Application {
#Override
public void start(Stage stage) throws Exception {
Button testButton = new Button("Test");
testButton.setStyle("-fx-background-color: green;");
Pane pane = new Pane(testButton);
pane.setPrefSize(500, 500);
pane.setBackground(
BackgroundUtil.createBackground(Color.RED)
);
//applyBackgroundColorTransition(pane);
applyBackgroundOpacityTransition(pane);
stage.setScene(new Scene(pane));
stage.show();
}
private void applyBackgroundColorTransition(Pane pane) {
BackgroundColorTransition backgroundColorTransition = new BackgroundColorTransition(
Duration.seconds(5),
pane,
Color.RED,
Color.TRANSPARENT
);
backgroundColorTransition.setCycleCount(Transition.INDEFINITE);
backgroundColorTransition.setAutoReverse(true);
backgroundColorTransition.play();
}
private void applyBackgroundOpacityTransition(Pane pane) {
BackgroundOpacityTransition backgroundOpacityTransition = new BackgroundOpacityTransition(
Duration.seconds(5),
pane,
Color.RED
);
backgroundOpacityTransition.setCycleCount(Transition.INDEFINITE);
backgroundOpacityTransition.setAutoReverse(true);
backgroundOpacityTransition.play();
}
}
class BackgroundColorTransition extends Transition {
private final Region target;
private final Color from;
private final Color to;
public BackgroundColorTransition(Duration duration, Region target, Color from, Color to) {
setCycleDuration(duration);
this.target = target;
this.from = from;
this.to = to;
}
#Override
protected void interpolate(double frac) {
Color cur = from.interpolate(to, frac);
target.setBackground(BackgroundUtil.createBackground(cur));
}
}
class BackgroundOpacityTransition extends Transition {
private final Region target;
private final Color color;
public BackgroundOpacityTransition(Duration duration, Region target, Color color) {
setCycleDuration(duration);
this.target = target;
this.color = color;
}
#Override
protected void interpolate(double frac) {
Color cur = Color.color(
color.getRed(),
color.getGreen(),
color.getBlue(),
color.getOpacity() * frac
);
target.setBackground(BackgroundUtil.createBackground(cur));
}
}
class BackgroundUtil {
public static Background createBackground(Color color) {
return new Background(
new BackgroundFill(
color, null, null
)
);
}
}
I have a code written using javafx and java 11. As I showed in the below code snippet, when I move my ImageView to right of the scene, The ImageView disappears.
At first:
and then after moving it using arrow keys with the help of scene event listener:
then:
and finally:
I don't use fxml. Here is my demo which has the problem too.
public class HelloApplication extends Application {
public String fetchResource(String path) {
return Objects.requireNonNull(getClass().getResource(path)).toString();
}
#Override
public void start(Stage stage) throws IOException {
Rectangle r = new Rectangle(100, 100);
ImageView spaceShip = new ImageView(fetchResource("spaceShip.png"));
spaceShip.setFitHeight(100);
spaceShip.setFitWidth(100);
spaceShip.setX(0);
spaceShip.setY(0);
EventHandler<KeyEvent> keyListener = event -> {
if (event.getCode() == KeyCode.RIGHT) {
spaceShip.setX(spaceShip.getX() + 20);
}
};
Group game = new Group(spaceShip);
Scene scene = new Scene(game, 1840, 1080);
scene.setOnKeyPressed(keyListener);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I should mention that I create a rectangle and check the scenario with that, And there was not any problem. I think there is some problem with ImageView.
I couldn't reproduce the issue with your code, but I could with SceneBuilder. You may get the desired result if you wrap the Group in a Pane (at least it works in SceneBuilder).
Group game = new Group(spaceShip);
Pane pane = new Pane(game);
Scene scene = new Scene(pane, 1840, 1080);
stage.setScene(scene);
stage.show();
I tried almost everything, but the mouse drag events are not firing, like explained here:
https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/input/MouseDragEvent.html
Here is a minimal example, so you can try it out (I am using Java 11 with JavaFX 11.0.2):
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class Main extends Application {
private double mouseClickPositionX, mouseClickPositionY, currentRelativePositionX, currentRelativePositionY;
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Hello World");
BorderPane mainBorderPane = new BorderPane();
BorderPane centerBorderPane = new BorderPane();
FlowPane flowPane = new FlowPane();
GridPane gridPane = new GridPane();
Button button1 = new Button("button1");
gridPane.add(button1, 0, 0);
flowPane.getChildren().add(gridPane);
centerBorderPane.setCenter(flowPane);
HBox hbox = new HBox();
TilePane tilePane = new TilePane();
Button button2 = new Button("button2");
tilePane.getChildren().add(button2);
hbox.getChildren().add(tilePane);
mainBorderPane.setCenter(centerBorderPane);
centerBorderPane.setBottom(hbox);
// button2 event handlers
button2.setOnMousePressed(event -> {
mouseClickPositionX = event.getSceneX();
mouseClickPositionY = event.getSceneY();
currentRelativePositionX = button2.getTranslateX();
currentRelativePositionY = button2.getTranslateY();
button2.setMouseTransparent(true);
});
button2.setOnMouseDragged(event -> {
button2.setTranslateX(currentRelativePositionX + (event.getSceneX() - mouseClickPositionX));
button2.setTranslateY(currentRelativePositionY + (event.getSceneY() - mouseClickPositionY));
});
button2.setOnDragDetected(event -> {
button2.startFullDrag();
});
button2.setOnMouseReleased((event) -> {
button2.setMouseTransparent(false);
});
// button1 event handlers
button1.setOnMouseDragReleased((event) -> {
System.out.println("it works in button1");
});
// gridPane event handlers
gridPane.setOnMouseDragReleased((event) -> {
System.out.println("it works in gridPane");
});
primaryStage.setScene(new Scene(mainBorderPane, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I want to get the reference of button2 either in button1 or in gridPane via setOnMouseDragReleased. There are many nested panes etc. because I wanted to maintain the original project layout structure. I did this because I am not sure if this also can be a reason for the non functioning.
Thanks in advance.
I've ended up manually triggering the events from centerBorderPane to gridPane, using node.fireEvent(event). Also implemented a helper function, which returns the right child node:
private Optional<Node> findNode(Pane pane, double x, double y) {
return pane.getChildren().stream().filter(n -> {
Point2D point = n.sceneToLocal(x, y);
return n.contains(point.getX(), point.getY());
}).findAny();
}
Don't forget to consume the events, so you won't get into an infinite loop.
MOUSE_DRAG_RELEASED fires when a drag ends on this node. For example
centerBorderPane.setOnMouseDragReleased((event) -> {
System.out.println("centerBorderPane drag released");
});
should fire when you drag button2 and the drag ends on centerBorderPane.
To fire an event when the mouse is dragged over button1 use button1.setOnMouseDragged
If you want to propagate a mouse event from parent to its children see this
I'm making an application with JavaFX and Scene Builder.
I have Main class and controller that is called from main class.
I have a button in the controller that has width - 45 and height - 90
Toggle button is wrapped in StackPane and StackPane is wrapped in AnchorPane
How can I change buttonPrefWidth to 25 when application is not fullscreen and change button's prefWidth again to 45 when application is fullscreen?
Or can I change prefWidth of a button according to size of application?
Main Class:
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
Parent root =
FXMLLoader.load(getClass().getResource("/card/resources/fxml/card.fxml"));
Scene scene = new Scene(root, 1600, 600);
primaryStage.setScene(scene);
scene.getStylesheets().add(getClass().getResource("style.css")
.toExternalForm());
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setMaximized(true);
primaryStage.setResizable(true);
primaryStage.getIcons().add(new Image("card/resources/logo-icon.png"));
primaryStage.show();
//adding resize and drag primary stage
ResizeHelper.addResizeListener(primaryStage);
//assign ALT+ENTER to maximize window
final KeyCombination kb = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.CONTROL_DOWN);
scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (kb.match(event)) {
primaryStage.setMaximized(!primaryStage.isMaximized());
primaryStage.setResizable(true);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
Controller:
public class Controller implements Initializable {
#FXML private AnchorPane anchorRow;
#FXML private StackPane hBoxCat0;
#FXML private Button btnPalette;
#FXML
public void initialize(URL location, ResourceBundle resources) {
}
}
EDIT:
#Slaw it doesn't work
If you just want to change the prefWidth between two numbers based on whether or not the window is full screen, then you can listen to the fullScreen property of the Stage the Button belongs to.
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class Controller {
#FXML private Button btnPallet;
#FXML
private void initialize() {
btnPallet.prefWidthProperty().bind(
Bindings.when(Bindings.selectBoolean(btnPallet.sceneProperty(), "window", "fullScreen"))
.then(45)
.otherwise(25)
);
}
}
This will give you warnings at first since the Button won't be part of a Scene or Window at this point of the application. While these warnings are annoying the code should still work.
I am trying to work around this bug in the jdk: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8088624
public class Blubb extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Button btn = new Button("Click");
btn.setTooltip(new Tooltip("Blubb"));
Scene scene = new Scene(new BorderPane(btn), 320, 240);
primaryStage.setScene(scene);
primaryStage.show();
Stage secondStage = new Stage();
secondStage.setScene(new Scene(new BorderPane(new Button("Click")), 320, 240));
//secondStage.initOwner(primaryStage);
secondStage.show();
}
}
If the button on the primary stage is hovered, it will come in front of the second stage. I found that calling initOwner() on a Stage will eliminate this behavior.
Now my problem is following: I have multiple "popups" that have a common owner (the primary stage). Hovering over controls on the primary stage doesn't cause any unexpected behavior after the initOwner() workaround. If you however hover over controls in a popup while another popup was in focus, the hovered popup will steal focus.
Is there a way I can work around this bug for not only the primary stage but also the popups?
UPDATE: turns out my workaround has undesired side-effects. Javadocs for Stage state following:
A stage will always be on top of its parent window.
So additionally, what would be a workaround that makes the popup not "always on top" and minimizable?
There is a way to get around it by overlaying StackPanes. Create your Scene with a StackPane so that you can add another StackPane when the stage has lost its focus. The overlayed pane will prevent Tooltips or anything else happening on mouse-over while the pane is not in focus. You may also minimize any of your stages and they won't be always-on-top.
public class Blubb extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Button button_1 = new Button("Button #1");
button_1.setTooltip(new Tooltip("Blubb #1"));
StackPane primary = new StackPane(new BorderPane(button_1));
primaryStage.setScene(new Scene(primary, 320, 240));
addStageFocusListener(primaryStage, primary);
primaryStage.show();
Button button_2 = new Button("Button #2");
button_2.setTooltip(new Tooltip("Blubb #2"));
StackPane second = new StackPane(new BorderPane(button_2));
Stage secondStage = new Stage();
addStageFocusListener(secondStage, second);
secondStage.setScene(new Scene(second, 320, 240));
secondStage.show();
}
public void addStageFocusListener(Stage stage, StackPane stackPane) {
stage.focusedProperty().addListener(new ChangeListener<Boolean>(){
public final StackPane preventTooltip = new StackPane();
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(stage.isFocused()) {
if(stackPane.getChildren().contains(preventTooltip)) {
stackPane.getChildren().remove(preventTooltip);
}
} else {
stackPane.getChildren().add(preventTooltip);
}
}
});
}
}
You can try this:
public static final disableMouseEventOnUnfocus(final Stage stage)
{
if (stage == null
|| stage.getScene() == null
|| stage.getScene().getRoot() == null)
return;
else
{
stage.getScene().getRoot().mouseTransparentProperty().bind(stage.focusedProperty().not());
}
}
I didn't try it though, but if it works, this should be a good alternative. There is no need to restructure your layout, and you can leave all your layout in FXML, without specifying fx:id for the tooltips.
I've come up with this alternative solution, as I've found it easier in my case to subclass Tooltip and apply a fix there. I just overload the show() method to only show if the owning window is focused. It's working like a charm for me...
public class FixedTooltip extends Tooltip {
public FixedTooltip(String string) {
super(string);
}
#Override
protected void show() {
Window owner = getOwnerWindow();
if (owner.isFocused())
super.show();
}
}
You could try to unset the tooltip whenever the node's window loses focus. Such as below:
public class Blubb extends Application {
public static void main(String[] args) {
launch(args);
}
public static void installTooltip(Node n, Tooltip tp)
{
Window w = n.getScene().getWindow();
w.focusedProperty().addListener((val, before, after) -> {
if (after)
Tooltip.install(n, tp);
else
Tooltip.uninstall(n, tp);
});
if (w.isFocused())
Tooltip.install(n, tp);
else
Tooltip.uninstall(n, tp);
}
#Override
public void start(Stage primaryStage) throws Exception {
Tooltip tp = new Tooltip("Blubb");
Button btn = new Button("Click");
Scene scene = new Scene(new BorderPane(btn), 320, 240);
primaryStage.setScene(scene);
//primaryStage.show();
Stage secondStage = new Stage();
secondStage.setScene(new Scene(new BorderPane(new Button("Click")), 320, 240));
//secondStage.initOwner(primaryStage);
secondStage.show();
primaryStage.show();
installTooltip(btn, tp);
}
}
Of course, you would have to call installTooltip after the node is added to the component.