Open External Application From JavaFX - java

I found a way to open a link on default browser using HostServices.
getHostServices().showDocument("http://www.google.com");
Is there any way to open a media in default media player?
Is there any way to launch a specific File or Application?

Generally speaking, you can use Desktop#open(file) to open a file natively as next:
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.OPEN)) {
desktop.open(file);
} else {
throw new UnsupportedOperationException("Open action not supported");
}
Launches the associated application to open the file. If the specified
file is a directory, the file manager of the current platform is
launched to open it.
More specifically, in case of a browser you can use directly Desktop#browse(uri), as next:
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
desktop.browse(uri);
} else {
throw new UnsupportedOperationException("Browse action not supported");
}
Launches the default browser to display a URI. If the default browser
is not able to handle the specified URI, the application registered
for handling URIs of the specified type is invoked. The application is
determined from the protocol and path of the URI, as defined by the
URI class. If the calling thread does not have the necessary
permissions, and this is invoked from within an applet,
AppletContext.showDocument() is used. Similarly, if the calling does
not have the necessary permissions, and this is invoked from within a
Java Web Started application, BasicService.showDocument() is used.

If you want to either open a URL which has an http: scheme in the browser, or open a file using the default application for that file type, the HostServices.showDocument(...) method you referenced provides a "pure JavaFX" way to do this. Note that you can't use this (as far as I can tell) to download a file from a web server and open it with the default application.
To open a file with the default application, you must convert the file to the string representation of the file: URL. Here is a simple example:
import java.io.File;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class OpenResourceNatively extends Application {
#Override
public void start(Stage primaryStage) {
TextField textField = new TextField("http://stackoverflow.com/questions/39898704");
Button openURLButton = new Button("Open URL");
EventHandler<ActionEvent> handler = e -> open(textField.getText());
textField.setOnAction(handler);
openURLButton.setOnAction(handler);
FileChooser fileChooser = new FileChooser();
Button openFileButton = new Button("Open File...");
openFileButton.setOnAction(e -> {
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
open(file.toURI().toString());
}
});
VBox root = new VBox(5,
new HBox(new Label("URL:"), textField, openURLButton),
new HBox(openFileButton)
);
root.setPadding(new Insets(20));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void open(String resource) {
getHostServices().showDocument(resource);
}
public static void main(String[] args) {
launch(args);
}
}

Only the solution with java.awt.Desktop worked for me to open a file from JavaFX.
However, at first, my application got stuck and I had to figure out that it is necessary to call Desktop#open(File file) from a new thread. Calling the method from the current thread or the JavaFX application thread Platform#runLater(Runnable runnable) resulted in the application to hang indefinitely without an exception being thrown.
This is a small sample JavaFX application with the working file open solution:
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class FileOpenDemo extends Application {
#Override
public void start(Stage primaryStage) {
final Button button = new Button("Open file");
button.setOnAction(event -> {
final FileChooser fileChooser = new FileChooser();
final File file = fileChooser.showOpenDialog(primaryStage.getOwner());
if (file == null)
return;
System.out.println("File selected: " + file.getName());
if (!Desktop.isDesktopSupported()) {
System.out.println("Desktop not supported");
return;
}
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
System.out.println("File opening not supported");
return;
}
final Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
try {
Desktop.getDesktop().open(file);
} catch (IOException e) {
System.err.println(e.toString());
}
return null;
}
};
final Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
});
primaryStage.setScene(new Scene(button));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The other proposed solution with javafx.application.HostServices did not work at all. I am using OpenJFX 8u141 on Ubuntu 17.10 amd64 and I got the following exception when invoking HostServices#showDocument(String uri):
java.lang.ClassNotFoundException: com.sun.deploy.uitoolkit.impl.fx.HostServicesFactory
Obviously, JavaFX HostServices is not yet properly implemented on all platforms. On this topic see also: https://github.com/Qabel/qabel-desktop/issues/420

Related

JavaFX error "Application launch must not be called more than once" while running 2 Test cases

