My JavaFX application creates a dialog as a second Stage and my JemmyFX tests intermittently fail to click controls in that dialog.
Failures occur at a rate of about 10% on my Ubuntu Linux workstation, but this works flawlessly on Windows.
The proximal cause of the failure seems to be that JemmyFX is clicking the mouse in the wrong places. I dug into this, and the bad click coordinates seem to be caused by incorrect window coordinates coming from the Window object that owns the Scene.
So, I created a minimal application and test that demonstrates the problem, and it actually fails at an even higher rate than my real application (about 50%).
Here is the application:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.stage.Stage;
public class MySmallApplication extends Application {
public void start(Stage primaryStage) {
class MyDialog extends Stage {
public MyDialog() {
setTitle("My Dialog");
ComboBox comboBox = new ComboBox();
comboBox.getItems().add("apple");
comboBox.getItems().add("pear");
comboBox.getItems().add("banana");
comboBox.setId("click-me");
setScene(new Scene(comboBox));
sizeToScene();
}
}
Button button = new Button("Show Dialog");
button.setOnAction((event) -> {
new MyDialog().showAndWait();
});
primaryStage.setScene(new Scene(button));
primaryStage.setTitle("My Small Application");
primaryStage.show();
}
}
Here is the test:
import javafx.application.Application;
import javafx.scene.control.ComboBox;
import javafx.stage.Window;
import org.jemmy.fx.AppExecutor;
import org.jemmy.fx.SceneDock;
import org.jemmy.fx.control.ComboBoxDock;
import org.jemmy.fx.control.LabeledDock;
import org.jemmy.resources.StringComparePolicy;
import org.junit.BeforeClass;
import org.junit.Test;
import MySmallApplication;
public class WindowBugTest3 {
#BeforeClass
public static void launch() throws InterruptedException {
AppExecutor.executeNoBlock(MySmallApplication.class);
Thread.sleep(1000);
}
#Test
public void testWindowPosition() throws InterruptedException {
SceneDock sceneDock = new SceneDock();
new LabeledDock(
sceneDock.asParent(),
"Show Dialog",
StringComparePolicy.EXACT).mouse().click();
Thread.sleep(1000);
SceneDock dialogSceneDock = new SceneDock(
"My Dialog",
StringComparePolicy.EXACT);
ComboBoxDock comboBoxDock = new ComboBoxDock(
dialogSceneDock.asParent(), "click-me");
comboBoxDock.selector().select("pear");
}
}
I don't really want to develop my tests on Windows.
I observed all of this with recent fetches of JemmyFX (8, 8u, 8u-dev) compiled and run on Java8u101 on Ubuntu 14.04.
It seems that it is a bug in JavaFX (https://bugs.openjdk.java.net/browse/JDK-8166414). It can't be resolved on JemmyFX side.
P.S. It is highly unlikely that it will be fixed in observable time. So I may only suggest to use some ugly workaround like restoring correct dialog coordinates after receiving incorrect ones (e.g. by additional centerOnScreen() on the second invocation of coordinate property listener).
Related
I use the javafx virtual keyboard with open jdk 8. At times I have to access the virtual keyboard to prevent it from displaying when certain text fields get focus. An example of this is a screen where an operator has to scan in multiple barcodes. This virtual keyboard gets in the way. With open jdk 8 we were able to disable the virtual keyboard like this:
FXVK.detach(); //after importing "com.sun.javafx.scene.control.skin.FXVK"
We are now upgrading to open jdk 15 and building our UI with gradle. "com.sun.javafx.scene.control.skin.FXVK" is no longer accessible with a modular project with gradle. I don't believe using a different virtual keyboard is an option so can anyone explain how to access this FXVK class after java 8?
Is there a way to use --add-exports or --patch-module with a JAR to patch JavaFX to gain access to the internal class?
Below is the code for a sample project that shows this problem.
This is the JavaFX Application class that simply displays a text field and shows the code I could use with java 8 to not show the virtual keyboard.
package com.test.sampleapp.application;
////not accessible in java 15
//import com.sun.javafx.scene.control.skin.FXVK;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
Label label = new Label("Text field below");
TextField textField = new TextField();
VBox vbox = new VBox(label);
vbox.getChildren().add(textField);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
textField.focusedProperty().addListener(new ChangeListener<Boolean>()
{
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue)
{
// If focused
if (newValue)
{
//Need this to disable the virtual keyboard when using a textfield with scanning
//FXVK.detach();
}
}
});
}
}
Then I needed to add a wrapper class to have the virtual keyboard show up. Please note that most of the time I do use the virtual keyboard when text fields get focus, it's other times where I need to be able to programmatically disable it during certain situations.
The wrapper class:
package com.test.sampleapp.application;
import java.lang.reflect.Method;
public class AppWrapper
{
public static void main(String[] args) throws Exception
{
Class<?> app = Class.forName("com.test.sampleapp.application.Main");
Method main = app.getDeclaredMethod("main", String[].class);
System.setProperty("com.sun.javafx.isEmbedded", "true");
System.setProperty("com.sun.javafx.touch", "true");
System.setProperty("com.sun.javafx.virtualKeyboard", "javafx");
Object[] arguments = new Object[]{args};
main.invoke(null, arguments);
}
}
Let me know if you need anything else such as the build.gradle file however this is mostly just an issue using java 9 or beyond.
The FXVK class still exists in the same package, so the only issue is that its package is not exported by the javafx.controls module. If you must use this internal class, then you can pass an appropriate --add-exports JVM argument both at compile-time and at run-time.
Here's a simple application that calls FXVK#detach():
// Will fail at compile-time if the '--add-exports` argument is not
// passed to 'javac'
import com.sun.javafx.scene.control.skin.FXVK;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
var root = new StackPane(new Label("Hello, World!"));
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
// Will fail at run-time if the '--add-exports' argument is
// not passed to 'java'
FXVK.detach();
}
}
Assuming you put the Main.java file in your working directory, you can compile it with:
javac -p <path-to-fx> --add-modules javafx.controls --add-exports javafx.controls/com.sun.javafx.scene.control.skin=ALL-UNNAMED Main.java
And run it with:
java -p <path-to-fx> --add-modules javafx.controls --add-exports javafx.controls/com.sun.javafx.scne.control.skin=ALL-UNNAMED Main
If your code is modular then you can get rid of the --add-modules and you must change ALL-UNNAMED to the name of your module. Plus, make sure to launch your application via --module (or -m). Note the -p above is shorthand for --module-path.
If you use a build tool (e.g., Maven, Gradle, etc.), then you'll have to lookup how to set these JVM arguments for that tool. You'll also have to take into account how you deploy your application. For instance, if you use jpackage then you can use its --java-options argument to set the --add-exports option for when your application is launched.
You may also need to tell your IDE that you are giving yourself access to the internal package. Otherwise, your IDE will likely yell at you for trying to use an inaccessible type.
I'm creating a JavaFx project in Visual Studio Code. And I was wanting to release a sound, but I ended up having the following problem:
The type javafx.scene.media.Media is not accessibleJava(16778666)
The type javafx.scene.media.MediaPlayer is not accessibleJava(16778666)
I've already added your modules and also seen them on the internet to add to pom.xml:
modules:
"vmArgs": "--module-path \"C:/Program Files/Java/javafx-sdk-19/lib\" --add-modules javafx.controls,javafx.fxml, javafx.media",
Pom.xml:
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>13</version>
</dependency>
I didn't find anyone commenting on this specific error, if you can help me I would be very grateful
Note: The paths are correct as the other packages are working normally
The code:
package com.example;
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
/**
* JavaFX App
*/
public class App extends Application {
private static Scene scene;
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("urna"));
stage.setScene(scene);
stage.setTitle("Urna EletrĂ´nica");
stage.show();
}
public void songMedia(String path) {
Media media = new Media(new File(path).toURI().toString());
MediaPlayer mediaPlayer = new MediaPlayer(media);
mediaPlayer.play();
}
you will need to add it manually to your dependencies first
all info available on the JavaFX maven plugin github page
https://github.com/openjfx/javafx-maven-plugin
Looking at the way you have placed your path to the JavaFX library, Visual studio is reading the Javafx library incorrectly.
On top of the Visual Studio menu,
Click on Run
Then select Open Configurations
Inside the curly braces under "configurations" add a comma then press Enter
Then add the lines below:
"vmArgs": "--module-path=PATH_TO_JAVAFX_LIB --add-modules=MODULE_1,MODULES_2"
Note: An example of the above arguments with the correct path format is as below;
"vmArgs": "--module-path=lib/javafx-sdk-13/lib --add-modules=javafx.media,javafx.controls"
I updated my Windows 10 laptop with May 2019 build (1903) and JavaFX does not seem to work anymore. After launching any JavaFX application, I see an icon on the taskbar, but no window is created. My java is the latest Java 8, latest Eclipse as IDE.
Is this a known issue or am I doing something wrong? Is there are a work-around or fix?
Thanks
I have created a small app that reproduces the problem.
If I comment out the following line
primaryStage.initStyle(StageStyle.UNDECORATED);
then it works as expected. Otherwise Windows 10 (1903) hangs, no window is shown. Be warned that you will need to use task-manager in windows to kill the JVM.
package com.alam33;
import java.io.IOException;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Win10_1903Test extends Application {
public Win10_1903Test() {
}
#Override
public void start(Stage primaryStage) throws IOException {
VBox vbox = new VBox();
vbox.setPrefHeight(200);
vbox.setPrefWidth(300);
Scene scene = new Scene(vbox);
primaryStage.setTitle("Win10_1903Test");
primaryStage.setScene(scene);
primaryStage.setFullScreen(true);
/* THIS IS THE OFFENDING LINE */
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This is a workaround provided by Oracle, although they could not reproduce it. I suspect the problem is specific to the hardware in my machines.
Workaround: add jvm option
-Dprism.order=sw
As noted below, it is not a proper solution but I take it as an answer because it does help to make sure that your code is OK, which is important during development.
I'm trying to populate my TreeView and update the text of status label before and after the populating.
my code looks something like this:
public void populateTreeView() {
// start message
this.label.setText("Process started...");
// populate the TreeView, this takes several minutes
// finish message
this.label.setText("Done!");
}
It's not working as I expected. I tried put each of the the 3 main sections of this method in Platform.runLater() and that didn't work, too. The app just freezes for some seconds and then I see the populated TreeView and the "Done" text.
How can I get this behavior?
Thanks in advance
You will need to execute the loading method in a background Thread. Basically, the interface that you create and all of its events are executed on the JavaFX Application Thread (JFXAT).
These events are generally executed one at a time, so if you run a long process on this Thread, the UI will appear to be frozen until that process is completed.
While there are several ways to create background tasks in JavaFX, below is a simple demo application that uses a Task to do so.
The example code is commented throughout to explain what we are doing. The example uses a ListView instead of a TreeView, just for simplicity, but the concept is the same regardless.
This example shows a basic interface with a ListView and a Button to start the loading process. At the bottom is a Label that will keep the user updated on the current step in the Task's process.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Just a simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Create the list view
ListView<String> listView = new ListView<>();
listView.setPrefHeight(200);
// Status label
Label lblStatus = new Label();
// Here we will create a new Task that will run in the background
Task<List<String>> loadDataTask = new Task<List<String>>() {
// We override the call() method; this is the code that is run when the task is started. When the Task
// is completed, it will return a new List of Strings
#Override
protected List<String> call() throws Exception {
// Tasks have a messageProperty that allows us to keep the UI informed. We will bind this value to
// our Label later
updateMessage("Loading data ...");
// Generate some sample data
List<String> listOfItems = new ArrayList<>();
listOfItems.add("One");
listOfItems.add("Two");
listOfItems.add("Three");
listOfItems.add("Four");
listOfItems.add("Five");
// Simulate a long-running task by pausing the thread for 10 seconds
Thread.sleep(10000);
// Now we can update the messageProperty again and return the completed data List
updateMessage("Finished!");
return listOfItems;
}
};
// We can now tell our application what to do once the Task has finished (either successfully or failure)
loadDataTask.setOnFailed(wse -> {
// This is called if an exception is thrown during the execution of the Task
// We will just print the Exception in this sample
loadDataTask.getException().printStackTrace();
});
// The Task completed successfully so lets now bind the List to our ListView
loadDataTask.setOnSucceeded(wse -> {
listView.setItems(FXCollections.observableArrayList(loadDataTask.getValue()));
});
// Now that we've defined our background Task and what to do when it's completed, let's create a button
// that allows us to start the task.
Button button = new Button("Load Data");
button.setOnAction(e -> {
// Let's bind our Label to the Task's messageProperty. This will cause the Label to update automatically
// whenever the Task calls the updateMessage() method.
lblStatus.textProperty().bind(loadDataTask.messageProperty());
// Now let's start the Task on a background Thread. This will cause the rest of the UI to remain
// responsive.
new Thread(loadDataTask).start();
});
// Add the controles to the Scene
root.getChildren().addAll(
button,
listView,
new Label("Status:"),
lblStatus);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
When the button is clicked, the background Task is executed, the Label is updated to show "Loading data ..." and the long-running task begins.
When the Task finishes, the ListView gets updated with the data and the Label will show `Finished!"
Basically, I have an application that opens mostly modal dialogs as I dont want the user to be able to continue working in the main app while a dialog is open. However, Modality.APPLICATION_MODAL as well as Modality.WINDOW_MODAL both also prevent minimizing and maximizing windows. That is a problem when a dialog gets triggered by an non-user event and the user has minimized the window already -> he cant open it again.
Now I have a sort of workaround that opens the dialog manually on the main Screen of the user. I could also simply make the dialog non-modal if the app is minimized, but then he could work in the application while ignoring the dialog. Both solutions are not particularly pretty and not exactly what I want.
I know its a "feature" but is there really no way to interact with the window but not the scene of the owner Stage? Perhaps a way to block user events to the scene without using modality? Would seem like a pretty common use case to me.
Here is a minimalistic code example:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.ScrollPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class DialogDemo extends Application {
#Override
public void start(Stage primaryStage) {
Stage mainStage = new Stage();
Button showDialogBtn = new Button("show Dialog");
showDialogBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Dialog<ButtonType> dialog = new Dialog<ButtonType>();
dialog.initStyle(StageStyle.DECORATED);
dialog.initOwner(mainStage);
//prevents mainStage from being resized, mini-/maximized and closed
dialog.initModality(Modality.WINDOW_MODAL);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
dialog.show();
}
});
ScrollPane pane = new ScrollPane(showDialogBtn);
pane.setMinSize(600, 480);
mainStage.setScene(new Scene(pane));
mainStage.show();
}
public static void main(String[] args) {
launch(args);
}
}