1

AppRunner class

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.fxml.FXMLLoader;

public class AppRunner {

    public static void main(String[] args) {
        startUi.launchPanel(args);
    }

    public static void waitFiveSecToClear() {
        new Thread(
                new Runnable() {
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException ex) {
                    Logger.getLogger(AppRunner.class.getName()).log(Level.SEVERE, null, ex);
                }
                FXMLLoader loader = new FXMLLoader(getClass().getResource("listView.fxml"));
                try {
                    loader.load();
                } catch (IOException ex) {
                    Logger.getLogger(AppRunner.class.getName()).log(Level.SEVERE, null, ex);
                }
                System.out.println("clearing listView");
                ((ListViewController) loader.getController()).clearAll();
            }
        }).start();
    }
}

ListViewController class

package apprunner;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ListView;

/**
 * FXML Controller class
 *
 */
public class ListViewController implements Initializable {
        
    public ListViewController() {
        
    }
    
    @FXML
    private ListView<String> listView;

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
        listView.getItems().add("clear all!");
        listView.getItems().add("clear all!");
        listView.getItems().add("clear all!");
        listView.getItems().add("clear all!");
        AppRunner.waitFiveSecToClear();
    }

    public void clearAll() {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                listView.getItems().clear();
            }
        });

    }
}

startUi class

package apprunner;

/**
 *
 */
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class startUi extends Application {

    public static void launchPanel(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("listView.fxml"));
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }
}

listView.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/17" fx:controller="apprunner.ListViewController">
   <children>
      <ListView fx:id="listView" layoutX="68.0" layoutY="39.0" prefHeight="200.0" prefWidth="200.0" />
   </children>
</AnchorPane>

Currently there is no error. However my code isn't doing what I want. I want the waitFiveSecToClear() method inside the AppRunner class to clear the listView in the javafx ui. However this isn't happening. I used ((ListViewController) loader.getController()).clearAll(); to attempt to clear the listView which doesen't seem to do anything. If someone could help me figure out my problem that would be great thanks!

Context for my problem