I am using JavaFX to write a sample video player. But the tests fails when run together, ( Note: individually test passes).
Error: Unexpected exception thrown: java.lang.IllegalStateException: Application launch must not be called more than once
I understand that calling launch() twice on same Application Instance is causing this issue as per this . But I am not able to understand, that after one test completes, why the app is still running ? Why new instance is not getting created for 2nd test. testUnsupportedVideoFileThrowsError() succeeds but testSupportedVideoFilePlaysSuccessfully() fails.
package media;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
import java.io.File;
public class PlayVideoSnippet extends Application {
private static String source;
private Media media = null;
private MediaPlayer mediaPlayer = null;
private MediaView mediaView = null;
private Scene scene = null;
public static void playVideo(String source) {
PlayVideoSnippet.source = source;
PlayVideoSnippet.launch();
}
#Override
public void start(Stage stage) throws InterruptedException {
try {
media = new Media(new File(source).toURI().toString());
//onError close the app
media.setOnError(() -> {
Platform.exit();
});
//Create MediaPlayer, with media
mediaPlayer = new MediaPlayer(media);
mediaPlayer.setAutoPlay(true);
//onEnd of media, close the app
mediaPlayer.setOnEndOfMedia(() -> {
Platform.exit();
});
//Create media viewer
mediaView = new MediaView(mediaPlayer);
//create scene
scene = new Scene(new Group(), media.getWidth(), media.getHeight());
stage.setScene(scene);
stage.setTitle("Video Player");
//attach the mediaView to the scene root
((Group) scene.getRoot()).getChildren().add(mediaView);
stage.show();
} finally {
System.out.println("Inside finally");
stage.close();
}
}
}
package media;
import javafx.scene.media.MediaException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class PlayVideoSnippetTest {
/**
* Tests for {#link PlayVideoSnippet#playVideo(String)}.
*/
final String PATH_OF_SUPPORTED_VIDEO_FILE = "src/test/resources/file_example_MP4_480_1_5MG.mp4";
final String PATH_OF_UNSUPPORTED_VIDEO_FILE = "src/test/resources/file_example_WMV_480_1_2MB.wmv";
#Test
void testSupportedVideoFilePlaysSuccessfully() {
assertDoesNotThrow(() -> PlayVideoSnippet.playVideo(PATH_OF_SUPPORTED_VIDEO_FILE));
}
#Test
void testUnsupportedVideoFileThrowsError() {
RuntimeException exception = assertThrows(RuntimeException.class, () -> PlayVideoSnippet.playVideo(PATH_OF_UNSUPPORTED_VIDEO_FILE));
assertTrue(exception.getCause().getClass().equals(MediaException.class));
}
}
I was able to fix the issue with help of below inputs:
Using TestFX for testing purpose as suggested by #jewelsea.
Inputs from #James_D
StackOver flow issue
ApplicationTest.launch() method internally takes care of setting up primary stage and cleaning before each test.
Solution:
import org.junit.jupiter.api.Test;
import org.testfx.framework.junit5.ApplicationTest;
class VideoTest extends ApplicationTest {
final String pathOfSupportedFile = "video.mp4";
final String pathOfUnsupportedFile = "video.wmv";
#Test
void testSupportedVideoFilePlaysSuccessfully() throws Exception {
assertDoesNotThrow(() -> PlayVideoSnippetTest.launch(PlayVideoSnippet.class,
new String[]{pathOfSupportedFile}));
}
#Test
void testUnsupportedVideoFileThrowsError() throws Exception {
RuntimeException exception = assertThrows(RuntimeException.class,
() -> PlayVideoSnippetTest.launch(PlayVideoSnippet.class,
new String[]{pathOfUnsupportedFile}));
assertTrue(exception.getCause().getCause().getClass().equals(MediaException.class));
}
}

When I run the Codename One HelloWorld Java Program from the Codename One HelloWorld Tutorial, I get an error

