Auto-Scroll-Down in Swing GUI Progress-/Status-Window

In this post I’ll describe how to enable autoscroll for a JTextArea in a Java Swing GUI.

Some GUI applications raise the need to incrementally inform the user about the state of the program, which is particularly important when starting time consuming operations. Providing a user with up-to-date progress information might prevent him from thinking the program got stuck and eventually closing it. Autoscroll makes this information more accessible by automatically providing the visual information independent of the information- as well as GUI size.

For our example we assume a MVC like structure where the GUI opens a new window (progressWindow) when the user starts a time-consuming operation. The operation itself is started as a SwingWorker inside the Controller (avoid inreactive GUI) and reports progress to the progressWindow. There are two important steps to achieve auto-scroll behaviour in the progressWindow:

  • The Caret of the JTextArea inside the progressWindow is set to always update caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
  • The append method of the JTextArea is always called in the Event Dispatcher Thread which practically means inside an EventHandlingMethod of the Controller.

Beneath you can see the code for my proof of concept implementation.
Pictures of how the GUI actually looks can be found here: [1] [2]

Main.java

package example;

public class Main {

	public static void main(String[] args) {
		Model model = new Model();
		View view = new View();
		@SuppressWarnings("unused")
		Controller controller = new Controller(model, view);
		view.run();
	}
}

Model.java

package example.dont.use;

import java.util.Observable;

public class Model extends Observable {

	public void calculate() throws InterruptedException {
		for (int i = 0; i < 100; ++i) {
			Thread.sleep(30);
			setChanged();
			notifyObservers(new Integer(i).toString());
		}
	}
}

View.java

package example.dont.use;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.text.DefaultCaret;

public class View extends JFrame {
	private static final long serialVersionUID = -1008301631861757647L;

	protected JFrame mainFrame = new JFrame();
	protected JTextArea mainText = new JTextArea("Autoscroll example.");
	protected JButton loadBUtton = new JButton("Start");
	protected JDialog progressWindow = new JDialog();
	protected JTextArea progressText = new JTextArea();
	protected JScrollPane progressTextSrollPane = new JScrollPane(progressText,
			JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
			JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

	public View() {
		createGui();
	}

	public void createGui() {
		mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainText.setPreferredSize(new Dimension(300, 100));
		mainText.setFont(new Font("Arial", 0, 30));
		mainFrame.setLayout(new BorderLayout());
		mainFrame.add(mainText, BorderLayout.CENTER);
		mainFrame.add(loadBUtton, BorderLayout.SOUTH);

		DefaultCaret caret = (DefaultCaret) progressText.getCaret();
		caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
		progressTextSrollPane.setPreferredSize(new Dimension(100, 100));

		progressWindow.setLayout(new BorderLayout());
		progressWindow.add(progressTextSrollPane);
		progressWindow.pack();
	}

	public void run() {
		mainFrame.pack();
		mainFrame.setVisible(true);
	}

}

Controller.java

package example.dont.use;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;

import javax.swing.SwingWorker;

public class Controller implements Observer {

	protected Model model;
	protected View view;

	public Controller(Model model, View gui) {
		this.model = model;
		this.view = gui;

		initGui();
		model.addObserver(this);
	}

	private void initGui() {
		view.loadBUtton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
					@Override
					protected Void doInBackground() throws Exception {
						view.mainFrame.setEnabled(false);
						view.progressWindow.setVisible(true);
						model.calculate();
						view.progressText.setText("");
						view.progressWindow.setVisible(false);
						view.mainFrame.setEnabled(true);
						return null;
					}
				};
				worker.execute();
			}
		});
	}

	@Override
	public void update(Observable o, Object arg) {
		view.progressText.append((String) arg + ("\n"));
		// System.out.println(arg);
	}
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: