I have a JavaFX application with a ScrollPane that handles resizing of nodes upon scrollEvents. However when the JavaFX stage (or Window) is not focused, I get an odd behaviour that I think might be a JFX bug, though wondering if anyone has encountered or managed to resolve it.
To replicate the issue, if you lose focus on the JavaFX window and perform some scrolling using the mouse-wheel on another window (eg your browser), and then relatively quickly move your mouse back to re-enter the JavaFX window (without clicking, scrolling or focusing upon the JavaFX window) the JavaFX window receives a bunch of scrollEvents despite no mouse-wheel action being performed.
I'm wondering if anyone has encountered this and worked out a way to somehow filter these odd scrollEvents out as it results in some strange zooming action that is unexpected given the lack of mouse-wheel scrolling!
I'm using Java & JavaFX 17 (OpenJFX), see below sample application that demonstrates, thanks!
public class ScrollEventIssueApplication extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane borderPane = new BorderPane ();
borderPane .setPrefWidth(600);
borderPane .setPrefHeight(600);
Pane content = new Pane();
content.setPrefWidth(1000);
content.setPrefHeight(1800);
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPrefWidth(700);
scrollPane.setPrefHeight(700);
content.setOnScroll(event -> {
System.out.println("Scroll event received: " + event.getDeltaY());
});
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 1800, 900);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
It appears that this may be a platform-dependent feature of the installed pointing device and driver. I could occasionally reproduce the effect on Mac OS X 12 with JavaFX 17, but only when I also accidentally raked an errant finger or two across the mouse's multi-touch surface.
For reference, I tested the following simpler variation. Note code to enumerate the OS and Java version numbers, as well as changes to the viewport dimensions in order to show the scroll bars when set to AS_NEEDED by default.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/** #see https://stackoverflow.com/q/72485336/230513 */
public class ScrollTest extends Application {
private static final double W = 640;
private static final double H = 480;
#Override
public void start(Stage stage) {
Pane content = new StackPane();
content.getChildren().addAll(new Rectangle(W, H, Color.BLUE),
new Circle(Math.min(W, H) / 2, Color.LIGHTBLUE));
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPrefViewportWidth(2 * W / 3);
scrollPane.setPrefViewportHeight(2 * H / 3);
content.setOnScroll(event -> System.out.println(
"dx dy: " + event.getDeltaX() + " " + event.getDeltaY()));
stage.setScene(new Scene(scrollPane));
stage.show();
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name")
+ " v" + System.getProperty("os.version")
+ "; Java v" + System.getProperty("java.version")
+ "; JavaFX v" + System.getProperty("javafx.runtime.version"));
launch(args);
}
}
I realised the issue was the mouse I was using (a Logitech MX Vertical Advanced Ergonomic) had a "smooth scrolling" option on the mouse wheel that meant to it would "continue" some small scroll events after the mouse wheel had stopped scrolling which were causing this issue. Turning off that option in the Logitech Options application resolved the issue.
Related
Hi I am a JavaFX newbie and I am trying to write my first application. I want to start with an empty window and provide a popup menu that allows users to add 3D elements to the window.
I have created a simple Group containing a few trivial geometric shapes and added this group as the parent to a Scene. I define a mouse event handler for the scene and call setScene to make this the scene for my Stage (passed in to my Application's start method).
Unfortunately, I can't seem to find a way of positioning the menu correctly in response to a mouse pressed event. I get it that I need to get the X and Y coordinates from the Event, but when I pass these unchanged to the context menu show method, the menu appears in the top left-hand corner of my laptop display, rather than inside my application window.
Clearly, I need to offset these values by the origin of some other window, but what? I have tried the Scene, the Group and and the Stage, but with no success :-( This ought to be a trivial problem - where am I going wrong??
Code sample shown below:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Box;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
public class PopupTest extends Application {
private static final ContextMenu contextMenu = new ContextMenu();
public static void main(String[] args) {
MenuItem cut = new MenuItem("Cut");
MenuItem copy = new MenuItem("Copy");
MenuItem paste = new MenuItem("Paste");
contextMenu.getItems().addAll(cut, copy, paste);
Application.launch(args);
}
#Override
public void start(Stage stage) {
// Create a Box
Box box = new Box(100, 100, 100);
box.setTranslateX(150);
box.setTranslateY(0);
box.setTranslateZ(400);
// Create a Sphere
Sphere sphere = new Sphere(50);
sphere.setTranslateX(300);
sphere.setTranslateY(-5);
sphere.setTranslateZ(400);
// Create a Cylinder
Cylinder cylinder = new Cylinder(40, 120);
cylinder.setTranslateX(500);
cylinder.setTranslateY(-25);
cylinder.setTranslateZ(600);
// Create a Light
PointLight light = new PointLight(Color.YELLOW);
light.setTranslateX(350);
light.setTranslateY(100);
light.setTranslateZ(300);
// Create a Camera to view the 3D Shapes
PerspectiveCamera camera = new PerspectiveCamera(false);
camera.setTranslateX(100);
camera.setTranslateY(-50);
camera.setTranslateZ(300);
// Add the Shapes and the Light to the Group
Group root = new Group(box, sphere, cylinder, light);
// Create a Scene with depth buffer enabled
Scene scene = new Scene(root, 400, 300, true);
scene.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("mouse click detected!");
if (event.isPopupTrigger()) {
// similar results with getX() vs getSceneX() etc.
System.out.println("Display menu at (" + event.getSceneX() + "," + event.getSceneY() + ")");
contextMenu.show(root, event.getSceneX(), event.getSceneY());
}
}
});
// Add the Camera to the Scene
scene.setCamera(camera);
// Add the Scene to the Stage
stage.setScene(scene);
// Set the Title of the Stage
stage.setTitle("Trying to get popup menu working");
// Display the Stage
stage.show();
}
}
I have created a JavaFX class that is similar to the DatePicker class but instead displays a popup with a way to choose a time. I am wanting to have the popup appear underneath the TextField and Button just as the DatePicker Popup does, but I am unable to find a way to get the X and Y coordinates of the Nodes. Any help would be appreciated.
You can get screen co-ordinates using: node.localToScreen(x,y).
The following code will locate (anchor) the top left corner of a pop-up window at the bottom center of a node on the pop-up owner window:
Point2D anchorPoint = node.localToScreen(
node.getWidth() / 2,
node.getHeight()
);
popup.setAnchorLocation(
PopupWindow.AnchorLocation.WINDOW_TOP_LEFT
);
popup.show(
node,
anchorPoint.getX(),
anchorPoint.getY()
);
Sample application:
Displays and hides a popup located at the bottom center of controlling button.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.*;
public class Popcorn extends Application {
#Override
public void start(Stage stage) {
StackPane popupLayout = new StackPane(
new Label(
"Clarke's third law\nAny sufficiently advanced technology is indistinguishable from magic."
)
);
popupLayout.setStyle("-fx-opacity: 0.8; -fx-background-color: paleturquoise;");
popupLayout.setPadding(new Insets(10));
Popup popup = new Popup();
popup.getContent().add(
popupLayout
);
popupLayout.setOnMouseClicked(event -> popup.hide());
ToggleButton showPopupButton = new ToggleButton("Show popup");
showPopupButton.textProperty().bind(
Bindings.when(showPopupButton.selectedProperty())
.then("Hide popup")
.otherwise("Show popup")
);
showPopupButton.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
if (isSelected) {
showPopup(popup, showPopupButton);
} else {
popup.hide();
}
});
popup.setOnShown(event -> showPopupButton.setSelected(true));
popup.setOnHidden(event -> showPopupButton.setSelected(false));
StackPane stageLayout = new StackPane(showPopupButton);
stageLayout.setPadding(new Insets(10));
stage.setScene(new Scene(stageLayout));
stage.show();
}
private void showPopup(Popup popup, Control ownerNode) {
Point2D anchorPoint = ownerNode.localToScreen(
ownerNode.getWidth() / 2,
ownerNode.getHeight()
);
popup.setAnchorLocation(
PopupWindow.AnchorLocation.WINDOW_TOP_LEFT
);
popup.show(
ownerNode,
anchorPoint.getX(),
anchorPoint.getY()
);
}
public static void main(String[] args) {
launch(args);
}
}
A note on popup anchors
The popup anchor point is just advice to the popup system on where to place the popup. The popup logic built into JavaFX is smart enough to know the size of the popup window and its initial location on the screen. If that means that there is not enough real estate left on the screen to display the popup at the suggested anchor point, the popup implementation will automatically adjust the anchor point to ensure that the popup will be entirely visible when first displayed. You can try this out by running the example code and placing the owner stage at the extreme bottom or right of the screen before trying to show the popup.
really liking JavaFX but have come across this problem and wondered if it was a bug.
The ScrollBar.setOnMousePressed() doesn't seem to fire when it has been initialised with a handler. The code below demonstrates the problem:-
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Play extends Application {
public static void main(String[] args) {
launch(args);
}
private static int cnt;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Bug?");
Button btn = new Button("This text will get replaced by the event handlers");
ScrollBar scrollBar = new ScrollBar();
// When pressing and releasing the ScrollBar thumb, we only get decrements
// If you replace the ScrollBar with say a Button, then the code below works as you might expect.
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
scrollBar.setOnMouseReleased( event -> btn.setText("X" + cnt--));
VBox root = new VBox();
root.getChildren().add(btn);
root.getChildren().add(scrollBar);
primaryStage.setScene(new Scene(root, 350, 250));
primaryStage.show();
}
}
Note, Im running on JDK 1.8.0_66 64 Bit on Microsoft Windows 10.
A simple workaround, as suggested by James_D, is to use EventFilters instead of setOnMousePressed(), as follows:-
So,
scrollBar.addEventFilter(MouseEvent.MOUSE_PRESSED,
event -> btn.setText("X" + cnt++));
instead of
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
I believe .setOnMousePressed() should work, but doesn't because of a bug in the library. I've raised with oracle and will update the answer once oracle clarifies.
When I zoom in on a web page opened in a WebView (zoom factor < 1.0), most of the content gets scaled as expected, however there are some things that do not, like radio buttons or check boxes:
In the above image the first WebView is unzoomed and the second has a zoom factor of 0.5 though the radio buttons have (or have nearly) the same size.
Here is the code for this example:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class WebViewScalingDemo2 extends Application {
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("HTML");
stage.setWidth(800);
stage.setHeight(800);
Scene scene = new Scene(new Group());
final VBox root = new VBox();
String html = "<html><head><meta charset=\"utf-8\"><title>Radio-Buttons definieren</title><style>"+
"fieldset{"+
"border:none;"+
"}</style></head>"+
"<body><h1>Hier wird abkassiert!</h1><form action=\"#\"><p>Geben Sie Ihre Zahlungsweise an:</p>"+
"<fieldset><input type=\"radio\" id=\"mc\" name=\"Zahlmethode\" value=\"Mastercard\"><label for=\"mc\"> Mastercard</label><br>"+
"<input type=\"radio\" id=\"vi\" name=\"Zahlmethode\" value=\"Visa\"><label for=\"vi\"> Visa</label><br>"+
"<input type=\"radio\" id=\"ae\" name=\"Zahlmethode\" value=\"AmericanExpress\"><label for=\"ae\"> American Express</label>"+
"</fieldset></form></body></html>";
WebView webView1 = new WebView();
webView1.setPrefSize(300, 300);
webView1.setLayoutX(250);
webView1.setLayoutY(250);
webView1.getEngine().loadContent(html);
WebView webView2 = new WebView();
webView2.setPrefSize(300, 300);
webView2.setLayoutX(250);
webView2.setLayoutY(250);
webView2.getEngine().loadContent(html);
root.getChildren().addAll(webView1, webView2);
webView1.setZoom(1);
webView2.setZoom(0.5);
scene.setRoot(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For me this is an issue because there are labels close to the right of the buttons, and if the zoom factor is too low, the label will be partially over the radio button.
I have two questions here:
Why are the radio buttons not scaled properly? They also stay the same size when using a zoom factor > 1.0.
Is there anything I can do about this? I was thinking along the lines of some CSS I could indject to force larger spacing when the zoom is low, but that would be dependent on the used zoom factor and it should be a solution that works for all radio buttons and check boxes, without the need to target them specially (by id or style class).
This is an issue with the native code, the rendering with webkit in javafx and is still present in Java 1.8.0_60.
The workaround is to apply -webkit-transform: scale(**your scale here**); on this elements on your own.
If you don't intend to change the zoomProperty dynamically I suggest adding CSS style:
input[type="checkbox"], input[type="radio"] {
-webkit-transform: scale(**your scale here**);
}
The other approach is with javascript and is good if you intend to change the zoomProperty dynamically:
(function($){
var scaleCheckboxesAndRadioButtons = function(scale) {
var $elements= $('input[type="checkbox"], input[type="radio"]');
$elements.css("-webkit-transform", "scale(" + scale + ")");
}
}(jQuery));
Than just add a listener on the zoomProperty of your webview and on change call the javascript function from your java code:
webView.zoomProperty().addListener((observable, oldValue, newValue) -> {
webView.getEngine().executeScript("scaleCheckboxesAndRadioButtons(" + newValue + ");");
});
I'm making an iOS7-themed JavaFX2/FXML project and I was wondering how I could make a Rectangle object have a iOS7-like frosted glass effect.
I'd also like it to have a small shadow. This is tricky, since you might be able to see the shadow behind the semi-transparent object. I'd just like it to be present around the edges.
Is this possible? Here's a picture showing the desired effect (not including the small drop-shadow):
UPDATE: Here's a continuation of the issue. This is going to look amazing :D.
Sample Solution
Run the program below and scroll or swipe up to show the glass pane.
The purpose of the program is just to sample the techniques involved not to act as a general purpose library for the frost effect.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.Rectangle2D;
import javafx.scene.*;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
// slides a frost pane in on scroll or swipe up; slides it out on scroll or swipe down.
public class Frosty extends Application {
private static final double W = 330;
private static final double H = 590;
private static final double BLUR_AMOUNT = 60;
private static final Duration SLIDE_DURATION = Duration.seconds(0.4);
private static final double UPPER_SLIDE_POSITION = 100;
private static final Effect frostEffect =
new BoxBlur(BLUR_AMOUNT, BLUR_AMOUNT, 3);
#Override public void start(Stage stage) {
DoubleProperty y = new SimpleDoubleProperty(H);
Node background = createBackground();
Node frost = freeze(background, y);
Node content = createContent();
content.setVisible(false);
Scene scene = new Scene(
new StackPane(
background,
frost,
content
)
);
stage.setScene(scene);
stage.show();
addSlideHandlers(y, content, scene);
}
// create a background node to be frozen over.
private Node createBackground() {
Image backgroundImage = new Image(
getClass().getResourceAsStream("ios-screenshot.png")
);
ImageView background = new ImageView(backgroundImage);
Rectangle2D viewport = new Rectangle2D(0, 0, W, H);
background.setViewport(viewport);
return background;
}
// create some content to be displayed on top of the frozen glass panel.
private Label createContent() {
Label label = new Label("The overlaid text is clear and the background below is frosty.");
label.setStyle("-fx-font-size: 25px; -fx-text-fill: midnightblue;");
label.setEffect(new Glow());
label.setMaxWidth(W - 20);
label.setWrapText(true);
return label;
}
// add handlers to slide the glass panel in and out.
private void addSlideHandlers(DoubleProperty y, Node content, Scene scene) {
Timeline slideIn = new Timeline(
new KeyFrame(
SLIDE_DURATION,
new KeyValue(
y,
UPPER_SLIDE_POSITION
)
)
);
slideIn.setOnFinished(e -> content.setVisible(true));
Timeline slideOut = new Timeline(
new KeyFrame(
SLIDE_DURATION,
new KeyValue(
y,
H
)
)
);
scene.setOnSwipeUp(e -> {
slideOut.stop();
slideIn.play();
});
scene.setOnSwipeDown(e -> {
slideIn.stop();
slideOut.play();
content.setVisible(false);
});
// scroll handler isn't necessary if you have a touch screen.
scene.setOnScroll((ScrollEvent e) -> {
if (e.getDeltaY() < 0) {
slideOut.stop();
slideIn.play();
} else {
slideIn.stop();
slideOut.play();
content.setVisible(false);
}
});
}
// create a frosty pane from a background node.
private StackPane freeze(Node background, DoubleProperty y) {
Image frostImage = background.snapshot(
new SnapshotParameters(),
null
);
ImageView frost = new ImageView(frostImage);
Rectangle filler = new Rectangle(0, 0, W, H);
filler.setFill(Color.AZURE);
Pane frostPane = new Pane(frost);
frostPane.setEffect(frostEffect);
StackPane frostView = new StackPane(
filler,
frostPane
);
Rectangle clipShape = new Rectangle(0, y.get(), W, H);
frostView.setClip(clipShape);
clipShape.yProperty().bind(y);
return frostView;
}
public static void main(String[] args) { launch(args); }
}
Source image
Save this image parallel to the Java source as a file named ios-screenshot.png and have your build system copy it to the target directory for the binary output of the build.
Answers to additional questions
"JDK 8," would that happen to be a requirement of this?
The sample code above is written against JDK 8. Porting it back to JDK 7 by replacing the lambda calls with anonymous inner classes is pretty trivial.
In general, Java 7 is pretty dated for JavaFX work. I advise upgrading at your earliest convenience to work with a Java 8 minimum version.
initiating your Panes with arguments
More convenient constructors for most parent nodes is a Java 8 feature. You can easily convert the Java 8 format:
StackPane stack = new StackPane(child1, child2);
To Java 7:
StackPane stack = new StackPane();
stack.getChildren().setAll(child1, child2);
will this working if the desktop is behind a frosty pane?
Not directly, you can create a new question for that.
Update: Related Questions
User created: JavaFX effect on background to allow the frosted effect to apply to a window over a desktop background.
Another user created: How do I create a JavaFX transparent stage with shadows on only the border? to apply a halo shadow effect around this window.