When I run the Codename One HelloWorld Java Program from the Codename One HelloWorld Tutorial video, I get this error:
java: cannot find symbol
symbol: class Button
location: class com.acmecorp.appname.AppName
I think I'm missing an import for class Button. How do I add the import statement for class Button so that the compilation won't have this error?
Here is my source code:
package com.acmecorp.appname;
import static com.codename1.ui.CN.*;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.Label;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.io.Log;
import com.codename1.ui.Toolbar;
import java.io.IOException;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.io.NetworkEvent;
/**
* This file was generated by Codename One for the purpose
* of building native mobile applications using Java.
*/
public class AppName {
private Form current;
private Resources theme;
public void init(Object context) {
// use two network threads instead of one
updateNetworkThreadCount(2);
theme = UIManager.initFirstTheme("/theme");
// Enable Toolbar on all Forms by default
Toolbar.setGlobalToolbar(true);
// Pro only feature
Log.bindCrashProtection(true);
addNetworkErrorListener(err -> {
// prevent the event from propagating
err.consume();
if(err.getError() != null) {
Log.e(err.getError());
}
Log.sendLogAsync();
Dialog.show("Connection Error", "There was a networking error in the connection to " + err.getConnectionRequest().getUrl(), "OK", null);
});
}
public void start() {
if(current != null){
current.show();
return;
}
Form hi = new Form("Hi World", BoxLayout.y());
hi.add(new Label("Hi World"));
Button b = new Button("Show Dialog");
hi.add(b);
b.addActionListener((e) -> Dialog.show("Dialog Title", "Hi", "OK", null));
hi.show();
}
public void stop() {
current = getCurrentForm();
if(current instanceof Dialog) {
((Dialog)current).dispose();
current = getCurrentForm();
}
}
public void destroy() {
}
}
I guess this com.codename1.ui.Button might works for u.

Exception handling in static initializer

Normally I handle my exceptions by showing some custom Alert (JavaFX) with details, but JavaFX runtime is not initialized at all when the static initializer of my class runs.
Is there any way to handle such exception without printing its content to output like an animal?
public class MyStaticInitializedClass {
static {
try {
//do the things that may throw exception
} catch(Exception ex) {
ExceptionHandler.showException(ex);
}
}
}
public class ExceptionHandler {
public static void showException(Exception ex) {
//constructs JavaFX alert with exception details
alert.show();
}
}
First consder if you shouldn't let the application simply crash and log the reason. A failure in a static initializer typically means there's something seriously wrong with the environment, which is not likely something you can recover from. Also, as far as I know, once a class fails to load it can't ever be loaded by the same ClassLoader again.
That said, if you want to show errors to your user in an alert, even if the error occurs before the JavaFX runtime has been initialized, then you need to save the error somewhere. Then, once you launch JavaFX, check wherever you stored the error(s) and show them. For example:
Main.java:
import javafx.application.Application;
public class Main {
public static void main(String[] args) {
// an "error" before JavaFX is launched
App.notifyUserOfError(new RuntimeException("OOPS!"));
Application.launch(App.class, args);
}
}
App.java:
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayDeque;
import java.util.Objects;
import java.util.Queue;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class App extends Application {
private static Queue<Throwable> errorQueue;
private static App appInstance;
public static synchronized void notifyUserOfError(Throwable throwable) {
Objects.requireNonNull(throwable);
if (appInstance == null) {
if (errorQueue == null) {
errorQueue = new ArrayDeque<>();
}
errorQueue.add(throwable);
} else {
if (Platform.isFxApplicationThread()) {
appInstance.showErrorAlert(throwable);
} else {
Platform.runLater(() -> appInstance.showErrorAlert(throwable));
}
}
}
private static synchronized Queue<Throwable> setAppInstance(App instance) {
if (appInstance != null) {
throw new IllegalStateException();
}
appInstance = instance;
var queue = errorQueue;
errorQueue = null; // no longer needed
return queue;
}
private Stage primaryStage;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
var scene = new Scene(new StackPane(new Label("Hello, World!")), 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
var errors = setAppInstance(this);
if (errors != null) {
// if non-null then should be non-empty
do {
showErrorAlert(errors.remove());
} while (!errors.isEmpty());
// possibly exit the application if you can't recover
}
}
private void showErrorAlert(Throwable error) {
var alert = new Alert(AlertType.ERROR);
alert.initOwner(primaryStage);
alert.setContentText(error.toString());
var sw = new StringWriter();
error.printStackTrace(new PrintWriter(sw));
var area = new TextArea(sw.toString());
area.setEditable(false);
area.setFont(Font.font("Monospaced", 12));
var details = new VBox(5, new Label("Stack trace:"), area);
VBox.setVgrow(area, Priority.ALWAYS);
alert.getDialogPane().setExpandableContent(details);
alert.showAndWait();
}
}
The above puts the error in a queue if JavaFX has not been initialized yet. At the end of the start method the queue is checked for any errors and they're displayed to the user one after the other. If JavaFX has already been initialized then the error is immediately shown to the user.

