I'm trying to create a simple UI that is accessible for screenreaders. I've been mostly successful, but I can't manage to design the UI in a way that has the screenreader read new text output.
Currently, I have a TextArea displaying the output of an anonymous PrintStream created and set by System.setOut. Sometimes I open up a TextField for string inputs, but I've been working with just the TextArea to test the reading of text (for now it just listens for keystrokes to display more text for testing purposes).
The issue is this: when new text is added via System.out to the TextArea, the screenreader does not read it. I am still able to navigate upward with the arrow keys to read what was added but it is not read when first added. Is there any way to get the screenreader to treat my TextArea more like a standard console (in which it reads all new text automatically)? I'm using NVDA.
Things I have tried:
- Using TextArea.notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT)
- Using TextArea.requestFocus() and TextArea.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_NODE)
- Disabling autoflush on the PrintStream while using TextArea.setAccessibleText(theNewText) during a flush
- Using a hidden Label set to the new text and focusing on it (I'm still fiddling with this one; Screenreaders can't read actual "hidden" text so I'm trying to find a way to draw it but also be "invisible", perhaps behind the TextArea somehow)
- Changing focus to another Node and back, which doesn't work as I like because it reads the other Nodes accessible stuff and then reads the entire body of the TextArea
- Various combinations of these
I just can't seem to get it to work. I feel like I'm missing something simple and obvious here, but the JavaFX Accessibility API is still relatively new and I can't find solutions to specific problems like this one.
Here's the relevant code of my Application, if it helps any:
#Override
public void start(Stage primaryStage) {
try {
primaryStage.setTitle("Test");
root = new BorderPane();
root.setFocusTraversable(false);
Scene scene = new Scene(root,800,600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
//Create middle
output = new TextArea();
output.setEditable(false);
output.setFocusTraversable(false); //I've tried true also, just to test
output.setAccessibleRole(AccessibleRole.TEXT_AREA);
root.setCenter(output);
...
//Begin
primaryStage.show();
Thread th = new Thread(new AppMain());
th.start();
} catch(Exception e) {
e.printStackTrace();
}
}
#Override
public void init() {
//Set output to TextArea
System.setOut(new PrintStream(new OutputStream() {
#Override
public void write(int b) throws IOException {
appendTextArea(String.valueOf((char) b));
}
}, true)); //I've also overriden flush while this is false, see above
}
public void appendTextArea(String str) {
Platform.runLater(() -> {
output.appendText(str);
});
}
I seriously appreciate any help or suggestions you can provide. I've been messing with this small issue for way too long, and I'm still new to JavaFX. Thank you!
Here is a full working example based off of your code.
Disclosure: For the screen reader I'm using the "Voice Over Utility" on my Mac, but hopefully that doesn't differ too much from your environment.
The key was to utilize the Control#executeAccessibleAction method.
Example:
TextArea.executeAccessibleAction(AccessibleAction.SET_TEXT_SELECTION, start, finish);
The Application Class
package application;
import java.io.PrintStream;
import java.io.IOException;
import java.io.OutputStream;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleRole;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
public class Main extends Application
{
private TextArea output;
private BorderPane root;
private final StringBuilder STR_BUFFER = new StringBuilder();
private static final String NEW_LINE = System.lineSeparator();
#Override
public void start(Stage primaryStage)
{
try
{
primaryStage.setTitle("Test");
root = new BorderPane();
root.setFocusTraversable(false);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
// Create middle
output = new TextArea();
output.setEditable(false);
output.setFocusTraversable(true); // I've tried true also, just to test
// ----------------------------------------------
// Tell the Screen Reader what it needs to access
// ----------------------------------------------
output.setAccessibleRole(AccessibleRole.TEXT_AREA);
root.setCenter(output);
// ...
// Begin
primaryStage.show();
// start the thread
Thread th = new Thread(new AppMain());
th.start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
#Override
public void init()
{
// Set output to TextArea when we have a full string
System.setOut(new PrintStream(new OutputStream()
{
#Override
public void write(int b) throws IOException
{
if (b == '\r')
{
return;
}
if (b == '\n')
{
final String text = STR_BUFFER.toString() + NEW_LINE;
appendTextArea(text);
STR_BUFFER.setLength(0);
}
else
{
STR_BUFFER.append((char) b);
}
}
}, true));
}
public void appendTextArea(String str)
{
Platform.runLater(() ->
{
int anchor = output.getText().length();
output.appendText(str);
// just to clear it
output.positionCaret(0);
// ----------------------------------------------
// Tell the Screen Reader what it needs to do
// ----------------------------------------------
output.executeAccessibleAction(AccessibleAction.SET_TEXT_SELECTION, anchor, anchor + str.length());
});
}
public static void main(String[] args)
{
launch(args);
}
}
The Thread Class
/*
* Just to simulate a feed to the console (textArea).
* This will die after 1 minute.
*/
package application;
public class AppMain implements Runnable
{
#Override
public void run()
{
int i = 0;
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 60000)
{
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{}
System.out.println("This is line number " + ++i);
}
}
}
Related
I am designing a matching memory game, I am almost done with it and everything is working as it should be, however, when the user has opened two different cards the program won't pause(wait) few seconds so the user can see what the second card was.
I have tried using a long for loop operation but encountered the same problem. I have tried Thread.sleep, TimeUnit.SECONDS.sleep, Task and Platform.runLater.
The program opens the card and closes it instantly THEN it waits for the specified duration, keeping in mind that I am calling pauseThread after open and before close functions.
I have tried the above suggestions but they are leading me no where and I can't seem to find where the problem is with my code or where should I place the pauseThread. Thanks in advance.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class MemoryMatchingGame extends Application{
private static Card selectedCard=null; // This is to save a reference for the first card to use in comparison
private static int numOfCorrectPairs = 0; // Keeping track of how many cards the user got correct
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
String[] images = {"C:\\Users\\userName\\Desktop\\Project#4\\1.png", // This is a string array to store images locations
"C:\\Users\\userName\\Desktop\\Project#4\\2.png",
"C:\\Users\\userName\\Desktop\\Project#4\\3.jpg",
"C:\\Users\\userName\\Desktop\\Project#4\\4.jpg",
"C:\\Users\\userName\\Desktop\\Project#4\\5.jpg",
"C:\\Users\\userName\\Desktop\\Project#4\\6.png",
"C:\\Users\\userName\\Desktop\\Project#4\\7.jpg",
"C:\\Users\\userName\\Desktop\\Project#4\\8.jpg"};
ArrayList<Card> listOfCards = new ArrayList<Card>();
for(int i=0; i<images.length; i++) { // This for loop will add each image twice to the array list
listOfCards.add(new Card(images[i]));
listOfCards.add(new Card(images[i]));
}
Collections.shuffle(listOfCards); // Shuffling the deck of cards
primaryStage.setTitle("Memory Matching Game");
HBox hb = new HBox();
VBox firstColoumn = new VBox();
for(int i=0; i<4; i++)
firstColoumn.getChildren().add(listOfCards.get(i));
VBox secondColoumn = new VBox();
for(int i=4; i<8; i++)
secondColoumn.getChildren().add(listOfCards.get(i));
VBox thirdColoumn = new VBox();
for(int i=8; i<12; i++)
thirdColoumn.getChildren().add(listOfCards.get(i));
VBox fourthColoumn = new VBox();
for(int i=12; i<16; i++)
fourthColoumn.getChildren().add(listOfCards.get(i));
hb.getChildren().addAll(firstColoumn, secondColoumn, thirdColoumn, fourthColoumn);
Scene scene = new Scene(hb, 460, 450);
primaryStage.setScene(scene);
primaryStage.show();
}
private class Card extends Button {
private String imageLocation; // To store the destination of the image
private Image img; // To store a reference of the image to be used when setting graphic on a button
public Card(String imageLocation) throws FileNotFoundException {
this.imageLocation = imageLocation;
FileInputStream fis = new FileInputStream(imageLocation);
img = new Image(fis);
setPrefSize(150, 150);
setOnMouseClicked(e -> {
if(isCardOpen()==true)
return; // To ensure no action is made once an image is already opened and the user clicked on it again
if(selectedCard==null) {// This will test if the user has a card open already for comparison or not, if not it will store a reference to the card to use to compare once another card is opened
selectedCard = this;
open();
}
else { // If we enter this statement, this means the user has a card open already and we are ready to perform comparison
open(); // First action taken is to reveal the second card then perform comparison
if(this.isEqual(selectedCard)) {
numOfCorrectPairs++;
System.out.println("Got one");
}
else {
//Get program to pause here
Hold pauseThread = new Hold();
pauseThread.run();
System.out.println("After pausing");
this.close();
selectedCard.close();
}
selectedCard=null; // This will nullify the variable so that we are able to perform comparison again for two other cards
} // End of else statement
}); // End of actionHandler
close(); // This will ensure whenever a card is created it is set face-down
}
private void close() {
setGraphic(null);
}
public void open() {
setGraphic(new ImageView(img));
System.out.println("Open");
}
private boolean isCardOpen() {
return this.getGraphic()!=null;
}
private boolean isEqual(Card selectedCard) {
return this.imageLocation.equals(selectedCard.imageLocation);
}
}
private class Hold extends Thread{
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Everything in your code is running in the JavaFX Application Thread. You don't want to pause this thread because it will lock your GUI. As has already been mentioned, you are starting another thread and putting it to sleep, but this doesn't add delay to your GUI that is running in the JavaFX Thread.
An alternative approach would be to use Platform.runLater(). The Hold thread can invoke a method in the JavaFX thread that implements a Platform.runLater() runnable. The runnable is a short lambda that holds the code to close the selected card. The timing may vary slightly from 3000 ms, but you don't have much going on in the JavaFX thread and it doesn't seem critical for this application.
Here are the modifications to try.
First modify the Hold class to include a constructor to pass in the Card object. Then call the closeAfterPause() method on card.
private class Hold extends Thread {
private Card card;
public Hold(Card card) {
this.card = card;
}
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
card.closeAfterPause();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Then create the closeAfterPause() method in the MemoryMatchingGame class.
private void closeAfterPause() {
Platform.runLater(() -> {
System.out.println("After Pausing");
close();
selectedCard.close();
});
}
Then modify the else part of your if-else statement as follows
else {
//Get program to pause here
Hold pauseThread = new Hold(this);
new Thread(pauseThread).start();
}
FX comes with a rich set of Animation/Timeline support - there's no need for falling back onto bare Threads. The most simple form of getting wait-for-xx is to use a Timeline configured with xx and an actionHandler that does something when ready:
Timeline holdTimer = new Timeline(new KeyFrame(
Duration.seconds(2), e -> closeCards()));
Also it's a good idea to centralize all logic outside of a Control. Actually, you should never-ever extend a view for the purpose to include view-unrelated logic. So your long-time goal should be to
extract all single card logic from Card into a CardModel which exposes properties like f.i. image, id, open, disposed
use a plain Button, configure and bind its properties to model properties as appropriate
centralize all game logic like timing, selecting, when opening is allowed, when succeed into a game controller
As I don't want to spoil your fun in doing that - I'll just post a little outline in the direction of the last bullet. Its responsibilities so far
provide api to open a single card
provide api to end a turn: either match or close cards
internals to keep track of opened cards and timing
The snippets just re-mixes your code a bit, moving game logic from the button into the controller (aka: here simply the outer class) and setting the button's action handler to access the controller.
private Card firstCard;
private Card secondCard;
private Timeline holdTimer = new Timeline(new KeyFrame(
Duration.millis(2000), e -> closeCards()));
public void closeCards() {
if (firstCard == null || secondCard == null) {
System.out.println("error!!");
return;
}
if (firstCard.isEqual(secondCard)) {
System.out.println("success");
firstCard.setDisable(true);
secondCard.setDisable(true);
firstCard = null;
secondCard = null;
} else {
firstCard.close();
secondCard.close();
firstCard = null;
secondCard = null;
}
}
public void openCard(Card card) {
if (card.isCardOpen()) return;
if (holdTimer.getStatus() == Status.RUNNING) return;
if (firstCard == null) {
firstCard = card;
firstCard.open();
} else if (secondCard == null) {
secondCard = card;
secondCard.open();
holdTimer.playFromStart();
}
}
// Dont! dont, dont!!! ever extend a Control
//**TBD**: Move open/close state logic into a CardModel
// then configure a plain Button with the properies of that model
private class Card extends Button {
private String imageLocation; // To store the destination of the image
// private Image img; // To store a reference of the image to be used when setting graphic on a button
public Card(String imageLocation) throws FileNotFoundException {
this.imageLocation = imageLocation;
setPrefSize(150, 150);
setOnAction(e -> openCard(this));
}
public void close() {
setText("");
}
public void open() {
setText(imageLocation);
System.out.println("Open");
}
public boolean isCardOpen() {
return getText() != null && getText().length() > 0;//this.getGraphic()!=null;
}
private boolean isEqual(Card selectedCard) {
if (selectedCard == null) return false;
return this.imageLocation.equals(selectedCard.imageLocation);
}
}
I am brand-new to programming and only started learning this past 2 weeks, so I'm sorry for any redundant or sloppy code...
I have 2 scenes, which are in my Main class. But I'm using FXML to develop each scene, and all code has been placed in the first scene's FXML Controller. I'm ready to start building my second scene, but don't know how to properly launch it.
My question is, how can I set the stage to show the second scene (mainCallWindow), specifically from within the first FXML file's controller class. If there is a bettery way, please let me know.
Main Class:
package supportTool;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
public String versionNumber = "v2.1";
#Override
public void start(Stage primaryStage) throws Exception {
// SETTING UP THE STAGE
Stage window;
window = primaryStage;
window.setTitle("Support Tool " + versionNumber);
// SETTING UP THE SCENES
Parent newCallDetailsFXML = FXMLLoader.load(getClass().getResource("newCallDetails.fxml"));
Parent mainCallWindowFXML = FXMLLoader.load(getClass().getResource("mainCallWindow.fxml"));
Scene newCallDetails = new Scene (newCallDetailsFXML, 800, 600);
Scene mainCallWindow = new Scene (mainCallWindowFXML, 800, 600);
// CHOOSING THE SCENE AND SHOWING THE STAGE
window.setScene(newCallDetails);
window.show();
}
}
Scene 1 FXML Controller:
package supportTool;
import javafx.scene.control.*;
import javafx.scene.image.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class newCallController {
private int maxChar;
public ChoiceBox choiceAccount;
public ImageView btnCall;
public TextField tfCallbackNumber;
public TextField tfCallerName;
public TextField tfStoreNumber;
// ACTION COMPLETED WHEN CALL BUTTON IS PRESSED
public void btnCall() {
Caller newCaller = new Caller();
newCaller.setCallerName(tfCallerName.getText());
newCaller.setCallbackNumber(tfCallbackNumber.getText());
newCaller.setAccount(String.valueOf(choiceAccount.getValue()));
newCaller.setStoreNumber(tfStoreNumber.getText());
try {
FileOutputStream fos = new FileOutputStream("caller.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(newCaller);
oos.close();
fos.close();
} catch(IOException e){
e.printStackTrace();
}
// RIGHT HERE IS WHERE I WANT TO SET THE SCENE TO "mainCallWindow"
}
// CHECKS TO SEE IF THE TEXT CONTAINS ONLY LETTERS
private boolean isNumberCheckEvent(String message) {
if (message.matches("[0-9]+")) {
return true;
} else {
return false;
}
}
// SETS THE MAX CHARACTERS FOR ALL TEXTFIELDS
public void maxCharEvent() {
// CALLER NAME MAX CHARACTERS
tfCallerName.setOnKeyTyped(maxCharEvent -> {
maxChar = 20;
if(tfCallerName.getText().length() >= maxChar) {
maxCharEvent.consume();
}
});
// CALLBACK NUMBER MAX CHARACTERS
tfCallbackNumber.setOnKeyTyped(maxCharEvent -> {
maxChar = 10;
if(tfCallbackNumber.getText().length() >= maxChar) {
maxCharEvent.consume();
}
});
// STORE NUMBER MAX CHARACTERS
tfStoreNumber.setOnKeyTyped(maxCharEvent -> {
maxChar = 5;
if (String.valueOf(choiceAccount.getValue()).equals("6 Digit Account")) {
maxChar = 6;
}
if (tfStoreNumber.getText().length() >= maxChar) {
maxCharEvent.consume();
}
});
}
// CHANGES TEXT TO ONLY LETTERS BASED ON isNumberCheckEvent
public void numberValidationEvent() {
tfCallbackNumber.setOnKeyReleased(numberValidationEvent -> {
maxCharEvent();
if(tfCallbackNumber.getText().length() > 0) {
if (!isNumberCheckEvent(tfCallbackNumber.getText())) {
tfCallbackNumber.setText(tfCallbackNumber.getText().substring(0, tfCallbackNumber.getText().length() - 1));
tfCallbackNumber.positionCaret(10);
numberValidationEvent.consume();
}
}
});
tfStoreNumber.setOnKeyReleased(numberValidationEvent -> {
maxCharEvent();
if(tfStoreNumber.getText().length() > 0) {
if (!isNumberCheckEvent(tfStoreNumber.getText())) {
tfStoreNumber.setText(tfStoreNumber.getText().substring(0, tfStoreNumber.getText().length() - 1));
tfStoreNumber.positionCaret(10);
numberValidationEvent.consume();
}
}
});
}
}
You can change scene in various ways. Within your current situation you can try something like below. First, you need reference to your FXMLLoader, scene and stage to change your scene from your controller. Instead of loading in main class, do loading in your controller class.
FXMLLoader loader = new FXMLLoader(getClass().getResource("mainCallWindow.fxml"));
Parent mainCallWindowFXML = loader.load();
//use one of components on your scene to get a reference to your scene object.
Stage stage = (Stage)tfCallerName.getScene.getWindow();//or use any other component in your controller
Scene mainCallWindow = new Scene (mainCallWindowFXML, 800, 600);
stage.setScene(newCallDetails);
stage.show(); //this line may be unnecessary since you are using the same stage.
}
This is not the only way to achieve this. You can use same scene to load different FXML files. I would suggest changing the root node of a scene instead of changing scene completely.
i have a simple JavaFX stage with a TextField. What i want to do is: when user inserts letters into the TextField, i want to print "now" (just to look if it works). Im using a Thread because later i want to scan a dictonary to see, if the letters the user entered are part of words from the dictionary.
But i get: java.lang.IllegalMonitorStateException
Any ideas? I don't seem to understand the whole concept of Condition.await and Multithreading..
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DictionaryThreading extends Application {
private static Lock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public static void main(String[] args) {
launch();
}
private static class ScanWords implements Runnable{
#Override
public void run() {
lock.lock();
try{
while(true){
this.wait();
System.out.println("clicked");
}
} catch (Exception e){
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
#Override
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
new ScanWords().run();
TextField tf = new TextField("Please enter a word");
tf.setOnKeyPressed(e -> {});
pane.getChildren().add(tf);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
There is no need to create a thread that does nothing other than wait for user events. The JavaFX framework already provides this for you (it is one of the fundamental pieces of functionality of any UI toolkit). All you need to do to respond to changes in the text in a text field is register a change listener with the text field's text property:
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
TextField tf = new TextField("Please enter a word");
tf.textProperty().addListener((obs, oldText, newText) -> {
System.out.println("text changed");
});
pane.getChildren().add(tf);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
If the thing you need to do in response to the text changing takes a long time, then you should launch that process in a background thread in the listener on the text field. If you are searching something large, you probably want to cancel any existing search, so that you don't end up with a large number of searches all running concurrently. The JavaFX Service class provides the functionality you need for this:
public class SearchService extends Service<List<String>> {
// modify and access only on FX Application Thread:
private String searchString ;
#Override
protected Task<List<String>> createTask() {
final String s = searchString ;
return new Task<List<String>>() {
#Override
protected List<String> call() throws Exception {
List<String> matches = new ArrayList<>();
// do search for strings matching s
// be sure to check isCancelled() regularly
return matches ;
}
};
}
public String getSearchString() {
checkThread();
return searchString ;
}
public void setSearchString(String searchString) {
checkThread();
this.searchString = searchString ;
}
private void checkThread() {
if (! Platform.isFxApplicationThread()) {
throw new IllegalStateException("Not on FX Application Thread");
}
}
}
Then you can do
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
SearchService searchService = new SearchService();
searchService.setOnSucceeded(e -> {
List<String> matches = searchService.getValue();
// do whatever you need with search results...
// this is called on FX application thread
});
TextField tf = new TextField("Please enter a word");
tf.textProperty().addListener((obs, oldText, newText) -> {
searchService.cancel();
searchService.setSearchText(newText);
searchService.restart();
});
pane.getChildren().add(tf);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
I don't use JavaFX, but I think you need to use EventListener. Try to use TextListener or InputMethodListener. For example:
StackPane pane = new StackPane();
TextField tf = new TextField("Please enter a word");
tf.addTextListener(e -> System.out.println("Pushed"));
pane.getChildren().add(tf);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
wait method should be executed within synchronized block:
try{
while(true){
synchronized(this){
this.wait();
System.out.println("clicked");
}
}
} catch (Exception e){
e.printStackTrace();
} finally{
lock.unlock();
}
I am getting the follow error when I run a JavaFx application as "Run" only. Debug works fine...
Exception in thread "main" java.lang.ExceptionInInitializerError
at Goose.Program.<clinit>(Program.java:26)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:123)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:550)
at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:512)
at javafx.scene.control.Control.<clinit>(Control.java:87)
... 4 more
I have read that you should subclass Application but I am already doing that so I am not sure why it doesn't work... It works fine if I debug but as soon as I try to run the application instead of debugging it, it throws that error message. Which is a little crazy.... Anyone have any idea what the heck is going on? Here is the code.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Program extends Application{
TextField input;
GameServer gm;
Player p = new Player();
/**
* Just starts our GameServer
*/
public static void main(String[] args) throws Exception {
launch(args);
}
public static final TextArea textArea = new TextArea();
#Override
public void start(Stage primaryStage) {
p.setState(Player.States.Ready);
p.setAccess(Player.AccessStatus.GameMaster);
input = new TextField();
input.setPrefWidth(500);
input.setOnKeyPressed(event -> {
if(event.getCode().equals(KeyCode.ENTER)){
textArea.appendText("Command: " + input.getText() + "\n");
handleEvent(input);
input.setText("");
}
});
GridPane gridPane = new GridPane();
gridPane.setAlignment(Pos.CENTER);
gridPane.setHgap(10);
gridPane.setVgap(10);
gridPane.add(input, 0, 0, 2, 1);
gridPane.add(textArea, 0,2, 2, 1);
Scene scene = new Scene(gridPane, 530, 250);
primaryStage.setMaxWidth(540);
primaryStage.setMaxHeight(280);
primaryStage.setMinWidth(540);
primaryStage.setMinHeight(280);
primaryStage.setTitle("My Server");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setOnCloseRequest(we -> {
try {
textArea.appendText("Shutting down server...");
if(gm.gameworld.getRunning()) {
gm.gameworld.setRunning(false);
Thread.sleep(2000);
}
System.exit(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
run();
}
public void run(){
try {
GameServer gameServer = new GameServer();
this.gm = gameServer;
gameServer.start();
}catch (Exception e){
e.printStackTrace();
}
}
public void handleEvent(TextField textField){
try {
String eventKey = textField.getText().trim();
Event e = gm.gameworld.getEventHandler().stringToEvent.get(eventKey);
if(e != null) {
e.setPlayer(p);
e.ready(gm.gameworld);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
There is a 'public void init() throws Exception' method in Application class whose documentation says:
"The application initialization method. This method is called immediately after the Application class is loaded and constructed. An application may override this method to perform initialization prior to the actual starting of the application". "NOTE: This method is not called on the JavaFX Application Thread. An application must not construct a Scene or a Stage in this method. An application may construct other JavaFX objects in this method."
So, I suppose that you should move: p = new Player(); and
textArea = new TextArea(); to it.
How to call the launch() more than once in java i am given an exception as "ERROR IN MAIN:java.lang.IllegalStateException: Application launch must not be called more than once"
I have create rest cleint in my java application when request comes it call javafx and opening webview after completing webview operarion am closing javafx windows using Platform.exit() method. when second request comes am getting this error how to reslove this error.
JavaFx Application Code:
public class AppWebview extends Application {
public static Stage stage;
#Override
public void start(Stage _stage) throws Exception {
stage = _stage;
StackPane root = new StackPane();
WebView view = new WebView();
WebEngine engine = view.getEngine();
engine.load(PaymentServerRestAPI.BROWSER_URL);
root.getChildren().add(view);
engine.setJavaScriptEnabled(true);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
engine.setOnResized(new EventHandler<WebEvent<Rectangle2D>>() {
public void handle(WebEvent<Rectangle2D> ev) {
Rectangle2D r = ev.getData();
stage.setWidth(r.getWidth());
stage.setHeight(r.getHeight());
}
});
JSObject window = (JSObject) engine.executeScript("window");
window.setMember("app", new BrowserApp());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
RestClient Method:
Calling to JavaFX application
// method 1 to lanch javafx
javafx.application.Application.launch(AppWebview.class);
// method 2 to lanch javafx
String[] arguments = new String[] {"123"};
AppWebview .main(arguments);
You can't call launch() on a JavaFX application more than once, it's not allowed.
From the javadoc:
It must not be called more than once or an exception will be thrown.
Suggestion for showing a window periodically
Just call Application.launch() once.
Keep the JavaFX runtime running in the background using Platform.setImplicitExit(false), so that JavaFX does not shutdown automatically when you hide the last application window.
The next time you need another window, wrap the window show() call in Platform.runLater(), so that the call gets executed on the JavaFX application thread.
For a short summary implementation of this approach:
See the answer by sergioFC
If you are mixing Swing you can use a JFXPanel instead of an Application, but the usage pattern will be similar to that outlined above.
For an example of the JFXPanel apprach, see Irshad Babar
s answer.
Wumpus Sample
This example is bit more complicated than it needs to be because it also involves timer tasks. However it does provide a complete stand-alone example, which might help sometimes.
import javafx.animation.PauseTransition;
import javafx.application.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
// hunt the Wumpus....
public class Wumpus extends Application {
private static final Insets SAFETY_ZONE = new Insets(10);
private Label cowerInFear = new Label();
private Stage mainStage;
#Override
public void start(final Stage stage) {
// wumpus rulez
mainStage = stage;
mainStage.setAlwaysOnTop(true);
// the wumpus doesn't leave when the last stage is hidden.
Platform.setImplicitExit(false);
// the savage Wumpus will attack
// in the background when we least expect
// (at regular intervals ;-).
Timer timer = new Timer();
timer.schedule(new WumpusAttack(), 0, 5_000);
// every time we cower in fear
// from the last savage attack
// the wumpus will hide two seconds later.
cowerInFear.setPadding(SAFETY_ZONE);
cowerInFear.textProperty().addListener((observable, oldValue, newValue) -> {
PauseTransition pause = new PauseTransition(
Duration.seconds(2)
);
pause.setOnFinished(event -> stage.hide());
pause.play();
});
// when we just can't take it anymore,
// a simple click will quiet the Wumpus,
// but you have to be quick...
cowerInFear.setOnMouseClicked(event -> {
timer.cancel();
Platform.exit();
});
stage.setScene(new Scene(cowerInFear));
}
// it's so scary...
public class WumpusAttack extends TimerTask {
private String[] attacks = {
"hugs you",
"reads you a bedtime story",
"sings you a lullaby",
"puts you to sleep"
};
// the restaurant at the end of the universe.
private Random random = new Random(42);
#Override
public void run() {
// use runlater when we mess with the scene graph,
// so we don't cross the streams, as that would be bad.
Platform.runLater(() -> {
cowerInFear.setText("The Wumpus " + nextAttack() + "!");
mainStage.sizeToScene();
mainStage.show();
});
}
private String nextAttack() {
return attacks[random.nextInt(attacks.length)];
}
}
public static void main(String[] args) {
launch(args);
}
}
Update, Jan 2020
Java 9 added a new feature called Platform.startup(), which you can use to trigger startup of the JavaFX runtime without defining a class derived from Application and calling launch() on it. Platform.startup() has similar restrictions to the launch() method (you cannot call Platform.startup() more than once), so the elements of how it can be applied is similar to the launch() discussion and Wumpus example in this answer.
For a demonstration on how Platform.startup() can be used, see Fabian's answer to How to achieve JavaFX and non-JavaFX interaction?
I use something like this, similar to other answers.
private static volatile boolean javaFxLaunched = false;
public static void myLaunch(Class<? extends Application> applicationClass) {
if (!javaFxLaunched) { // First time
Platform.setImplicitExit(false);
new Thread(()->Application.launch(applicationClass)).start();
javaFxLaunched = true;
} else { // Next times
Platform.runLater(()->{
try {
Application application = applicationClass.newInstance();
Stage primaryStage = new Stage();
application.start(primaryStage);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
try this, I tried this and found successful
#Override
public void start() {
super.start();
try {
// Because we need to init the JavaFX toolkit - which usually Application.launch does
// I'm not sure if this way of launching has any effect on anything
new JFXPanel();
Platform.runLater(new Runnable() {
#Override
public void run() {
// Your class that extends Application
new ArtisanArmourerInterface().start(new Stage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}