How to catch TableView creation in JavaFX 8 - java

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!
}

Related

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

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.

JavaFX Stage shows empty scene

I've been trying to open a new Window in order to display a progress bar, from a controller :
Stage fenetre = new Stage();
fenetre.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader;
Parent root;
Scene chargementBox;
loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/views/Chargement.fxml"));
loader.load();
root = loader.getRoot();
chargementBox = new Scene(root);
fenetre.setTitle("Chargement");
fenetre.resizableProperty().set(false);
fenetre.setScene(chargementBox);
fenetre.show();
It shows the window. But it's empty :
This is what it should show
This is what i got instead
I tried everything, I used other FXML files, the window sizes are correct but it's always empty. The same code works on other Controllers, but not here.
Help please. There is no exceptions and no errors. Thank's
Edit : I've found the reason why it doesn't show, it's because later in the code i have this function : Transport.send(message); that blocks the program from refreshing my scene and displaying the elements. Is there a way i can run that line in the background or in another thread (I never used threads before.) Thank's again for the help.
To run something on the background thread you need to either use a task (useable once) or a service (reusable).
This is how you can use a service:
Service<Void> service = new Service<Void>(){
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
//do your logic here.
return null;
}
};
}
};
service.setOnSucceeded(event -> {
//do some processing when complete
});
service.setOnFailed(event -> {
//do some processing when it failes
});
service.start();

Building Multithreading ProgressBar in JavaFx

I am attempting to build a progress bar that is being updated as my application is retrieving and populating data to the GUI. I figured that the progress bar will be reused a lot so I decided to create its own class. However, I don't believe I understand either the Worker/Task or Multi-Threading in general to create a re-usable situation. What would be the recommended approach to creating a progress bar that can listen to the application thread and update the bar accordingly. Here is my attempt:
// Simple Progress Bar View as Pop Up
public class ProgressIndicatorUtil{
#FXML
private ProgressBar progressBar;
#FXML
private Label statusLabel;
#FXML
private Button closeButton;
#FXML
private Label valueLabel;
private Worker worker;
private Stage stage;
public void setPopUpStage(Stage stage) {
this.stage = stage;
}
public void setWorker(Worker worker) {
this.worker = worker;
}
public void setLinkToMainPage(Object controller) {
((Task<String>) worker).setOnSucceeded(event -> stage.close());
((Task<String>) worker).setOnCancelled(event -> {
closeButton.setVisible(true);
stage.requestFocus();
statusLabel.setTextFill(Color.RED);}
);
valueLabel.textProperty().bind(Bindings.format("%5.1f%%", worker.progressProperty().multiply(100)));
progressBar.progressProperty().bind(worker.progressProperty());
statusLabel.textProperty().bind(worker.messageProperty());
}
#FXML
private void handleClose(ActionEvent e){
stage.close();
}
}
The Controller that calls the View Pop-Up and runs the Progress Bar Thread.
public class MyController{
//Controller calling the view and disabling the main GUI
private void loadProgressBar(Worker worker){
try{
FXMLLoader loader = new FXMLLoader(getClass()
.getClassLoader().getResource("main/resources/fxml/ProgressBar.fxml"));
AnchorPane pane = (AnchorPane)loader.load();
Stage popUpStage = new Stage();
popUpStage.initModality(Modality.WINDOW_MODAL);
Scene scene = new Scene(pane);
popUpStage.setScene(scene);
ProgressIndicatorUtil controller = loader.getController();
controller.setPopUpStage(popUpStage);
controller.setWorker(worker);
controller.setLinkToMainPage(this);
mainPane.setDisable(true);
popUpStage.showingProperty().addListener((obs, hidden, showing) -> {
if(hidden) mainPane.setDisable(false);});
popUpStage.show();
}catch(IOException e){
e.printStackTrace();
}
}
private void runProgressBar(Worker worker) {
new Thread((Runnable) worker).start();
}
//A user action that runs the progress bar and GUI
#FXML
private void aBigProcessingEvent(ActionEvent event) {
Worker worker = new Task<String>(){
#Override
protected String call() throws Exception {
updateProgress(0, 3);
updateMessage("Clearing Data");
processingEvent01();
updateProgress(1, 3);
updateMessage("Retriving Data And Adding To List");
processingEvent02();
updateProgress(2, 3);
updateMessage("Populating Data");
processingEvent03();
updateProgress(3, 3);
return "Finished!";
}
};
loadProgressBar(worker);
runProgressBar(worker);
}
}
The program works fine, visually, but it throws an Exception like this (Not On FX Application Thread) and running Platform.runLater() on my "processingEvent" methods will cause my progress bar to be 100% immediately, but it won't throw anymore Exceptions. Any suggestion to how to split the application modification methods and the worker methods apart while keeping the progression connected to the processingEvent methods? Much thanks.
There is nothing wrong with the (incomplete) code you have posted, so there errors are in other parts of your code. Since the code is incomplete, I have to make some educated guesses as to what is happening. (Note: it is actually much better if you can create complete examples when you post questions, so that you ensure the cause of the issue you are asking about is included.)
Since you are getting an IllegalStateException "Not on the FX Application Thread", you must be updating the UI from a background thread. Since the only code you've posted that runs in a background thread is in the Task you create in aBigProcessingEvent(), the UI updates must be happening in the one or more of the processingEventXX() methods you haven't shown.
If you wrap the calls to processingEventXX() in Platform.runLater(), then you won't see any progressive updates to the progress bar. Platform.runLater() schedules the runnable you provide to be executed on the FX Application Thread and exits immediately. There is no other code in the Task that takes time to run, so the entire task is completed in very little time, and by the time the FX Application Thread renders the next frame, the task is complete and the progress property is at 1.
So presumably your processingEventXX() methods take time to execute, and also update the UI. You must wrap the calls that update the UI in those methods in Platform.runLater(...). The code wrapped in Platform.runLater(...) must not include code that takes a long time to run. I.e. they should look like
private void processingEvent01() {
// some long-running process here...
Platform.runLater(() -> {
// update UI here....
});
// some other long-running process here (perhaps)
}

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