Executing a new JavaFX application on a separate JVM

I'm trying to launch a new process on a separate JVM via code following the method illustrated here:
Executing a Java application in a separate process
The code I'm using is the following (taken from the question above):
public static int exec(Class klass) throws IOException, InterruptedException {
String javaHome = System.getProperty("java.home");
String javaBin = javaHome +
File.separator + "bin" +
File.separator + "java";
String classpath = System.getProperty("java.class.path");
String className = klass.getName();
ProcessBuilder builder = new ProcessBuilder(javaBin,"-cp",classpath,className);
Process process = builder.inheritIO().start();
process.waitFor();
return process.exitValue();
}
...in which klass is the class I want to launch.
This would work for a normal Java process, but the problem is that I'm trying to launch a JavaFX application, and the code above generates the following error:
Error: JavaFX runtime components are missing, and are required to run this application
So, to add the JavaFX modules, I tried including the --module-path and --add-modules commands in the declaration of builder, I even attempted copying and pasting the entire execution command, and I kept getting this other error:
Unrecognized option: (command string with modules)
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
How could I solve this?
Let me know if details are needed.
Since the advent of modules, there are at least three different ways a JavaFX application can be configured:
Put everything on the module-path, including the JavaFX modules and your module.
This is the ideal situation but not always possible/viable (e.g. because of incompatible dependencies).
Put the JavaFX modules on the module-path and your own code on the class-path.
This configuration requires the use of --add-modules.
Put everything on the class-path, including the JavaFX modules and your own code.
With this configuration your main class cannot be a subtype of Application. Otherwise you get the error you mentioned in your question: "Error: JavaFX runtime components are missing, and are required to run this application".
This configuration allows for easy use of so-called fat/uber JARs.
Warning: This approach is explicitly unsupported.
The command line used with ProcessBuilder will depend on which configuration your application uses. You also have to take into account any other options passed the command line, such as the default encoding or locale. Unfortunately, your question doesn't provide enough information to tell what exactly is going wrong. The error you mention makes me think you're using the third configuration, but I can't be sure.
That said, I'll give some examples of launching the same application from within the application; you should be able to modify things to fit your needs. Note I used Java/JavaFX 13.0.1 when testing the below code.
Configuration #1
Put everything on the module-path.
module-info.java:
module app {
requires javafx.controls;
exports com.example.app to
javafx.graphics;
}
Main.java:
package com.example.app;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import static java.lang.System.getProperty;
public class Main extends Application {
private static void launchProcess() {
try {
new ProcessBuilder(
Path.of(getProperty("java.home"), "bin", "java").toString(),
"--module-path",
getProperty("jdk.module.path"),
"--module",
getProperty("jdk.module.main") + "/" + getProperty("jdk.module.main.class"))
.inheritIO()
.start();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
#Override
public void start(Stage primaryStage) {
Button launchBtn = new Button("Launch process");
launchBtn.setOnAction(
event -> {
event.consume();
launchProcess();
});
primaryStage.setScene(new Scene(new StackPane(launchBtn), 500, 300));
primaryStage.setTitle("Multi-Process Example");
primaryStage.show();
}
}
Command line:
java --module-path <PATH> --module app/com.example.app.Main
Replace "<PATH>" with a path containing both the JavaFX modules and the above module.
Configuration #2
Put JavaFX modules on the module-path and your code on the class-path.
Main.java:
package com.example.app;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import static java.lang.System.getProperty;
public class Main extends Application {
private static void launchProcess() {
try {
new ProcessBuilder(
Path.of(getProperty("java.home"), "bin", "java").toString(),
"--module-path",
getProperty("jdk.module.path"),
"--add-modules",
"javafx.controls",
"--class-path",
getProperty("java.class.path"),
Main.class.getName())
.inheritIO()
.start();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
#Override
public void start(Stage primaryStage) {
Button launchBtn = new Button("Launch process");
launchBtn.setOnAction(
event -> {
event.consume();
launchProcess();
});
primaryStage.setScene(new Scene(new StackPane(launchBtn), 500, 300));
primaryStage.setTitle("Multi-Process Example");
primaryStage.show();
}
}
Command line:
java --module-path <M_PATH> --add-modules javafx.controls --class-path <C_PATH> com.example.app.Main
Replace "<M_PATH>" with a path containing the JavaFX modules and replace "<C_PATH>" with a path containing the above code.
Configuration #3
Put everything on the class-path. Note the main class (now Launcher) is not a subclass of Application.
Launcher.java:
package com.example.app;
import javafx.application.Application;
public class Launcher {
public static void main(String[] args) {
Application.launch(Main.class, args);
}
}
Main.java:
package com.example.app;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import static java.lang.System.getProperty;
public class Main extends Application {
private static void launchProcess() {
try {
new ProcessBuilder(
Path.of(getProperty("java.home"), "bin", "java").toString(),
"--class-path",
getProperty("java.class.path"),
Launcher.class.getName())
.inheritIO()
.start();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
#Override
public void start(Stage primaryStage) {
Button launchBtn = new Button("Launch process");
launchBtn.setOnAction(
event -> {
event.consume();
launchProcess();
});
primaryStage.setScene(new Scene(new StackPane(launchBtn), 500, 300));
primaryStage.setTitle("Multi-Process Example");
primaryStage.show();
}
}
Command line:
java --class-path <PATH> com.example.app.Launcher
Replace "<PATH>" with a path containing the JavaFX JARs and the above code.

Java Illegal Argument Exception while running my java application

I am trying a build a small video player using java I am getting some errors,please help me fix them.
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Player player = new Player("/Users/name/Desktop/play.mp4");
Scene scene = new Scene(player, 720,480,Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
package sample;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
/**
* Created by akanksh on 03/11/17.
*/
public class Player extends BorderPane {
Media media;
MediaPlayer player;
MediaView view;
Pane mpane;
public Player(String file){
media = new Media(file);
player = new MediaPlayer(media);
view = new MediaView(player);
mpane = new Pane();
mpane.getChildren().add(view);
setCenter(mpane);
player.play();
}
}
errors :
No matter how many times I tried using different videos and different paths,its not working...help needed...
The media class needs a valid URI
Therefore you need a "schema" for your file such as file:///Users/...
Or you could use new File("/Users/name/Desktop/play.mp4").toURI()
The media constructor is looking for a URI, not a file path. If you are really specifying a file for the media (i.e. something the user specified on the file system, for example via a FileChooser) you should convert the path to a valid URI that both has a scheme and properly encodes any invalid characters in the path, such as whitespace.
If you have a File object, you simply do this with
File file = ... ;
Media media = new Media(file.toURI().toString());
If the file is specified as a string, create a file object first (though in any realistic situation you should have a File to begin with):
public Player(String file){
media = new Media(new File(file).toURI().toString());
player = new MediaPlayer(media);
view = new MediaView(player);
mpane = new Pane();
mpane.getChildren().add(view);
setCenter(mpane);
player.play();
}
Note that in the case where the media is part of the application (instead of being provided on the user's filesystem at run time), you should use a different technique entirely and treat it as a resource. See, for example, How to reference javafx fxml files in resource folder? for accessing resources in JavaFX.

Categories

Resources