I have a highschool capstone project. I chose to make a music internet downloader. When my program is downloading music, it would freeze the whole javafx application without the thread, so I used a thread and Thread.sleep(5000) in my waitFiveSecToClear() method to simulate that. My program has a listView which shows a queue of all the music urls currently being downloaded. I want it so that when one url is finished downloading, the listView is updated which must be done on the Javafx thread which requires Platform.runlater. So, I used Platform.runLater in the clearAll() method to simluate that. I don't want to do that in the controller because based on what I've learned that would break model, view, controller architecture. I have an AppRunner class because my teachers program example had that so I just copied it.

  • You need JavaFX to create the controller using `FXMLLoader`. Then, you can get the controller with `yourFXMLLoader.getController()`. – dan1st Feb 06 '22 at 00:10
  • @dan1st I added that to my code and there is no more error. However, it does not seem to be chaning my ui when I call the clearListView() method. Is there a solution to this? – harharprogrammer Feb 06 '22 at 00:35
  • 1
    You shouldn't create multiple controllers but use the same one whenever you need it. – dan1st Feb 06 '22 at 00:38
  • 1
    Create and post a [mre]. Include everything necessary for others to recreate the issue. – James_D Feb 06 '22 at 00:51
  • @dan1st I ended up doing this ```((DownloadPageViewController)loader.getController()).clearListView()``` which still isn't changing my ui. Do you know why? I did ```loader.load()``` prior to running the code above btw. – harharprogrammer Feb 06 '22 at 01:20
  • 1
    Are you loading the same FXML file multiple times in your code? – dan1st Feb 06 '22 at 01:21
  • 2
    Instead of trying stuff and failing and posting incomplete info in comments that people speculate on and ask you about, edit the question and provide the requested [mcve], complete code that *only* replicates the issue and somebody can copy and paste to run without change and see the issue in their environment.. – jewelsea Feb 06 '22 at 02:28
  • 1
    There is really no good reason that I can imagine for an @FXML annotated method to invoke runLater, nor to do the work you are doing in the main method rather than just invoking the app launch method and doing that work in the start method of the application, study the Application javadoc to understand the lifecycle, and the Platform javadoc, as well as resources on Java concurrency and threading. – jewelsea Feb 06 '22 at 02:30
  • Also why you would need to clear the list view and put an error in it as soon as it is loaded is a mystery. ListView has a [placeholder](https://openjfx.io/javadoc/17/javafx.controls/javafx/scene/control/ListView.html#placeholderProperty) for such things. – jewelsea Feb 06 '22 at 06:18
  • 1
    @jewelsea Sorry about that, I updated my problem above to try to be more concise and clear. It is in an mre form now. – harharprogrammer Feb 06 '22 at 07:52
  • @James_D Made one thanks! – harharprogrammer Feb 06 '22 at 07:52
  • @dan1st I don't think I am loading the FXML file in my code multiple times. However, I updated my post to include a minimal reproducible example to help demonstrate my problem. – harharprogrammer Feb 06 '22 at 07:54
  • 2
    Based on the update, I reopened the question. There are quite a few problems here, not one. – jewelsea Feb 06 '22 at 08:23
  • I don’t have time for an answer right now and won’t address all issues in comments. “I don't think I am loading the FXML file in my code multiple times” -> you are loading it multiple times, don’t do that. Don’t create threads unnecessarily. Use a PauseTransition to pause. The Application class is the point of entry for the app, get rid of the AppRunner. runLater is not required. Try to avoid static methods. Follow naming conventions for class names. Make waitFiveSecToClear an instance method in the controller. Explain the context and purpose of what you are really trying to accomplish. – jewelsea Feb 06 '22 at 08:35
  • 2
    @jewelsea Sorry, I have updated the context of my problem to make it more clear. – harharprogrammer Feb 06 '22 at 08:59
  • 2
    Thanks for the context update, it is finally a meaningful question :-) – jewelsea Feb 06 '22 at 09:13
  • 3
    You had an [xy problem](https://xyproblem.info/). You want to use a [Task](https://openjfx.io/javadoc/17/javafx.graphics/javafx/concurrent/Task.html). Study the javadoc then try to implement it. Test each portion individually. Don’t get too hung up trying to stick with patterns like mvc, at least for small programs anyway. Invoking the task from the controller is better than trying to call a static method in the launcher. – jewelsea Feb 06 '22 at 09:22
  • 1
    If you really want to understand how to apply mvc in JavaFX, study the [eden coding tutorial](https://edencoding.com/mvc-in-javafx/). – jewelsea Feb 06 '22 at 09:32
  • I agree that simply invoking a `Task` to download music from each URL from the controller is simplest here, and while it violates MVC it’s probably not going to get you in too bad a mess. However, in case your instructor insists on an MVC design (or if you just want to understand it well, which I’d encourage) your **model** should have an `ObservableList` for the “currently downloading music”. The controller has a reference to the model and sets the items on the `ListView` to that list. Then in the model, launch a `Task` to download the song, and update the list in `onSucceeded`. – James_D Feb 06 '22 at 15:16
  • 1
    @James_D Thank you! I have successfully implemented the code shown above and my program is working just as I want it to. – harharprogrammer Feb 06 '22 at 18:51
  • @jewelsea Thank you for your help! I'll make sure to remember the xy problem for my next question. – harharprogrammer Feb 06 '22 at 18:52
  • 2
    @harharprogrammer You can self-answer the question if you like. – James_D Feb 06 '22 at 18:52

1 Answers1

0

I solved my problem by using a Task and ObservableList as suggested by @James_D and @jewelsea. I have a Task in my model which would download music and remove the url of the music from the ObservableList once it finished downloading. I then have a Listener in my controller which would update the ListView when the ObservableList changed.