I am trying to follow the Java best practices by not doing long tasks on the main thread (EDT). So I am planning to use a swingWorker with Modal Dialog. This way the modal dialog blocks the user for doing anything until that task is done and I can update status on the dialog while the process is taking place.
Now the problem is that with the modal dialog, it not only blocks the user but also nothing after setVisible gets called
So if I do
dialog.setVisible(true);
new SwingWorkerTask().execute(); //This does not get called
and if I do
new SwingWorkerTask().execute();
dialog.setVisible(true); // Well what the point of setting visible after the fact.
So How do I block user action and show a dialog while task is taking place?
Thank you
It is only a chicken/egg if you make it such. You can construct all Swing objects on EDT and then let your SwingWorker (or any other thread) govern all updates by instructing EDT to execute them via SwingUtilities.invokeLater(Runnable).
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class RudeProgressBar extends JFrame {
private JButton button;
public RudeProgressBar() {
setTitle("Rude Progress Bar");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
button = new JButton("Do teh work");
add(button, BorderLayout.SOUTH);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JDialog dialog = new JDialog(RudeProgressBar.this, true);
dialog.setTitle("Doing teh work");
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
final JProgressBar progressBar = new JProgressBar(0, 100);
dialog.setLayout(new BorderLayout());
dialog.add(progressBar);
dialog.setSize(100, 100);
dialog.setLocationRelativeTo(RudeProgressBar.this);
MyTask task = new MyTask(dialog);
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
task.execute();
}
});
setSize(200, 200);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new RudeProgressBar().setVisible(true);
}
});
}
private class MyTask extends SwingWorker<Void, Void> {
private final JDialog dialog;
public MyTask(JDialog dialog) {
this.dialog = dialog;
}
#Override
protected Void doInBackground() throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
dialog.setVisible(true);
}
});
int progress = 0;
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
setProgress(progress += 20);
}
return null;
}
#Override
protected void done() {
dialog.setVisible(false);
dialog.dispose();
}
}
}
If you are worried that the invokeLater implementation (inside SwingWorker.doInBackground) might get executed after SwingWorker.done, simply put the code in done into another invokeLater. By doing this, you queue your Runnable implementations for EDT to execute them in certain order. The queuing will happen even if this method is called from EDT itself.
Note that if you take a look at SwingWorker implementation, you'll see that it relies on javax.swing.Timer to execute done() and the Timer itself calls invokeLater, so calling it inside done again amounts to doing nothing. Nothing will be wrong if you do it, however.
You can try SwingUtilities.invokeLater and SwingUtilities.invokeAndWait instead of swingWorker.
Also, this topic may be useful.
Related
What is the correct way of disposing a frame which is created inside a Runnable object?
The code below returns a null pointer exception when the endDialog is called before the LoadingRunnable has completed its constructor.
How can the endDialog be executed after the constructor has finished?
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LoadingRunnable implements Runnable
{
private JFrame jFrame;
#Override
public void run()
{
jFrame = new JFrame("Window");
JPanel jPanel = new JPanel();
JLabel label = new JLabel("Loading...");
jPanel.add(label);
jFrame.setContentPane(jPanel);
jFrame.pack();
jFrame.setVisible(true);
}
public void endDialog()
{
jFrame.setVisible(false);
jFrame.dispose();
}
public static void main(String args[])
{
LoadingRunnable l = new LoadingRunnable();
SwingUtilities.invokeLater(l);
//work done here
l.endDialog();
}
};
You have a concurrency problem here because SwingUtilities.invokeLater() schedules your runnable class execution in the Event Dispatch Thread asynchronously while your main thread's flow still running, causing a NPE.
The correct way to dispose a frame is through events, just as Swing is designed to be used. For instance by clicking the "X" (close) button or by dispatching a WindowEvent:
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
You may want to take a look to this question too: Optional way to close a dialog window
In addition
If you just want to show something during your application start up, then you can use SplashScreen API instead of JFrame. See How to Create a Splash Screen for further details.
Based on your previous question and this new one, I'd suggest you read the whole Concurrency in Swing tutorial to understand about common concurrency problems in Swing and how to deal with them.
Ok found how:
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Loading
{
private JFrame jFrame;
public void startDialog()
{
jFrame = new JFrame("Window");
JPanel jPanel = new JPanel();
JLabel label = new JLabel("Loading...");
jPanel.add(label);
jFrame.setContentPane(jPanel);
jFrame.pack();
jFrame.setVisible(true);
}
public void endDialog()
{
jFrame.setVisible(false);
jFrame.dispose();
}
public static void main(String args[])
{
final Loading l = new Loading();
for (int i = 0; i < 200; i++)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
l.startDialog();
}
});
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
l.endDialog();
}
});
}
}
};
Is there a way how to use a dialog in Swing which prohibits any gui activity under it but at the same time DOES NOT stop execution on the thread where it was set to visible?
Yes it can be done .
dlg.setModal(false);
or
dlg.setModalityType(Dialog.ModalityType.MODELESS);
where dlg is instance of your JDialog .
The basic idea of a JDialog IS to block the underlying thread until the user reacts to it. If you need to run something on the UI thread which should not be interrupted, consider using an additional worker thread for it. This way, the UI will be blocked by the JDialog, but the underlying process won't.
Yes, there is a little trick to make it work. We simply deactivate modality and manually disable the JFrame we want to make unclickable.
private final static JDialog dialog; static {
JOptionPane pane = new JOptionPane();
pane.setOptions(new Object[]{}); // Removes all buttons
dialog = pane.createDialog(frame, ""); // Create dialog with pane
dialog.setModal(false); // IMPORTANT! Now the thread isn't blocked
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
}
Now you can use it like this:
dialog.setVisible(true);
frame.setEnabled(false);
// Logic
dialog.setVisible(false);
frame.setEnabled(true);
Technically, no. Like MadProgrammer wrote in a comment, you are never expected to access any Swing component off-EDT, JDialogs included, therefore the situation you hinted at in the question can never happen (there can never be any thread other than EDT that sets a dialog visible).
You could make it seem like it is, though. That's what SwingUtilities.invokeLater(Runnable) is for (doc).
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class BlockingDialogDemo extends JFrame {
private Timer timer;
private JDialog blocker;
public BlockingDialogDemo() {
setTitle("Blocking Dialog");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 200);
setLocationRelativeTo(null);
blocker = new JDialog(this, true);
blocker.setLayout(new FlowLayout());
blocker.setUndecorated(true);
blocker.getRootPane().setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.black));
blocker.add(new JLabel("I'm blocking EDT!"));
JProgressBar progress = new JProgressBar();
progress.setIndeterminate(true);
blocker.add(progress);
blocker.pack();
timer = new Timer(3000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
doSomeWork();
}
});
timer.setRepeats(false);
timer.start();
}
private void doSomeWork() {
// this executes on-EDT
Runnable runnable = new Runnable() {
public void run() {
// this executes off-EDT - never ever access Swing components here
showBlocker();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("Ummm.. I was sleeping here!");
} finally {
hideBlocker();
}
}
};
new Thread(runnable).start();
}
private void showBlocker() {
// this executes off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this executes on-EDT
blocker.setLocationRelativeTo(BlockingDialogDemo.this);
blocker.setVisible(true);
}
});
}
private void hideBlocker() {
// this executes off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this executes on-EDT
blocker.setVisible(false);
timer.restart();
}
});
}
public static void main(String[] args) {
// this is called off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this is called on-EDT
new BlockingDialogDemo().setVisible(true);
}
});
}
}
This works for me... sometimes:
public class NonBlockingModalDialogDemo extends JFrame{
JButton btnDoIt;
public NonBlockingModalDialogDemo() {
setTitle("NonBlockingModalDialog Demo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300,300);
setLayout(new FlowLayout());
btnDoIt = new JButton("Non-Blocking Notify");
btnDoIt.addActionListener( new ActionListener(){
#Override
public void actionPerformed(ActionEvent arg0) {
JDialog asyncDialog = createNonBlockingModalDialog("Please wait while we do some work", "Please wait");
doWork(50);
//Once your done, just dispose the dialog to allow access to GUI
asyncDialog.dispose();
}
});
this.add(btnDoIt);
}
private JDialog createNonBlockingModalDialog(String message, String title)
{
final JDialog dialog = new JDialog();
dialog.setLayout(new FlowLayout());
dialog.add(new JLabel(message));
dialog.setTitle(title);
dialog.setModal(true);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setAlwaysOnTop(true);
dialog.pack();
Runnable dialogDisplayThread = new Runnable() {
public void run() {
dialog.setVisible(true);
}};
new Thread(dialogDisplayThread).start();
//Need to wait until dialog is fully visible and then paint it
//or else it doesn't show up right
while(!dialog.isVisible()){/*Busy wait*/}
dialog.paint(dialog.getGraphics());
return dialog;
}
private void doWork(int amount) {
for(int i = 0; i < amount; i++)
{
System.out.println("Doing work step number " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
System.out.println("done");
}
public static void main(String[] args) {
new NonBlockingModalDialogDemo().setVisible(true);
}
}
I don't really like that it has a busy wait in it to check if the Dialog is visible yet, but so far I haven't found a way around it. At any rate, the busy wait should not take very long at all, so it really shouldn't matter.
Edit:
I did something very similar to this and for some reason, on some machines, sometimes, it just blocks forever without even showing the dialog.
I haven't figured out the root cause, but this leads me to conclude that all the people who say "never modify the GUI outside of the Event Dispatch Thread" may be on to something.
Perhaps rather than trying to continue the work you need to do on the EDT, maybe you should try something like this:
https://stackoverflow.com/a/4413563/2423283
Which uses SwingWorker to spawn a new thread and then allows you to update the GUI components when you are done.
I have a server call from the UI. It has response time is little high. So I was thinking to display a progress bar during data loading from the server. I have tried the following code using this approach to show the progress bar. Some where I am doing wrong I am not seeing the progress bar when I call the calculateResult() method on button click. I no need to display any percentage on the progress bar. It just needs to show that data is loading.
// The following code I have tried.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class MyProgressBarTest extends JFrame {
private static final long serialVersionUID = 1L;
private static JProgressBar progressBar;
public static void main(String[] args) {
MyProgressBarTest obj = new MyProgressBarTest();
obj.createGUI();
}
public void createGUI() {
JPanel panel = new JPanel();
JButton button = new JButton("Progress");
progressBar = new JProgressBar();
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
MyCustomProgressBarDialog progressBarObj = new MyCustomProgressBarDialog(progressBar);
progressBarObj.createProgressUI();
MyActionPerformer actionObj = new MyActionPerformer(progressBar);
actionObj.execute();
progressBarObj.setVisible(false);
}
});
// panel.add(progressBar);
panel.add(button);
add(panel);
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
setLocationRelativeTo(null);
// pack();
setSize(200, 300);
setVisible(true);
}
}
class MyActionPerformer extends SwingWorker<String, Object> {
JProgressBar fProgressBar;
public MyActionPerformer(JProgressBar progressBar) {
this.fProgressBar = progressBar;
this.fProgressBar.setVisible(true);
this.fProgressBar.setIndeterminate(true);
}
protected String doInBackground() throws Exception {
calculateResult();
return "Finished";
}
protected void done() {
fProgressBar.setVisible(false);
}
public void calculateResult() {
for (int i = 0; i < 500000; i++) {
System.out.println("Progress Bar: " + i);
}
}
}
class MyCustomProgressBarDialog extends JDialog {
private static JProgressBar progressBar;
public MyCustomProgressBarDialog(JProgressBar progressBar) {
this.progressBar = progressBar;
}
public void createProgressUI() {
add(progressBar);
setLocationRelativeTo(null);
setSize(50, 20);
setVisible(true);
}
}
The reason your progress bar disappears immediately is your ActionListener
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
MyCustomProgressBarDialog progressBarObj = new MyCustomProgressBarDialog(progressBar);
progressBarObj.createProgressUI();
MyActionPerformer actionObj = new MyActionPerformer(progressBar);
actionObj.execute();
progressBarObj.setVisible(false);
}
});
The actionObj.execute(); method is not blocking (good thing or it would be useless) meaning that immediately after you start the SwingWorker with that call you will execute the progressBarObj.setVisible(false); statement.
This causes the progress bar dialog to disappear.
I can think of 2 solutions for this
Pass the dialog to the SwingWorker as well and call setVisible( false ) on the dialog in the done method of the SwingWorker
A SwingWorker fires PropertyChangeEvents which allow you to determine how far it progressed. You can use such a listener to hide the dialog when the calculations are finished
You can't see your progress bar because you are not adding it to your panel
A simple:
panel.add(PROGRESS_BAR);
panel.add(button);
will do the trick.
I hope it helped. Cheers
JProgressBar is a Component. It must be added to a Container in order to be visible. You haven't added yours anywhere.
Good day,
I've read a few other stack overflow postings and other tutorials, but I can't get my GUI to update correctly after a button starts a long process. I've attached the full code of the problem that I am having. Notice if you run the code, the JList gets updated all at once at the end instead of every iteration of the for loop.
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class theframe extends JFrame implements ActionListener
{
private JList list;
private DefaultListModel listmodel;
private JButton start;
public theframe()
{
listmodel = new DefaultListModel();
list = new JList(listmodel);
start = new JButton("Start");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500,500);
setVisible(true);
list.setPreferredSize(new Dimension(200,200));
start.addActionListener(this);
JPanel p = new JPanel();
p.add(start);
p.add(list);
this.add(p);
}
public static void main(String[] args)
{
theframe frame = new theframe();
}
#Override
public void actionPerformed(ActionEvent arg0)
{
if(arg0.getSource() == start)
{
for(int i=0;i<10;i++)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
// the JList should update one by one
listmodel.addElement("Start pushed ");
}
});
try
{
//This thread sleep simulates a long job
Thread.sleep(300);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
Any help would be greatly appreciated.
Your problem here is the fact that you are calling the invokeLater method already from the EDT (Event Dispatching Thread).
The method actionPerformed is called from the EDT so what happens is that the sleep call just stops the EDT itself: you can imagine that this is not how it should work, no EDT running means no GUI updates.
Since it's a time consuming task you should implement it in a Thread/Runnable so that you can execute it in parallel and then call the invokeLater from this other thread.
Something like:
class LongProcess extends Thread {
public void run() {
for (int i = 0; i < 10; ++i) {
SwingUtilities.invokeLater(...);
Thread.sleep(300);
}
}
}
void actionPerformed(ActionEvent e) {
LongProcess process = new LongProcess();
process.start();
}
The actionPerformed method will be called on the Event Dispatch Thread. Calling Thread.sleep on the EDT stops it from updating your GUI. Since your GUI cannot update, your JList will not repaint itself when items are added to it until after your loop exits.
You should probably be using a SwingWorker. (SwingWorker tutorial.)
I have a GUI which is quite heavy to build/initialize on the platform on which it runs.. Therefore I want to update progress while it initializes..
I have a small undecorated JDialog containing a JLabel and a JProgressBar which I want to update at specific places during initialization, however, because the event dispatch thead (as per Swing rules) is used to build/initialize the GUI, the progress is of course not updated until the EDT is idle again (i.e. initialization is finished)..
The JProgressBar I have gotten to redraw using "paintImmediately", but I can't seem to make it work properly for the JLabel and the dialog itself.. Is there any simple recommended/proven method to accomplish this?
cheers...
EDIT: Adding an example of what it is I'm trying to do; greatly simplified, of course.
private JLabel progressLabel;
private JProgressBar progressBar;
public static int main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showProgressDialog();
progressLabel.setText("construct 1");
constructSomeHeavyGUI();
progressLabel.setText("construct 2");
progressBar.setValue(33);
constructSomeMoreHeavyGUI();
progressLabel.setText("construct 3");
progressBar.setValue(67);
constructEvenMoreHeavyGUI();
progressLabel.setText("done");
progressBar.setValue(100);
hideProgressDialog();
showHeavyGUI();
}
});
}
the repaints caused by the calls to progressBar.setValue()/progressLabel.setText() above will of course get queued as long as the EDT is busy and result in a repaint after we are all done instead of updating along the way..
I would suggest that by using SwingWorker , then you can update the JProgressBar correctly on EDT and without any freeze or isuees with Concurency in Swing,
there is another option by using Runnable#thread, but then you have to wrapp all output to the GUI into invokeLater();
for example:
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class TestProgressBar {
private static void createAndShowUI() {
JFrame frame = new JFrame("TestProgressBar");
frame.getContentPane().add(new TestPBGui().getMainPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowUI();
}
});
}
private TestProgressBar() {
}
}
class TestPBGui {
private JPanel mainPanel = new JPanel();
public TestPBGui() {
JButton yourAttempt = new JButton("WRONG attempt to show Progress Bar");
JButton myAttempt = new JButton("BETTER attempt to show Progress Bar");
yourAttempt.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
yourAttemptActionPerformed();
}
});
myAttempt.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
myAttemptActionPerformed();
}
});
mainPanel.add(yourAttempt);
mainPanel.add(myAttempt);
}
private void yourAttemptActionPerformed() {
Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
JDialog progressDialog = new JDialog(thisWin, "Uploading...");
JPanel contentPane = new JPanel();
contentPane.setPreferredSize(new Dimension(300, 100));
JProgressBar bar = new JProgressBar(0, 100);
bar.setIndeterminate(true);
contentPane.add(bar);
progressDialog.setContentPane(contentPane);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
Task task = new Task("Your attempt");
task.execute();
progressDialog.setVisible(true);
while (!task.isDone()) {
}
progressDialog.dispose();
}
private void myAttemptActionPerformed() {
Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
final JDialog progressDialog = new JDialog(thisWin, "Uploading...");
JPanel contentPane = new JPanel();
contentPane.setPreferredSize(new Dimension(300, 100));
final JProgressBar bar = new JProgressBar(0, 100);
bar.setIndeterminate(true);
contentPane.add(bar);
progressDialog.setContentPane(contentPane);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
final Task task = new Task("My attempt");
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equalsIgnoreCase("progress")) {
int progress = task.getProgress();
if (progress == 0) {
bar.setIndeterminate(true);
} else {
bar.setIndeterminate(false);
bar.setValue(progress);
progressDialog.dispose();
}
}
}
});
task.execute();
progressDialog.setVisible(true);
}
public JPanel getMainPanel() {
return mainPanel;
}
}
class Task extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 4000;
private String text;
public Task(String text) {
this.text = text;
}
#Override
public Void doInBackground() {
setProgress(0);
try {
Thread.sleep(SLEEP_TIME);// imitate a long-running task
} catch (InterruptedException e) {
}
setProgress(100);
return null;
}
#Override
public void done() {
System.out.println(text + " is done");
Toolkit.getDefaultToolkit().beep();
}
}
EDIT:
1) you showed another issues, why do you create lots of Top-Level Containers on Fly/Runtime, create only required numbers of Containers and re-use that by removeAll()
2) here is probably what you needed, all those JProgressBars in the JTable are pretty accesible and configurable
3) this is your paintImmediately(), that really reason why not painting any of Progress to the JLabel but using JProgressBar#setValue(int);
instead,
It's possible that constructSome*HeavyGUI() really takes long enough to matter, but it's more likely that filling in the data model(s) is the problem. Instead, construct and show the empty GUI elements and launch one or more SwingWorker instances to marshal each element's data. There are related examples here and here.
Addendum: If the problem is instantiating components, and not loading data models, you can chain the calls to invokeLater(), as suggested in a comment below. If you're instantiating that many components, consider the flyweight pattern. JTable is a familiar example.
Move the long running code in a separate thread and use SwingUtilities.invokeAndWait or invokeLater to update GUI.
Either use SwingUtilities.invokeLater(...) as suggested by #StanislavL, or use SwingWorker.
See also:
Worker Threads and SwingWorker