Initializing second window in separate class (e.g. ChoiceBox) - java

I have two window controllers, both of which load an FXML file and show a screen. The first, GUIController is the main window, which spawns a second window, PackageBuilder, inside which the user inputs some data and it's stored.
The second window is spawned via a Button in the main window like so (FXML linked):
#FXML private void onNewPackage(ActionEvent e){
PackageBuilder pb = new PackageBuilder(owner); // Scene owner
}
I figured the second class cannot call the JavaFX Application launch method and the original Scene needs to be passed along. So I wrote this inside PackageBuilder:
public PackageBuilder(Scene owner) {
/* Removed redundant setup info */
fstart(owner);
}
private void fstart(owner) {
Stage window = new Stage();
window.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader;
Scene scene;
try {
loader = new FXMLLoader(getClass().getResource("PackageBuilder.fxml"));
scene = new Scene(loader.load());
window.initOwner(owner);
window.setScene(scene);
window.showAndWait();
} catch (IOException e) {
e.printStackTrace();
}
}
This all would be all good, if the initialize-method could be called. But it can't. So is there a way to for example initialize the elements of a ChoiceBox anywhere else?
I'm assuming there's a stupid mistake somewhere. There always is.

The second class with a ChoiceBox to be initialized can implement an interface:
public class PackageBuilder implements Initializable{
#Override
public void initialize(URL url, ResourceBundle rb){
/* Initialize things needed */
}
}
The method is called before the constructor of the class.

Related

ScalaFX (or JavaFX) - Calling GUI from another main class multiple times

