Search code examples
javaswing

How to set the modal dialog visible without blocking current Thread?


I am making an online game where a dialog with some information from server should appear. Transferring data takes some time, and it can look like a lag, if I get it first, and then create the dialog.

I would like to make the dialog visible and after that get all data required and add a new panel to the dialog. But...

modalDialog.setVisible(true);
JPanel jPanelWithServerData=new JPanel();
waitForSomeServerToGetSomeData();
modalDialog.add(jPanelWithServerData);

In this example the thread will be blocked until the dialog is hidden and modalDialog.add(jPanelWithServerData); won't be executed. How to do something after setVisible(true) call?


Solution

  • Don’t retrieve your data in the AWT event dispatch thread.

    AWT and Swing are single-threaded. Most GUI frameworks are. What this means is that all painting of windows, and all processing of user input like typing and mouse clicking, is done in a single special thread created by the system. AWT and Swing call it the Event Dispatch Thread.

    If you do something to hold that thread up, there will be no repainting and no response to user input during that time.

    If waitForSomeServerToGetSomeData() takes ten seconds to complete, your GUI will be frozen for ten seconds. Windows cannot be closed during that time. Buttons cannot be pressed during that time. And if the user happens to switch to a different window that obscures your dialog, then tries to look at your dialog again, your dialog will be a blank gray rectangle during those ten seconds.

    The solution is to perform tasks that may take a long time in a different thread:

    new Thread(() -> waitForSomeServerToGetSomeData()).start();
    

    But… you want to update the dialog as data is being read. Changes to Swing components must be done in the Event Dispatch Thread and in no other thread. To do that, there is a special method that executes code in the Event Dispatch Thread: EventQueue.invokeLater.

    So your code would do something like this:

    Runnable dataRetriever = () -> {
        waitForSomeServerToGetSomeData();
        EventQueue.invokeLater(() -> modalDialog.add(jPanelWithServerData));
    
        waitForSomeMoreData();
        EventQueue.invokeLater(() -> modalDialog.add(panelWithMoreData));
    };
    
    new Thread(dataRetriever).start();
    modalDialog.setVisible(true);
    

    There are other ways to do this. As Gilbert suggested, you may just want to use a JProgressBar. AS MadProgrammer suggested, you may benefit from using a SwingWorker, which lets you update Swing components by “publishing” the completion of each step of your long-running task.

    But if it’s a fairly simple case, a Thread and some invokeLater calls are all you need.