I am following on from a previous question: link
I am writing a standalone visualization package for a simulation package
The simulation package is written by one of our team in Scala
What I want to do, is create a matplotlib like package for this simulation package. My envisioned end use would look something like matplotlib:
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
plt.show()
plt.plot([10, 20, 3000, 4121212])
plt.ylabel('some numbers')
plt.show()
For my package, I would do something analogous. Assume here that myFXPackage is a chart package written in ScalaFX. In my DRIVER class:
Import myFXPackage
// run some simulation code here...
myFXPackage.plot(results)
// run some MORE simulation code here...
myFXPackage.plot(results)
Now it seems that for ScalaFX, there can only be one entry point for the whole app; this is the JFXApp class. However, I want to import the package and simply run this code multiple times in my DRIVER class as shown above. So somehow how DRIVER class would call ScalaFX and run the plot, close it, then run another plot.
Is this feasible? If so how would I go about doing this?
Most JavaFX example code conflates the main method with the Application subclass, and in many cases even does the UI layout, etc., in the same class. There's not necessarily a reason to do this, and in the latter case it's not a particularly good design.
Assuming you separate concerns appropriately in your UI code, you might have a class like
public class MainUI {
private BorderPane root ;
public MainUI() {
root = new BorderPane();
// do layout, register event handlers, etc etc
}
public Pane getView() {
return root ;
}
}
Then if you want a standalone JavaFX application, you would do
public class JavaFxApp extends Application {
#Override
public void start(Stage stage) {
MainUI ui = new MainUI();
Scene scene = new Scene(ui.getView());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
But this isn't the only place you can use the UI class. In JavaFX (as in most UI toolkits), you must create a new stage, and perform other UI tasks, on the UI thread (i.e. the FX Application Thread).
Additionally, in JavaFX you must explicitly start up the FX toolkit. In the code above, this is done by the Application.launch() method.
JavaFX 9 introduced a Platform.startup(Runnable), which starts the FX toolkit and executes the supplied Runnable on the FX Application Thread.
So, using JavaFX 9 and later, you can have code like
public class SomeApp {
public static void main(String[] args) {
// start FX toolkit
// Since we don't want it to exit when we close a window, set
// implicit exit to false:
Platform.startup(() -> Platform.setImplicitExit(false));
// do some other stuff...
// whenever you need to:
Platform.runLater(SomeApp::showStage);
// do other stuff...
}
private static void showStage() {
Scene scene = new Scene(new MainUI().getView());
Stage stage = new Stage();
stage.show();
}
}
Prior to JavaFX 9, you can still do this, but you need to launch a "blank" application:
public class FXStartup extends Application {
#Override
public void start(Stage ignored) {
// probably need this:
Platform.setImplicitExit(false);
}
}
Note that the launch() method blocks until the toolkit exits, so your application needs to start it in another thread:
public class SomeApp {
public static void main(String[] args) {
// start FX toolkit
new Thread(() -> Application.launch(FXStartup.class, args)).start();
// do some other stuff...
// whenever you need to:
Platform.runLater(SomeApp::showStage);
// do other stuff...
}
private static void showStage() {
Scene scene = new Scene(new MainUI().getView());
Stage stage = new Stage();
stage.show();
}
}

How to catch TableView creation in JavaFX 8

I have a JavaFX application with a TableView that I need to fill up with data once the application starts. I am starting the application in the following manner:
private LayoutController theController;
#Override
public void start(Stage primaryStage)
{
try {
FXMLLoader fxmlload = new FXMLLoader(getClass().getResource("Sample.fxml"));
BorderPane root = (BorderPane )fxmlload.load();
Scene scene = new Scene(root,640,480);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
theController = (LayoutController )fxmlload.getController();
primaryStage.setTitle("Title Application");
primaryStage.addEventHandler(WindowEvent.WINDOW_SHOWN,theController.windowStarted);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
In my controller, called SampleController, I have the TableView object so that it (initially) creates some columns once it is up:
#FXML Parent myRoot;
#FXML TableView datTable<DataClass>;
private Stage theStage;
public EventHandler<WindowEvent> windowStarted = event -> {
theStage = (Stage )myRoot.getScene().getWindow();
getData();
};
protected void getData()
{
dataTable.setEditable(false);
.
// Call a SOAP service to get the data
.
}
I had assumed that once the Stage's WINDOW_SHOWN event occurs, the controls are created and I can do things with them. That apparently isn't the case. Apparently, controls specified using FXML are actually created sometime after the main application window is created!
What happens is that when the windowStarted lambda is executed, the getData() method gets called, but apparently the dataTable was not created before the WINDOW_SHOWN event occurred. As a result, I get NullPointerException failures when I try to call any of dataTable's methods!
I need to catch when the dataTable actually gets created so that I can use its methods. Is there some way to do this?
Someone please advise...
Put your code for data download in method initialize which is called on controller after its root element has been completely processed or in other words after all FXML field are assigned.
#FXML
public void initialize() {
//Here!
}

Could not initialize class javafx.stage.screen

I'm trying to launch 2 javaFX applications, obviously Application#launch() can only be called once per JVM.
After some browsing one told me to manually create a Scene and call Application#start() for the second Application, and so I did:
public class Launcher extends Application {
private Stage primaryStage;
#Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
this.primaryStage.setResizable(false);
this.primaryStage.setTitle("title");
initLayout();
}
public void initLayout() throws IOException {
Parent root = FXMLLoader.load(Launcher.class.getResource("myFile.fxml"));
Scene scene = new Scene(root, 450, 300);
this.primaryStage.setScene(scene);
this.primaryStage.show();
}
}
And loading it (from another class) with:
try {
Application launcher = new Launcher();
launcher.start(new Stage());
} catch (Exception e) {}
Though this results in an error saying
Exception in thread "Thread_number" java.lang.NoClassDefFoundError: Could not initialize class javafx.stage.Screen
at javafx.stage.Window.<init><Unknown Source>
at javafx.stage.Stage.<init><Unknown Source>
at javafx.stage.Stage.<init><Unknown Source>
at javafx.stage.Stage.<init><Unknown Source>
at classILaunchedFrom.methodLaunchedFrom<Main.java:lineNumber>
Does anyone know what I'm doing wrong, because I'm completely at a loss here. Been cracking my skull far too long with javaFX now.
Why do you want to completely separate applications? Just make a class that extends stage for your second window and then call it's .show() method from inside the main javafx application thread. Javafx is rather picky when doing things, only one application instance at a time, all work must be done on the same thread and so on.

JavaFX Object is always null after launch

I'm trying to create an Application, using JavaFX. But the JavaFX Application is not the Main entrance of the Application.
I'm using a Main-Class, a Controller-Class (which controlls everything), and other classes like the JavaFX Application
Main -(calls)-> Controller -(creates)-> JavaFX Application
After the JavaFX Application Object is created, the Controller calls a method, so the JavaFX Application Object has an instance of Controller
But this object is always null, as soon as I'm outside of the method-call.
Main
public class Main{
public static void main(String[] args){
Controller c = new Controller();
}
}
Controller
public class Controller{
private MyApplication app;
public Controller(){
app = new MyApplication(); //create Application
app.setController(this); //set Controller Object
app.startApplication(); //launch the application
}
}
MyApplication
public class MyApplication extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private Controller controller;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
initRootLayout();
}
public void setController(Controller con){
this.controller = con;
}
public void startApplication(String... args){
launch(args);
}
public void initRootLayout(){
System.out.println(controller==null); //returns true. But why?
try{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MyApplication.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
}
catch(IOException e){
e.printStackTrace();
}
}
}
How come, the Controller Instance within MyApplication is always null. The only time it's not null is within the call setController
Since JavaFX doesn't have system tray access, you basically need an AWT application to run in the system tray. I would then consider embedding the JavaFX aspects in Swing using a JFXPanel, and basically make it a Swing/AWT application with some JavaFX embedded.
Alternatively, you could launch everything from an Application subclass, and just bootstrap the AWT part in the start method, setting up the triggers to do the JavaFX stuff when needed. That feels uglier though.
Finally, your approach only really fails because you need to pass an object to the JavaFX application. If it makes sense to make that object a singleton, then you could just let the JavaFX application retrieve it, rather than passing the object to it.
I think your problem is that you are referencing different instances of your Controller class. After the call to launch(args) JavaFX creates its own private instance of your Application class. When you call setController you are setting the controller for a different instance of your class than what JavaFX is using. One way to fix this would be to make the controller variable and its setter static.

NullPointerException when using TextArea.append()

I realize there is an nearly identical question in title. The question does not seem to be relevant to my particular issues.
I'm using the JavaFX scene builder to create my UI, (which includes the TextArea in question). All I want to do is take the message I get from the server and post it into the text area. Through various println statements I have narrowed the problem to this. I have tried various solutions (for hours); coming here was a last resort.
The only other possible cause I can think of would be something going wrong with the multithreading, but can even begin to think of what.
public class IncomingReader implements Runnable
{
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
FXMLDocumentController.controller.chatBox
.appendText(message + "\n"); /////////PROBLEM HERE//////
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
FXML controller class (relevant line only):
#FXML protected TextArea chatBox;
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader());
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The line of FXML that defines the chatBox:
<TextArea id="ChatBox" fx:id="chatBox" focusTraversable="false" mouseTransparent="true" prefHeight="400.0" prefWidth="200.0" promptText="Chat:" wrapText="true" BorderPane.alignment="CENTER">
The resulting exception:
Exception in thread "Thread-4" java.lang.NullPointerException
at javachat.IncomingReader.run(IncomingReader.java:28)
at java.lang.Thread.run(Thread.java:745)
Note: this is really covered fully in Passing Parameters JavaFX FXML. However, the code you posted is so far from being structured properly to use the approaches there, that you probably need to see it specifically for your example. I would strongly recommend you read through that post and understand it, though.
Why you are seeing a NullPointerException:
FXMLDocumentController.controller refers to an instance of FXMLDocumentController which is not the same instance that was created for you by the FXMLLoader. Consequently, the chatBox field in FXMLDocumentController.controller was not initialized by the FXMLLoader.
What FXMLLoader does:
When you call one of the FXMLLoader.load(...) methods, it basically does the following:
Parses the FXML file (or stream)
If the root element of the FXML contains an fx:controller attribute, and no controller has been set by other means, it creates an instance of the class specified in that attribute
Creates the object hierarchy described by the FXML. If any of the objects defined in FXML have fx:id attributes, and a controller is associated with the FXMLLoader, it initializes #FXML annotated fields of the controller with the corresponding objects
Associates event handlers with the nodes, where defined
Returns the root object of the FXML object hierarchy
How to access the controller after loading the FXML
To access the controller, you must create an FXMLLoader instance instead of relying on the (evil) static FXMLLoader.load(URL) method. You can either pass the URL of the FXML resource into the FXMLLoader constructor, or call setLocation(...) on the FXMLLoader. Then, to load the FXML file, just call the no-argument load() method on the FXMLLoader. Once that is complete, you can access the controller by calling getController() on the FXMLLoader instance.
Other issues in your code
You cannot update the UI from a background thread: you must update it from the JavaFX Application Thread. As it stands (if you fix your NullPointerException issue), your code would throw an IllegalArgumentException in Java 8. In JavaFX 2.2 and earlier you would have to live with the possibility of bugs showing up at arbitrary times in the future. You can schedule code to be executed on the FX Application Thread by wrapping that code in a call to Platform.runLater().
Not quite so bad, but imo a bad design, is that you are exposing UI elements (the TextArea) outside of your FXML-controller pair. This becomes a real issue when your boss comes into your office and tells you he doesn't want the messages displayed in a TextArea any more, he wants them in a ListView. Since the TextArea is exposed, you have to trawl through all your code looking for any references to it. A better approach is to define a appendMessage(String) method in your controller, and access it. You might even want to factor all the data out into a separate model class, and pass references an instance of that class to both the controller and the reader class, but that is beyond the scope of this question.
I will refrain from complaining about your overuse of static stuff.. ;).
Here's the skeleton of one way to fix this code:
public class IncomingReader implements Runnable
{
private final FXMLDocumentController controller ;
public IncomingReader(FXMLDocumentController controller) {
this.controller = controller ;
}
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
final String msg = message ;
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.appendMessage(msg + "\n");
}
});
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
Controller:
public class FXMLDocumentController {
#FXML
private TextArea chatBox ;
public void appendMessage(String message) {
chatBox.appendText(message);
}
// other stuff as before...
}
Application:
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
FMXLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
FXMLDocumentController controller = (FXMLDocumentController) loader.getController();
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader(controller));
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Categories