Java/Threads/Swing Thread
Содержание
- 1 An abstract class to perform lengthy GUI-interacting tasks in a dedicated thread.
- 2 Animation: Swing and thread
- 3 Counter: Swing and thread
- 4 Eliminating race Conditions using Swing Components
- 5 GUI clock
- 6 InvokeExample: Swing and thread
- 7 Is Event Dispatcher Thread
- 8 Race Conditions using Swing Components
- 9 Swing and Thread: cancel a lengthy operation
- 10 Swing and Thread for length operation
- 11 Swing and Threading
- 12 Swing and thread: invoke and wait
- 13 Swing and Thread: repaint
- 14 Swing and threads: invoke later
- 15 Swing and threads: scroll text
- 16 Swing and threads: slide
- 17 Swing Type Tester 10
- 18 SwingUtilities.invokeLater and swing thread
- 19 Thread accuracy: Swing and threads
- 20 Thread and Swing 1
- 21 Thread and Swing 2
- 22 Thread and Swing 3
- 23 User interface responsiveness
- 24 Using the Runnable interface
- 25 Write your Beans this way so they can run in a multithreaded environment
An abstract class to perform lengthy GUI-interacting tasks in a dedicated thread.
/*
* $Id: SwingWorker.java,v 1.4 2006/10/19 21:03:49 evanx Exp $
*
* Copyright � 2005 Sun Microsystems, Inc. All rights
* reserved. Use is subject to license terms.
*/
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.SwingUtilities;
/**
* An abstract class to perform lengthy GUI-interacting tasks in a
* dedicated thread.
*
* <p>
* When writing a multi-threaded application using Swing, there are
* two constraints to keep in mind:
* (refer to
* for more details):
* <ul>
* <li> Time-consuming tasks should not be run on the <i>Event
* Dispatch Thread</i>. Otherwise the application becomes unresponsive.
* </li>
* <li> Swing components should be accessed on the <i>Event
* Dispatch Thread</i> only.
* </li>
* </ul>
*
* <p>
*
* <p>
* These constraints mean that a GUI application with time intensive
* computing needs at least two threads: 1) a thread to perform the lengthy
* task and 2) the <i>Event Dispatch Thread</i> (EDT) for all GUI-related
* activities. This involves inter-thread communication which can be
* tricky to implement.
*
* <p>
* {@code SwingWorker} is designed for situations where you need to have a long
* running task run in a background thread and provide updates to the UI
* either when done, or while processing.
* Subclasses of {@code SwingWorker} must implement
* the {@see #doInBackground} method to perform the background computation.
*
*
* <p>
* <b>Workflow</b>
* <p>
* There are three threads involved in the life cycle of a
* {@code SwingWorker} :
* <ul>
* <li>
* <p>
* <i>Current</i> thread: The {@link #execute} method is
* called on this thread. It schedules {@code SwingWorker} for the execution on a
* <i>worker</i>
* thread and returns immediately. One can wait for the {@code SwingWorker} to
* complete using the {@link #get get} methods.
* <li>
* <p>
* <i>Worker</i> thread: The {@link #doInBackground}
* method is called on this thread.
* This is where all background activities should happen. To notify
* {@code PropertyChangeListeners} about bound properties changes use the
* {@link #firePropertyChange firePropertyChange} and
* {@link #getPropertyChangeSupport} methods. By default there are two bound
* properties available: {@code state} and {@code progress}.
* <li>
* <p>
* <i>Event Dispatch Thread</i>: All Swing related activities occur
* on this thread. {@code SwingWorker} invokes the
* {@link #process process} and {@link #done} methods and notifies
* any {@code PropertyChangeListeners} on this thread.
* </ul>
*
* <p>
* Often, the <i>Current</i> thread is the <i>Event Dispatch
* Thread</i>.
*
*
* <p>
* Before the {@code doInBackground} method is invoked on a <i>worker</i> thread,
* {@code SwingWorker} notifies any {@code PropertyChangeListeners} about the
* {@code state} property change to {@code StateValue.STARTED}. After the
* {@code doInBackground} method is finished the {@code done} method is
* executed. Then {@code SwingWorker} notifies any {@code PropertyChangeListeners}
* about the {@code state} property change to {@code StateValue.DONE}.
*
* <p>
* {@code SwingWorker} is only designed to be executed once. Executing a
* {@code SwingWorker} more than once will not result in invoking the
* {@code doInBackground} method twice.
*
* <p>
* <b>Sample Usage</b>
* <p>
* The following example illustrates the simplest use case. Some
* processing is done in the background and when done you update a Swing
* component.
*
* <p>
* Say we want to find the "Meaning of Life" and display the result in
* a {@code JLabel}.
*
* <pre>
* final JLabel label;
* class MeaningOfLifeFinder extends SwingWorker<String, Object> {
* {@code @Override}
* public String doInBackground() {
* return findTheMeaningOfLife();
* }
*
* {@code @Override}
* protected void done() {
* try {
* label.setText(get());
* } catch (Exception ignore) {
* }
* }
* }
*
* (new MeaningOfLifeFinder()).execute();
* </pre>
*
* <p>
* The next example is useful in situations where you wish to process data
* as it is ready on the <i>Event Dispatch Thread</i>.
*
* <p>
* Now we want to find the first N prime numbers and display the results in a
* {@code JTextArea}. While this is computing, we want to update our
* progress in a {@code JProgressBar}. Finally, we also want to print
* the prime numbers to {@code System.out}.
* <pre>
* class PrimeNumbersTask extends
* SwingWorker<List<Integer>, Integer> {
* PrimeNumbersTask(JTextArea textArea, int numbersToFind) {
* //initialize
* }
*
* {@code @Override}
* public List<Integer> doInBackground() {
* while (! enough && ! isCancelled()) {
* number = nextPrimeNumber();
* publish(number);
* setProgress(100 * numbers.size() / numbersToFind);
* }
* }
* return numbers;
* }
*
* {@code @Override}
* protected void process(Integer... chunks) {
* for (int number : chunks) {
* textArea.append(number + "\n");
* }
* }
* }
*
* JTextArea textArea = new JTextArea();
* final JProgressBar progressBar = new JProgressBar(0, 100);
* PrimeNumbersTask task = new PrimeNumbersTask(textArea, N);
* task.addPropertyChangeListener(
* new PropertyChangeListener() {
* public void propertyChange(PropertyChangeEvent evt) {
* if ("progress".equals(evt.getPropertyName())) {
* progressBar.setValue((Integer)evt.getNewValue());
* }
* }
* });
*
* task.execute();
* System.out.println(task.get()); //prints all prime numbers we have got
* </pre>
*
* <p>
* Because {@code SwingWorker} implements {@code Runnable}, a
* {@code SwingWorker} can be submitted to an
* {@link java.util.concurrent.Executor} for execution.
*
* @author Igor Kushnirskiy
* @version $Revision: 1.4 $ $Date: 2006/10/19 21:03:49 $
*
* @param <T> the result type returned by this {@code SwingWorker"s}
* {@code doInBackground} and {@code get} methods
* @param <V> the type used for carrying out intermediate results by this
* {@code SwingWorker"s} {@code publish} and {@code process} methods
*
*/
public abstract class SwingWorker<T, V> implements Future<T>, Runnable {
/**
* number of worker threads.
*/
private static final int MAX_WORKER_THREADS = 10;
/**
* current progress.
*/
private volatile int progress;
/**
* current state.
*/
private volatile StateValue state;
/**
* everything is run inside this FutureTask. Also it is used as
* a delegatee for the Future API.
*/
private final FutureTask<T> future;
/**
* all propertyChangeSupport goes through this.
*/
private final SwingPropertyChangeSupport propertyChangeSupport;
/**
* handler for {@code process} mehtod.
*/
private AccumulativeRunnable<V> doProcess;
/**
* handler for progress property change notifications.
*/
private AccumulativeRunnable<Integer> doNotifyProgressChange;
private static ExecutorService executorService = null;
/**
* Values for the {@code state} bound property.
*/
public enum StateValue {
/**
* Initial {@code SwingWorker} state.
*/
PENDING,
/**
* {@code SwingWorker} is {@code STARTED}
* before invoking {@code doInBackground}.
*/
STARTED,
/**
* {@code SwingWorker} is {@code DONE}
* after {@code doInBackground} method
* is finished.
*/
DONE
};
/**
* Constructs this {@code SwingWorker}.
*/
public SwingWorker() {
Callable<T> callable =
new Callable<T>() {
public T call() throws Exception {
setState(StateValue.STARTED);
return doInBackground();
}
};
future = new FutureTask<T>(callable) {
@Override
protected void done() {
doneEDT();
setState(StateValue.DONE);
}
};
state = StateValue.PENDING;
propertyChangeSupport = new SwingPropertyChangeSupport(this, true);
doProcess = null;
doNotifyProgressChange = null;
}
/**
* Computes a result, or throws an exception if unable to do so.
*
* <p>
* Note that this method is executed only once.
*
* <p>
* Note: this method is executed in a background thread.
*
*
* @return the computed result
* @throws Exception if unable to compute a result
*
*/
protected abstract T doInBackground() throws Exception ;
/**
* Sets this {@code Future} to the result of computation unless
* it has been cancelled.
*/
public final void run() {
future.run();
}
/**
* Sends data chunks to the {@link #process} method. This method is to be
* used from inside the {@code doInBackground} method to deliver
* intermediate results
* for processing on the <i>Event Dispatch Thread</i> inside the
* {@code process} method.
*
* <p>
* Because the {@code process} method is invoked asynchronously on
* the <i>Event Dispatch Thread</i>
* multiple invocations to the {@code publish} method
* might occur before the {@code process} method is executed. For
* performance purposes all these invocations are coalesced into one
* invocation with concatenated arguments.
*
* <p>
* For example:
*
* <pre>
* publish("1");
* publish("2", "3");
* publish("4", "5", "6");
* </pre>
*
* might result in:
*
* <pre>
* process("1", "2", "3", "4", "5", "6")
* </pre>
*
* <p>
* <b>Sample Usage</b>. This code snippet loads some tabular data and
* updates {@code DefaultTableModel} with it. Note that it safe to mutate
* the tableModel from inside the {@code process} method because it is
* invoked on the <i>Event Dispatch Thread</i>.
*
* <pre>
* class TableSwingWorker extends
* SwingWorker<DefaultTableModel, Object[]> {
* private final DefaultTableModel tableModel;
*
* public TableSwingWorker(DefaultTableModel tableModel) {
* this.tableModel = tableModel;
* }
*
* {@code @Override}
* protected DefaultTableModel doInBackground() throws Exception {
* for (Object[] row = loadData();
* ! isCancelled() && row != null;
* row = loadData()) {
* publish((Object[]) row);
* }
* return tableModel;
* }
*
* {@code @Override}
* protected void process(Object[]... chunks) {
* for (Object[] row : chunks) {
* tableModel.addRow(row);
* }
* }
* }
* </pre>
*
* @param chunks intermediate results to process
*
* @see #process
*
*/
protected final void publish(V... chunks) {
synchronized (this) {
if (doProcess == null) {
doProcess = new AccumulativeRunnable<V>() {
@Override
public void run(V... args) {
process(args);
}
};
}
}
doProcess.add(chunks);
}
/**
* Receives data chunks from the {@code publish} method asynchronously on the
* <i>Event Dispatch Thread</i>.
*
* <p>
* Please refer to the {@link #publish} method for more details.
*
* @param chunks intermediate results to process
*
* @see #publish
*
*/
protected void process(V... chunks) {
}
/**
* Executed on the <i>Event Dispatch Thread</i> after the {@code doInBackground}
* method is finished. The default
* implementation does nothing. Subclasses may override this method to
* perform completion actions on the <i>Event Dispatch Thread</i>. Note
* that you can query status inside the implementation of this method to
* determine the result of this task or whether this task has been cancelled.
*
* @see #doInBackground
* @see #isCancelled()
* @see #get
*/
protected void done() {
}
/**
* Sets the {@code progress} bound property.
* The value should be from 0 to 100.
*
* <p>
* Because {@code PropertyChangeListener}s are notified asynchronously on
* the <i>Event Dispatch Thread</i> multiple invocations to the
* {@code setProgress} method might occur before any
* {@code PropertyChangeListeners} are invoked. For performance purposes
* all these invocations are coalesced into one invocation with the last
* invocation argument only.
*
* <p>
* For example, the following invokations:
*
* <pre>
* setProgress(1);
* setProgress(2);
* setProgress(3);
* </pre>
*
* might result in a single {@code PropertyChangeListener} notification with
* the value {@code 3}.
*
* @param progress the progress value to set
* @throws IllegalArgumentException is value not from 0 to 100
*/
protected final void setProgress(int progress) {
if (progress < 0 || progress > 100) {
throw new IllegalArgumentException("the value should be from 0 to 100");
}
int oldProgress = this.progress;
this.progress = progress;
synchronized (this) {
if (doNotifyProgressChange == null) {
doNotifyProgressChange =
new AccumulativeRunnable<Integer>() {
@Override
public void run(Integer... args) {
firePropertyChange("progress",
args[0],
args[args.length - 1]);
}
};
}
}
doNotifyProgressChange.add(oldProgress, progress);
}
/**
* Returns the {@code progress} bound property.
*
* @return the progress bound property.
*/
public final int getProgress() {
return progress;
}
/**
* Schedules this {@code SwingWorker} for execution on a <i>worker</i>
* thread. There are a number of <i>worker</i> threads available. In the
* event all <i>worker</i> threads are busy handling other
* {@code SwingWorkers} this {@code SwingWorker} is placed in a waiting
* queue.
*
* <p>
* Note:
* {@code SwingWorker} is only designed to be executed once. Executing a
* {@code SwingWorker} more than once will not result in invoking the
* {@code doInBackground} method twice.
*/
public final void execute() {
getWorkersExecutorService().execute(this);
}
// Future methods START
/**
* {@inheritDoc}
*/
public final boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
/**
* {@inheritDoc}
*/
public final boolean isCancelled() {
return future.isCancelled();
}
/**
* {@inheritDoc}
*/
public final boolean isDone() {
return future.isDone();
}
/**
* {@inheritDoc}
* <p>
* Note: calling {@code get} on the <i>Event Dispatch Thread</i> blocks
* <i>all</i> events, including repaints, from being processed until this
* {@code SwingWorker} is complete.
*
* <p>
* When you want the {@code SwingWorker} to block on the <i>Event
* Dispatch Thread</i> we recommend that you use a <i>modal dialog</i>.
*
* <p>
* For example:
*
* <pre>
* class SwingWorkerCompletionWaiter extends PropertyChangeListener {
* private JDialog dialog;
*
* public SwingWorkerCompletionWaiter(JDialog dialog) {
* this.dialog = dialog;
* }
*
* public void propertyChange(PropertyChangeEvent event) {
* if ("state".equals(event.getPropertyName())
* && SwingWorker.StateValue.DONE == event.getNewValue()) {
* dialog.setVisible(false);
* dialog.dispose();
* }
* }
* }
* JDialog dialog = new JDialog(owner, true);
* swingWorker.addPropertyChangeListener(
* new SwingWorkerCompletionWaiter(dialog));
* swingWorker.execute();
* //the dialog will be visible until the SwingWorker is done
* dialog.setVisible(true);
* </pre>
*/
public final T get() throws InterruptedException, ExecutionException {
return future.get();
}
/**
* {@inheritDoc}
* <p>
* Please refer to {@link #get} for more details.
*/
public final T get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
// Future methods END
// PropertyChangeSupports methods START
/**
* Adds a {@code PropertyChangeListener} to the listener list. The listener
* is registered for all properties. The same listener object may be added
* more than once, and will be called as many times as it is added. If
* {@code listener} is {@code null}, no exception is thrown and no action is taken.
*
* <p>
* Note: This is merely a convenience wrapper. All work is delegated to
* {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}.
*
* @param listener the {@code PropertyChangeListener} to be added
*/
public final void addPropertyChangeListener(PropertyChangeListener listener) {
getPropertyChangeSupport().addPropertyChangeListener(listener);
}
/**
* Removes a {@code PropertyChangeListener} from the listener list. This
* removes a {@code PropertyChangeListener} that was registered for all
* properties. If {@code listener} was added more than once to the same
* event source, it will be notified one less time after being removed. If
* {@code listener} is {@code null}, or was never added, no exception is
* thrown and no action is taken.
*
* <p>
* Note: This is merely a convenience wrapper. All work is delegated to
* {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}.
*
* @param listener the {@code PropertyChangeListener} to be removed
*/
public final void removePropertyChangeListener(PropertyChangeListener listener) {
getPropertyChangeSupport().removePropertyChangeListener(listener);
}
/**
* Reports a bound property update to any registered listeners. No event is
* fired if {@code old} and {@code new} are equal and non-null.
*
* <p>
* This {@code SwingWorker} will be the source for
* any generated events.
*
* <p>
* When called off the <i>Event Dispatch Thread</i>
* {@code PropertyChangeListeners} are notified asynchronously on
* the <i>Event Dispatch Thread</i>.
* <p>
* Note: This is merely a convenience wrapper. All work is delegated to
* {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}.
*
*
* @param propertyName the programmatic name of the property that was
* changed
* @param oldValue the old value of the property
* @param newValue the new value of the property
*/
public final void firePropertyChange(String propertyName, Object oldValue,
Object newValue) {
getPropertyChangeSupport().firePropertyChange(propertyName,
oldValue, newValue);
}
/**
* Returns the {@code PropertyChangeSupport} for this {@code SwingWorker}.
* This method is used when flexible access to bound properties support is
* needed.
* <p>
* This {@code SwingWorker} will be the source for
* any generated events.
*
* <p>
* Note: The returned {@code PropertyChangeSupport} notifies any
* {@code PropertyChangeListener}s asynchronously on the <i>Event Dispatch
* Thread</i> in the event that {@code firePropertyChange} or
* {@code fireIndexedPropertyChange} are called off the <i>Event Dispatch
* Thread</i>.
*
* @return {@code PropertyChangeSupport} for this {@code SwingWorker}
*/
public final PropertyChangeSupport getPropertyChangeSupport() {
return propertyChangeSupport;
}
// PropertyChangeSupports methods END
/**
* Returns the {@code SwingWorker} state bound property.
*
* @return the current state
*/
public final StateValue getState() {
/*
* DONE is a special case
* to keep getState and isDone is sync
*/
if (isDone()) {
return StateValue.DONE;
} else {
return state;
}
}
/**
* Sets this {@code SwingWorker} state bound property.
* @param the state state to set
*/
private void setState(StateValue state) {
StateValue old = this.state;
this.state = state;
firePropertyChange("state", old, state);
}
/**
* Invokes {@code done} on the EDT.
*/
private void doneEDT() {
Runnable doDone =
new Runnable() {
public void run() {
done();
}
};
if (SwingUtilities.isEventDispatchThread()) {
doDone.run();
} else {
SwingUtilities.invokeLater(doDone);
}
}
/**
* returns workersExecutorService.
*
* returns the service stored in the appContext or creates it if
* necessary. If the last one it triggers autoShutdown thread to
* get started.
*
* @return ExecutorService for the {@code SwingWorkers}
* @see #startAutoShutdownThread
*/
private static synchronized ExecutorService getWorkersExecutorService() {
if (executorService == null) {
//this creates non-daemon threads.
ThreadFactory threadFactory =
new ThreadFactory() {
final ThreadFactory defaultFactory =
Executors.defaultThreadFactory();
public Thread newThread(final Runnable r) {
Thread thread =
defaultFactory.newThread(r);
thread.setName("SwingWorker-"
+ thread.getName());
return thread;
}
};
/*
* We want a to have no more than MAX_WORKER_THREADS
* running threads.
*
* We want a worker thread to wait no longer than 1 second
* for new tasks before terminating.
*/
executorService = new ThreadPoolExecutor(0, MAX_WORKER_THREADS,
1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory) {
private final ReentrantLock pauseLock = new ReentrantLock();
private final Condition unpaused = pauseLock.newCondition();
private boolean isPaused = false;
private final ReentrantLock executeLock = new ReentrantLock();
@Override
public void execute(Runnable command) {
/*
* ThreadPoolExecutor first tries to run task
* in a corePool. If all threads are busy it
* tries to add task to the waiting queue. If it
* fails it run task in maximumPool.
*
* We want corePool to be 0 and
* maximumPool to be MAX_WORKER_THREADS
* We need to change the order of the execution.
* First try corePool then try maximumPool
* pool and only then store to the waiting
* queue. We can not do that because we would
* need access to the private methods.
*
* Instead we enlarge corePool to
* MAX_WORKER_THREADS before the execution and
* shrink it back to 0 after.
* It does pretty much what we need.
*
* While we changing the corePoolSize we need
* to stop running worker threads from accepting new
* tasks.
*/
//we need atomicity for the execute method.
executeLock.lock();
try {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
setCorePoolSize(MAX_WORKER_THREADS);
super.execute(command);
setCorePoolSize(0);
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
} finally {
executeLock.unlock();
}
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
pauseLock.lock();
try {
while(isPaused) {
unpaused.await();
}
} catch(InterruptedException ignore) {
} finally {
pauseLock.unlock();
}
}
};
}
return executorService;
}
}
/*
* $Id: SwingPropertyChangeSupport.java,v 1.4 2006/10/19 21:03:49 evanx Exp $
*
* Copyright � 2005 Sun Microsystems, Inc. All rights
* reserved. Use is subject to license terms.
*/
/**
* This subclass of {@code java.beans.PropertyChangeSupport} is almost
* identical in functionality. The only difference is if constructed with
* {@code SwingPropertyChangeSupport(sourceBean, true)} it ensures
* listeners are only ever notified on the <i>Event Dispatch Thread</i>.
*
* @author Igor Kushnirskiy
* @version $Revision: 1.4 $ $Date: 2006/10/19 21:03:49 $
*/
final class SwingPropertyChangeSupport extends PropertyChangeSupport {
/**
* Constructs a SwingPropertyChangeSupport object.
*
* @param sourceBean The bean to be given as the source for any
* events.
* @throws NullPointerException if {@code sourceBean} is
* {@code null}
*/
public SwingPropertyChangeSupport(Object sourceBean) {
this(sourceBean, false);
}
/**
* Constructs a SwingPropertyChangeSupport object.
*
* @param sourceBean the bean to be given as the source for any events
* @param notifyOnEDT whether to notify listeners on the <i>Event
* Dispatch Thread</i> only
*
* @throws NullPointerException if {@code sourceBean} is
* {@code null}
* @since 1.6
*/
public SwingPropertyChangeSupport(Object sourceBean, boolean notifyOnEDT) {
super(sourceBean);
this.notifyOnEDT = notifyOnEDT;
}
/**
* {@inheritDoc}
*
* <p>
* If {@see #isNotifyOnEDT} is {@code true} and called off the
* <i>Event Dispatch Thread</i> this implementation uses
* {@code SwingUtilities.invokeLater} to send out the notification
* on the <i>Event Dispatch Thread</i>. This ensures listeners
* are only ever notified on the <i>Event Dispatch Thread</i>.
*
* @throws NullPointerException if {@code evt} is
* {@code null}
* @since 1.6
*/
public void firePropertyChange(final PropertyChangeEvent evt) {
if (evt == null) {
throw new NullPointerException();
}
if (! isNotifyOnEDT()
|| SwingUtilities.isEventDispatchThread()) {
super.firePropertyChange(evt);
} else {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
firePropertyChange(evt);
}
});
}
}
/**
* Returns {@code notifyOnEDT} property.
*
* @return {@code notifyOnEDT} property
* @see #SwingPropertyChangeSupport(Object sourceBean, boolean notifyOnEDT)
* @since 1.6
*/
public final boolean isNotifyOnEDT() {
return notifyOnEDT;
}
// Serialization version ID
static final long serialVersionUID = 7162625831330845068L;
/**
* whether to notify listeners on EDT
*
* @serial
* @since 1.6
*/
private final boolean notifyOnEDT;
}
/*
* $Id: AccumulativeRunnable.java,v 1.4 2006/10/19 21:03:49 evanx Exp $
*
* Copyright � 2005 Sun Microsystems, Inc. All rights
* reserved. Use is subject to license terms.
*/
/**
* An abstract class to be used in the cases where we need {@code Runnable}
* to perform some actions on an appendable set of data.
* The set of data might be appended after the {@code Runnable} is
* sent for the execution. Usually such {@code Runnables} are sent to
* the EDT.
*
* <p>
* Usage example:
*
* <p>
* Say we want to implement JLabel.setText(String text) which sends
* {@code text} string to the JLabel.setTextImpl(String text) on the EDT.
* In the event JLabel.setText is called rapidly many times off the EDT
* we will get many updates on the EDT but only the last one is important.
* (Every next updates overrides the previous one.)
* We might want to implement this {@code setText} in a way that only
* the last update is delivered.
* <p>
* Here is how one can do this using {@code AccumulativeRunnable}:
* <pre>
* AccumulativeRunnable<String> doSetTextImpl =
* new AccumulativeRunnable<String>() {
* @Override
* protected void run(String... args) {
* //set to the last string being passed
* setTextImpl(args[args.size - 1]);
* }
* }
* void setText(String text) {
* //add text and send for the execution if needed.
* doSetTextImpl.add(text);
* }
* </pre>
*
* <p>
* Say we want want to implement addDirtyRegion(Rectangle rect)
* which sends this region to the
* handleDirtyRegions(List<Rect> regiouns) on the EDT.
* addDirtyRegions better be accumulated before handling on the EDT.
*
* <p>
* Here is how it can be implemented using AccumulativeRunnable:
* <pre>
* AccumulativeRunnable<Rectangle> doHandleDirtyRegions =
* new AccumulativeRunnable<Rectangle>() {
* @Override
* protected void run(Rectangle... args) {
* handleDirtyRegions(Arrays.asList(args));
* }
* };
* void addDirtyRegion(Rectangle rect) {
* doHandleDirtyRegions.add(rect);
* }
* </pre>
*
* @author Igor Kushnirskiy
* @version $Revision: 1.4 $ $Date: 2006/10/19 21:03:49 $
*
* @param <T> the type this {@code Runnable} accumulates
*
*/
abstract class AccumulativeRunnable<T> implements Runnable {
private List<T> arguments = null;
private Class<?> componentType = null;
/**
* Equivalent to {@code Runnable.run} method with the
* accumulated arguments to process.
*
* @param args accumulated argumets to process.
*/
protected abstract void run(T... args);
/**
* {@inheritDoc}
*
* <p>
* This implementation calls {@code run(T... args)} mehtod
* with the list of accumulated arguments.
*/
public final void run() {
run(flush());
}
/**
* appends arguments and sends this {@cod Runnable} for the
* execution if needed.
* <p>
* This implementation uses {@see #submit} to send this
* {@code Runnable} for execution.
* @param args the arguments to accumulate
*/
public final synchronized void add(T... args) {
if (componentType == null) {
componentType = (Class<T>) args.getClass().getComponentType();
}
boolean isSubmitted = true;
if (arguments == null) {
isSubmitted = false;
arguments = new ArrayList<T>();
}
Collections.addAll(arguments, args);
if (!isSubmitted) {
submit();
}
}
/**
* Sends this {@code Runnable} for the execution
*
* <p>
* This method is to be executed only from {@code add} method.
*
* <p>
* This implementation uses {@code SwingWorker.invokeLater}.
*/
protected void submit() {
SwingUtilities.invokeLater(this);
}
/**
* Returns accumulated arguments and flashes the arguments storage.
*
* @return accumulated artuments
*/
private final synchronized T[] flush() {
List<T> list = arguments;
arguments = null;
if (componentType == null) {
componentType = Object.class;
}
T[] args = (T[]) Array.newInstance(componentType,
list.size());
list.toArray(args);
return args;
}
}
Animation: Swing and thread
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class SwingWithThread extends JComponent {
private Image[] frameList;
private long msPerFrame;
private volatile int currFrame;
private Thread internalThread;
private volatile boolean noStopRequested;
public SwingWithThread(int width, int height, long msPerCycle, int framesPerSec,
Color fgColor) {
setPreferredSize(new Dimension(width, height));
int framesPerCycle = (int) ((framesPerSec * msPerCycle) / 1000);
msPerFrame = 1000L / framesPerSec;
frameList = buildImages(width, height, fgColor, framesPerCycle);
currFrame = 0;
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
// in case ANY exception slips through
x.printStackTrace();
}
}
};
internalThread = new Thread(r);
internalThread.start();
}
private Image[] buildImages(int width, int height, Color color, int count) {
BufferedImage[] im = new BufferedImage[count];
for (int i = 0; i < count; i++) {
im[i] = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
double xShape = 0.0;
double yShape = ((double) (i * height)) / (double) count;
double wShape = width;
double hShape = 2.0 * (height - yShape);
Rectangle2D shape = new Rectangle2D.Double(xShape, yShape, wShape,
hShape);
Graphics2D g2 = im[i].createGraphics();
g2.setColor(color);
g2.fill(shape);
g2.dispose();
}
return im;
}
private void runWork() {
while (noStopRequested) {
currFrame = (currFrame + 1) % frameList.length;
repaint();
try {
Thread.sleep(msPerFrame);
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
public void paint(Graphics g) {
g.drawImage(frameList[currFrame], 0, 0, this);
}
public static void main(String[] args) {
SwingWithThread redSquish = new SwingWithThread(250, 200, 2500L, 10, Color.red);
JFrame f = new JFrame();
f.setLayout(new FlowLayout());
f.add(redSquish);
f.setSize(450, 250);
f.setVisible(true);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
Counter: Swing and thread
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class SecondCounterDemo extends JPanel {
private SecondCounterRunnable sc = new SecondCounterRunnable();
private JButton startB = new JButton("Start");
private JButton stopB = new JButton("Stop");
public SecondCounterDemo() {
stopB.setEnabled(false); // begin with this disabled
startB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startB.setEnabled(false);
Thread counterThread = new Thread(sc, "Counter");
counterThread.start();
stopB.setEnabled(true);
stopB.requestFocus();
}
});
stopB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
stopB.setEnabled(false);
sc.stopClock();
startB.setEnabled(true);
startB.requestFocus();
}
});
JPanel innerButtonP = new JPanel();
innerButtonP.setLayout(new GridLayout(0, 1, 0, 3));
innerButtonP.add(startB);
innerButtonP.add(stopB);
JPanel buttonP = new JPanel();
buttonP.setLayout(new BorderLayout());
buttonP.add(innerButtonP, BorderLayout.NORTH);
this.setLayout(new BorderLayout(10, 10));
this.setBorder(new EmptyBorder(20, 20, 20, 20));
this.add(buttonP, BorderLayout.WEST);
this.add(sc, BorderLayout.CENTER);
}
public static void main(String[] args) {
SecondCounterDemo scm = new SecondCounterDemo();
JFrame f = new JFrame();
f.setContentPane(scm);
f.setSize(320, 200);
f.setVisible(true);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
class SecondCounterRunnable extends JComponent implements Runnable {
private volatile boolean keepRunning;
private Font paintFont = new Font("SansSerif", Font.BOLD, 14);
private volatile String timeMsg = "never started";
private volatile int arcLen = 0;
public SecondCounterRunnable() {
}
public void run() {
runClock();
}
public void runClock() {
DecimalFormat fmt = new DecimalFormat("0.000");
long normalSleepTime = 100;
int counter = 0;
keepRunning = true;
while (keepRunning) {
try {
Thread.sleep(normalSleepTime);
} catch (InterruptedException x) {
// ignore
}
counter++;
double counterSecs = counter / 10.0;
timeMsg = fmt.format(counterSecs);
arcLen = (((int) counterSecs) % 60) * 360 / 60;
repaint();
}
}
public void stopClock() {
keepRunning = false;
}
public void paint(Graphics g) {
g.setColor(Color.black);
g.setFont(paintFont);
g.drawString(timeMsg, 0, 15);
g.fillOval(0, 20, 100, 100);
g.setColor(Color.white);
g.fillOval(3, 23, 94, 94);
g.setColor(Color.red);
g.fillArc(2, 22, 96, 96, 90, -arcLen);
}
}
}
Eliminating race Conditions using Swing Components
// : c14:InvokeLaterFrame.java
// Eliminating race Conditions using Swing Components.
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class InvokeLaterFrame extends JFrame {
private JTextField statusField = new JTextField("Initial Value");
public InvokeLaterFrame() {
Container cp = getContentPane();
cp.add(statusField, BorderLayout.NORTH);
addWindowListener(new WindowAdapter() {
public void windowOpened(WindowEvent e) {
try { // Simulate initialization overhead
Thread.sleep(2000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
statusField.setText("Initialization complete");
}
});
}
public static void main(String[] args) {
final InvokeLaterFrame ilf = new InvokeLaterFrame();
run(ilf, 150, 60);
// Use invokeAndWait() to synchronize output to prompt:
// SwingUtilities.invokeAndWait(new Runnable() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ilf.statusField.setText("Application ready");
}
});
System.out.println("Done");
}
public static void run(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
} ///:~
GUI clock
/* From http://java.sun.ru/docs/books/tutorial/index.html */
/*
* Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or intended
* for use in the design, construction, operation or maintenance of any
* nuclear facility.
*/
import java.applet.Applet;
import java.awt.Graphics;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
public class GUIClock extends Applet implements Sleeper {
private AlarmClock clock;
public void init() {
clock = new AlarmClock();
}
public void start() {
clock.letMeSleepFor(this, ONE_SECOND);
}
public void paint(Graphics g) {
Calendar cal = Calendar.getInstance();
Date date = cal.getTime();
DateFormat dateFormatter = DateFormat.getTimeInstance();
g.drawString(dateFormatter.format(date), 5, 10);
}
public void wakeUp() {
repaint();
clock.letMeSleepFor(this, ONE_SECOND);
}
}
interface Sleeper {
public void wakeUp();
public long ONE_SECOND = 1000; // in milliseconds
}
class AlarmClock {
private static final int MAX_CAPACITY = 10;
private static final int UNUSED = -1;
private static final int NOROOM = -1;
private Sleeper[] sleepers = new Sleeper[MAX_CAPACITY];
private long[] sleepFor = new long[MAX_CAPACITY];
public AlarmClock() {
for (int i = 0; i < MAX_CAPACITY; i++)
sleepFor[i] = UNUSED;
}
public synchronized boolean letMeSleepFor(Sleeper s, long time) {
int index = findNextSlot();
if (index == NOROOM) {
return false;
} else {
sleepers[index] = s;
sleepFor[index] = time;
new AlarmThread(index).start();
return true;
}
}
private synchronized int findNextSlot() {
for (int i = 0; i < MAX_CAPACITY; i++) {
if (sleepFor[i] == UNUSED)
return i;
}
return NOROOM;
}
private synchronized void wakeUpSleeper(int sleeperIndex) {
sleepers[sleeperIndex].wakeUp();
sleepers[sleeperIndex] = null;
sleepFor[sleeperIndex] = UNUSED;
}
private class AlarmThread extends Thread {
int mySleeper;
AlarmThread(int sleeperIndex) {
super();
mySleeper = sleeperIndex;
}
public void run() {
try {
sleep(sleepFor[mySleeper]);
} catch (InterruptedException e) {
}
wakeUpSleeper(mySleeper);
}
}
}
InvokeExample: Swing and thread
/*
Java Swing, 2nd Edition
By Marc Loy, Robert Eckstein, Dave Wood, James Elliott, Brian Cole
ISBN: 0-596-00408-7
Publisher: O"Reilly
*/
// InvokeExample.java
//This class demonstrates several examples of how to handle long-running
//tasks (such as querying a remote resource). Some of the examples are
//good, some are not!
//
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class InvokeExample {
private static JButton good = new JButton("Good");
private static JButton bad = new JButton("Bad");
private static JButton bad2 = new JButton("Bad2");
private static JLabel resultLabel = new JLabel("Ready", JLabel.CENTER);
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Layout . . .
JPanel p = new JPanel();
p.setOpaque(true);
p.setLayout(new FlowLayout());
p.add(good);
p.add(bad);
p.add(bad2);
Container c = f.getContentPane();
c.setLayout(new BorderLayout());
c.add(p, BorderLayout.CENTER);
c.add(resultLabel, BorderLayout.SOUTH);
// Listeners
good.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
resultLabel.setText("Working . . .");
setEnabled(false);
// We"re going to do something that takes a long time, so we
// spin off a thread and update the display when we"re done.
Thread worker = new Thread() {
public void run() {
// Something that takes a long time . . . in real life,
// this
// might be a DB query, remote method invocation, etc.
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
}
// Report the result using invokeLater().
SwingUtilities.invokeLater(new Runnable() {
public void run() {
resultLabel.setText("Ready");
setEnabled(true);
}
});
}
};
worker.start(); // So we don"t hold up the dispatch thread.
}
});
bad.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
resultLabel.setText("Working . . .");
setEnabled(false);
// We"re going to do the same thing, but not in a separate
// thread.
try {
Thread.sleep(5000); // Dispatch thread is starving!
} catch (InterruptedException ex) {
}
// Report the result.
resultLabel.setText("Ready");
setEnabled(true);
}
});
bad2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
resultLabel.setText("Working . . . ");
setEnabled(false);
// The wrong way to use invokeLater(). The runnable() shouldn"t
// starve the dispatch thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
Thread.sleep(5000); // Dispatch thread is starving!
} catch (InterruptedException ex) {
}
resultLabel.setText("Ready");
setEnabled(true);
}
});
}
});
f.setSize(300, 100);
f.setVisible(true);
}
// Allows us to turn the buttons on or off while we work.
static void setEnabled(boolean b) {
good.setEnabled(b);
bad.setEnabled(b);
bad2.setEnabled(b);
}
}
Is Event Dispatcher Thread
import java.awt.Color;
import java.awt.ruponent;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class IsEDTExample extends JPanel {
private boolean keepRunning;
private static int RED = 0;
private static int BLUE = 1;
private static int GREEN = 2;
private static int VARIABLE = 3;
private static int SIZE = 3;
private int threadShade;
private ColorTableModel tableModel= new ColorTableModel();
private Thread colorShadeThread;
public IsEDTExample() {
JTable table = new JTable(tableModel);
table.setRowHeight(100);
table.setDefaultRenderer(Object.class, new ColorRenderer());
add(table);
add(new JLabel("Thread Color Shade:"));
ButtonGroup group = new ButtonGroup();
JRadioButton redOption = new JRadioButton("Red");
group.add(redOption);
redOption.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
threadShade = RED;
}
});
JRadioButton blueOption = new JRadioButton("Blue");
group.add(blueOption);
blueOption.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
threadShade = BLUE;
}
});
JRadioButton greenOption = new JRadioButton("Green");
group.add(greenOption);
greenOption.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
threadShade = GREEN;
}
});
redOption.setSelected(true);
this.threadShade = RED;
add(redOption);
add(greenOption);
add(blueOption);
add(new JButton(new RandomColorAction()));
this.keepRunning = true;
this.colorShadeThread = new Thread(new RandomColorShadeRunnable());
this.colorShadeThread.start();
}
private class RandomColorAction extends AbstractAction {
public RandomColorAction() {
super("Create Random Color");
}
public void actionPerformed(ActionEvent e) {
IsEDTExample.this.tableModel.generateRandomColor(VARIABLE);
}
}
private class ColorTableModel extends AbstractTableModel {
private Color[][] colors = new Color[3][3];
public ColorTableModel() {
for (int i = 0; i < SIZE; i++) {
for (int x = 0; x < SIZE; x++) {
colors[i][x] = Color.white;
}
}
}
public int getRowCount() {
return SIZE;
}
public int getColumnCount() {
return SIZE;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return colors[rowIndex][columnIndex];
}
public void generateRandomColor(int type) {
Random random = new Random(System.currentTimeMillis());
final int row = random.nextInt(SIZE);
final int column = random.nextInt(SIZE);
final Color color;
if (type == RED) {
color = new Color(random.nextInt(256), 0, 0);
} else if (type == BLUE) {
color = new Color(0, 0, random.nextInt(256));
} else if (type == GREEN) {
color = new Color(0, random.nextInt(256), 0);
} else {
color = new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256));
}
if (SwingUtilities.isEventDispatchThread()) {
colors[row][column] = color;
fireTableCellUpdated(row, column);
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
colors[row][column] = color;
fireTableCellUpdated(row, column);
}
});
}
}
}
private class ColorRenderer implements TableCellRenderer {
private JLabel label;
public ColorRenderer() {
label = new JLabel();
label.setOpaque(true);
label.setPreferredSize(new Dimension(100, 100));
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
label.setBackground((Color) value);
return label;
}
}
private class RandomColorShadeRunnable implements Runnable {
public void run() {
while (keepRunning) {
tableModel.generateRandomColor(threadShade);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] a) {
JFrame f = new JFrame("Is Event Dispatch Thread Example");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new IsEDTExample());
f.pack();
f.setVisible(true);
}
}
Race Conditions using Swing Components
// : c14:EventThreadFrame.java
// Race Conditions using Swing Components.
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextField;
public class EventThreadFrame extends JFrame {
private JTextField statusField = new JTextField("Initial Value");
public EventThreadFrame() {
Container cp = getContentPane();
cp.add(statusField, BorderLayout.NORTH);
addWindowListener(new WindowAdapter() {
public void windowOpened(WindowEvent e) {
try { // Simulate initialization overhead
Thread.sleep(2000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
statusField.setText("Initialization complete");
}
});
}
public static void main(String[] args) {
EventThreadFrame etf = new EventThreadFrame();
run(etf, 150, 60);
etf.statusField.setText("Application ready");
System.out.println("Done");
}
public static void run(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
} ///:~
Swing and Thread: cancel a lengthy operation
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class BalanceLookupCantCancel extends JPanel {
private JButton searchB = new JButton("Search");
private JButton cancelB = new JButton("Cancel Search");
private JLabel balanceL;
public BalanceLookupCantCancel() {
buildGUI();
hookupEvents();
}
private void buildGUI() {
cancelB.setEnabled(false);
JPanel innerButtonP = new JPanel();
innerButtonP.setLayout(new GridLayout(1, -1, 5, 5));
innerButtonP.add(searchB);
innerButtonP.add(cancelB);
JPanel buttonP = new JPanel();
buttonP.setLayout(new FlowLayout(FlowLayout.CENTER));
buttonP.add(innerButtonP);
JLabel balancePrefixL = new JLabel("Account Balance:");
balanceL = new JLabel("BALANCE UNKNOWN");
JPanel balanceP = new JPanel(new FlowLayout(FlowLayout.CENTER));
balanceP.add(balancePrefixL);
balanceP.add(balanceL);
JPanel northP = new JPanel();
northP.add(buttonP);
northP.add(balanceP);
setLayout(new BorderLayout());
add(northP, BorderLayout.NORTH);
}
private void hookupEvents() {
searchB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
search();
}
});
cancelB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cancelSearch();
}
});
}
private void search() {
// better be called by event thread!
searchB.setEnabled(false);
cancelB.setEnabled(true);
balanceL.setText("SEARCHING ...");
String bal = lookupBalance();
setBalance(bal);
}
private String lookupBalance() {
try {
// Simulate a lengthy search that takes 5 seconds
// to communicate over the network.
Thread.sleep(5000);
// result "retrieved", return it
return "1,234.56";
} catch (InterruptedException x) {
return "SEARCH CANCELLED";
}
}
private void setBalance(String newBalance) {
// better be called by event thread!
balanceL.setText(newBalance);
cancelB.setEnabled(false);
searchB.setEnabled(true);
}
private void cancelSearch() {
System.out.println("in cancelSearch()");
// Here"s where the code to cancel would go if this
// could ever be called!
}
public static void main(String[] args) {
BalanceLookupCantCancel bl = new BalanceLookupCantCancel();
JFrame f = new JFrame("Can"t Cancel");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setContentPane(bl);
f.setSize(400, 150);
f.setVisible(true);
}
}
Swing and Thread for length operation
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingLengthyOperation extends JPanel {
private JButton searchBn = new JButton("Search");
private JButton cancelBn = new JButton("Cancel Search");
private JLabel balanceL = new JLabel();
private volatile Thread lookupThread;
public SwingLengthyOperation() {
cancelBn.setEnabled(false);
JPanel innerButtonP = new JPanel();
innerButtonP.setLayout(new GridLayout(1, -1, 5, 5));
innerButtonP.add(searchBn);
innerButtonP.add(cancelBn);
JPanel buttonP = new JPanel();
buttonP.setLayout(new FlowLayout(FlowLayout.CENTER));
buttonP.add(innerButtonP);
JLabel balancePrefixL = new JLabel("Account Balance:");
JPanel balanceP = new JPanel();
balanceP.setLayout(new FlowLayout(FlowLayout.CENTER));
balanceP.add(balancePrefixL);
balanceP.add(balanceL);
JPanel northP = new JPanel();
northP.setLayout(new GridLayout(-1, 1, 5, 5));
northP.add(buttonP);
northP.add(balanceP);
setLayout(new BorderLayout());
add(northP, BorderLayout.NORTH);
searchBn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
search();
}
});
cancelBn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cancelSearch();
}
});
}
private void search() {
ensureEventThread();
searchBn.setEnabled(false);
cancelBn.setEnabled(true);
balanceL.setText("SEARCHING ...");
lookupAsync();
}
private void lookupAsync() {
Runnable lookupRun = new Runnable() {
public void run() {
String bal = lookupBalance();
setBalanceSafely(bal);
}
};
lookupThread = new Thread(lookupRun, "lookupThread");
lookupThread.start();
}
private String lookupBalance() {
try {
Thread.sleep(5000);
return "1,234.56";
} catch (InterruptedException x) {
return "SEARCH CANCELLED";
}
}
private void setBalanceSafely(String newBal) {
final String newBalance = newBal;
Runnable r = new Runnable() {
public void run() {
try {
setBalance(newBalance);
} catch (Exception x) {
x.printStackTrace();
}
}
};
SwingUtilities.invokeLater(r);
}
private void setBalance(String newBalance) {
ensureEventThread();
balanceL.setText(newBalance);
cancelBn.setEnabled(false);
searchBn.setEnabled(true);
}
private void cancelSearch() {
ensureEventThread();
cancelBn.setEnabled(false);
if (lookupThread != null) {
lookupThread.interrupt();
}
}
private void ensureEventThread() {
if (SwingUtilities.isEventDispatchThread()) {
return;
}
throw new RuntimeException("only the event "
+ "thread should invoke this method");
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setContentPane(new SwingLengthyOperation());
f.setSize(400, 150);
f.setVisible(true);
}
}
Swing and Threading
/*
* Copyright (c) 2007, Romain Guy
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
/**
* @author Romain Guy
*/
public class SwingThreading extends JFrame implements ActionListener {
private JLabel counter;
private int tickCounter = 0;
private static SwingThreading edt;
public SwingThreading() {
super("Swing Threading");
JButton freezer = new JButton("Increment");
freezer.addActionListener(this);
counter = new JLabel("0");
add(freezer, BorderLayout.CENTER);
add(counter, BorderLayout.SOUTH);
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
incrementLabel();
}
private void incrementLabel() {
tickCounter++;
Runnable code = new Runnable() {
public void run() {
counter.setText(String.valueOf(tickCounter));
}
};
if (SwingUtilities.isEventDispatchThread()) {
code.run();
} else {
SwingUtilities.invokeLater(code);
}
}
public static void main(String... args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
edt = new SwingThreading();
edt.setVisible(true);
new Thread(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
}
edt.incrementLabel();
}
}
}).start();
}
});
}
}
Swing and thread: invoke and wait
import java.awt.FlowLayout;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class InvokeAndWaitDemo extends Object {
private static void print(String msg) {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + msg);
}
public static void main(String[] args) {
final JLabel label = new JLabel("--------");
JPanel panel = new JPanel(new FlowLayout());
panel.add(label);
JFrame f = new JFrame("InvokeAndWaitDemo");
f.setContentPane(panel);
f.setSize(300, 100);
f.setVisible(true);
try {
print("sleeping for 3 seconds");
Thread.sleep(3000);
print("creating code block for event thread");
Runnable setTextRun = new Runnable() {
public void run() {
print("about to do setText()");
label.setText("New text!");
}
};
print("about to invokeAndWait()");
SwingUtilities.invokeAndWait(setTextRun);
print("back from invokeAndWait()");
} catch (InterruptedException ix) {
print("interrupted while waiting on invokeAndWait()");
} catch (InvocationTargetException x) {
print("exception thrown from run()");
}
}
}
Swing and Thread: repaint
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class DigitalTimer extends JLabel {
private volatile String timeText;
private Thread internalThread;
private volatile boolean noStopRequested;
public DigitalTimer() {
setBorder(BorderFactory.createLineBorder(Color.black));
setHorizontalAlignment(SwingConstants.RIGHT);
setFont(new Font("SansSerif", Font.BOLD, 16));
setText("00000.0"); // use to size component
setMinimumSize(getPreferredSize());
setPreferredSize(getPreferredSize());
setSize(getPreferredSize());
timeText = "0.0";
setText(timeText);
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
x.printStackTrace();
}
}
};
internalThread = new Thread(r, "DigitalTimer");
internalThread.start();
}
private void runWork() {
long startTime = System.currentTimeMillis();
int tenths = 0;
long normalSleepTime = 100;
long nextSleepTime = 100;
DecimalFormat fmt = new DecimalFormat("0.0");
Runnable updateText = new Runnable() {
public void run() {
setText(timeText);
}
};
while (noStopRequested) {
try {
Thread.sleep(nextSleepTime);
tenths++;
long currTime = System.currentTimeMillis();
long elapsedTime = currTime - startTime;
nextSleepTime = normalSleepTime
+ ((tenths * 100) - elapsedTime);
if (nextSleepTime < 0) {
nextSleepTime = 0;
}
timeText = fmt.format(elapsedTime / 1000.0);
SwingUtilities.invokeAndWait(updateText);
} catch (InterruptedException ix) {
// stop running
return;
} catch (InvocationTargetException x) {
x.printStackTrace();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.getContentPane().setLayout(new FlowLayout());
f.getContentPane().add(new DigitalTimer());
f.setSize(250, 100);
f.setVisible(true);
}
}
Swing and threads: invoke later
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class InvokeLaterDemo extends Object {
private static void print(String msg) {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + msg);
}
public static void main(String[] args) {
final JLabel label = new JLabel("--------");
JPanel panel = new JPanel(new FlowLayout());
panel.add(label);
JFrame f = new JFrame();
f.setContentPane(panel);
f.setSize(300, 100);
f.setVisible(true);
try {
print("sleeping for 3 seconds");
Thread.sleep(3000);
} catch (InterruptedException ix) {
print("interrupted while sleeping");
}
print("creating code block for event thread");
Runnable setTextRun = new Runnable() {
public void run() {
try {
Thread.sleep(500);
print("about to do setText()");
label.setText("New text!");
} catch (Exception x) {
x.printStackTrace();
}
}
};
print("call invokeLater()");
SwingUtilities.invokeLater(setTextRun);
print("return from invokeLater()");
}
}
Swing and threads: scroll text
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ScrollText extends JComponent {
private BufferedImage image;
private Dimension imageSize;
private volatile int currOffset;
private Thread internalThread;
private volatile boolean noStopRequested;
public ScrollText(String text) {
currOffset = 0;
buildImage(text);
setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
x.printStackTrace();
}
}
};
internalThread = new Thread(r, "ScrollText");
internalThread.start();
}
private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
BufferedImage scratchImage = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB);
Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);
Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);
FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());
int horizontalPad = 10;
int verticalPad = 6;
imageSize = new Dimension(textWidth + horizontalPad, textHeight
+ verticalPad);
image = new BufferedImage(imageSize.width, imageSize.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);
int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());
g2.setColor(Color.white);
g2.fillRect(0, 0, imageSize.width, imageSize.height);
g2.setColor(Color.blue);
tl.draw(g2, 0, baselineOffset);
// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
}
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);
int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);
// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
}
private void runWork() {
while (noStopRequested) {
try {
Thread.sleep(100); // 10 frames per second
// adjust the scroll position
currOffset = (currOffset + 1) % imageSize.width;
// signal the event thread to call paint()
repaint();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
public static void main(String[] args) {
ScrollText st = new ScrollText("Java can do animation!");
JPanel p = new JPanel(new FlowLayout());
p.add(st);
JFrame f = new JFrame("ScrollText Demo");
f.setContentPane(p);
f.setSize(400, 100);
f.setVisible(true);
}
}
Swing and threads: slide
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SlideShow extends JComponent {
private BufferedImage[] slide;
private Dimension slideSize;
private volatile int currSlide;
private Thread internalThread;
private volatile boolean noStopRequested;
public SlideShow() {
currSlide = 0;
slideSize = new Dimension(50, 50);
buildSlides();
setMinimumSize(slideSize);
setPreferredSize(slideSize);
setMaximumSize(slideSize);
setSize(slideSize);
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
x.printStackTrace();
}
}
};
internalThread = new Thread(r, "SlideShow");
internalThread.start();
}
private void buildSlides() {
RenderingHints renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
slide = new BufferedImage[20];
Color rectColor = Color.BLUE;
Color circleColor = Color.YELLOW;
for (int i = 0; i < slide.length; i++) {
slide[i] = new BufferedImage(slideSize.width, slideSize.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = slide[i].createGraphics();
g2.setRenderingHints(renderHints);
g2.setColor(rectColor);
g2.fillRect(0, 0, slideSize.width, slideSize.height);
g2.setColor(circleColor);
int diameter = 0;
if (i < (slide.length / 2)) {
diameter = 5 + (8 * i);
} else {
diameter = 5 + (8 * (slide.length - i));
}
int inset = (slideSize.width - diameter) / 2;
g2.fillOval(inset, inset, diameter, diameter);
g2.setColor(Color.black);
g2.drawRect(0, 0, slideSize.width - 1, slideSize.height - 1);
g2.dispose();
}
}
public void paint(Graphics g) {
g.drawImage(slide[currSlide], 0, 0, this);
}
private void runWork() {
while (noStopRequested) {
try {
Thread.sleep(100); // 10 frames per second
currSlide = (currSlide + 1) % slide.length;
repaint();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
public static void main(String[] args) {
SlideShow ss = new SlideShow();
JPanel p = new JPanel(new FlowLayout());
p.add(ss);
JFrame f = new JFrame("SlideShow");
f.setContentPane(p);
f.setSize(250, 150);
f.setVisible(true);
}
}
Swing Type Tester 10
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester10 extends JFrame implements CharacterSource {
protected RandomCharacterGenerator producer;
private AnimatedCharacterDisplayCanvas displayCanvas;
private CharacterDisplayCanvas feedbackCanvas;
private JButton quitButton;
private JButton startButton;
private JButton stopButton;
private CharacterEventHandler handler;
private ScoreLabel score;
public SwingTypeTester10() {
initComponents();
}
private void initComponents() {
handler = new CharacterEventHandler();
producer = new RandomCharacterGenerator();
producer.setDone(true);
producer.start();
displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
feedbackCanvas = new CharacterDisplayCanvas(this);
quitButton = new JButton();
startButton = new JButton();
stopButton = new JButton();
score = new ScoreLabel(producer, this);
Container pane = getContentPane();
JPanel p1 = new JPanel();
p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
p1.add(displayCanvas);
p1.add(feedbackCanvas);
JPanel p2 = new JPanel();
score.setText(" ");
score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
p2.add(score);
startButton.setText("Start");
p2.add(startButton);
stopButton.setText("Stop");
stopButton.setEnabled(false);
p2.add(stopButton);
quitButton.setText("Quit");
p2.add(quitButton);
p1.add(p2);
pane.add(p1, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
quit();
}
});
feedbackCanvas.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent ke) {
char c = ke.getKeyChar();
if (c != KeyEvent.CHAR_UNDEFINED)
newCharacter((int) c);
}
});
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
displayCanvas.setDone(false);
producer.setDone(false);
score.resetScore();
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus();
}
});
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
startButton.setEnabled(true);
stopButton.setEnabled(false);
producer.setDone(true);
displayCanvas.setDone(true);
feedbackCanvas.setEnabled(false);
}
});
quitButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
quit();
}
});
}
private void quit() {
System.exit(0);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void newCharacter(int c) {
handler.fireNewCharacter(this, c);
}
public void nextCharacter() {
throw new IllegalStateException("We don"t produce on demand");
}
public static void main(String args[]) {
new SwingTypeTester10().show();
}
}
class ScoreLabel extends JLabel implements CharacterListener {
private volatile int score = 0;
private int char2type = -1;
private CharacterSource generator = null, typist = null;
private Lock scoreLock = new ReentrantLock();
public ScoreLabel(CharacterSource generator, CharacterSource typist) {
this.generator = generator;
this.typist = typist;
if (generator != null)
generator.addCharacterListener(this);
if (typist != null)
typist.addCharacterListener(this);
}
public ScoreLabel() {
this(null, null);
}
public void resetGenerator(CharacterSource newGenerator) {
try {
scoreLock.lock();
if (generator != null)
generator.removeCharacterListener(this);
generator = newGenerator;
if (generator != null)
generator.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetTypist(CharacterSource newTypist) {
try {
scoreLock.lock();
if (typist != null)
typist.removeCharacterListener(this);
typist = newTypist;
if (typist != null)
typist.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetScore() {
try {
scoreLock.lock();
score = 0;
char2type = -1;
setScore();
} finally {
scoreLock.unlock();
}
}
private void setScore() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setText(Integer.toString(score));
}
});
}
public void newCharacter(CharacterEvent ce) {
scoreLock.lock();
try {
if (ce.source == generator) {
if (char2type != -1) {
score--;
setScore();
}
char2type = ce.character;
} else {
if (char2type != ce.character) {
score--;
} else {
score++;
char2type = -1;
}
}
setScore();
} finally {
scoreLock.unlock();
}
}
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
private static char[] chars;
private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
static {
chars = charArray.toCharArray();
}
private Random random;
private CharacterEventHandler handler;
private boolean done = true;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
public RandomCharacterGenerator() {
random = new Random();
handler = new CharacterEventHandler();
}
public int getPauseTime(int minTime, int maxTime) {
return (int) (minTime + ((maxTime - minTime) * random.nextDouble()));
}
public int getPauseTime() {
return getPauseTime(2000, 5500);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void nextCharacter() {
handler.fireNewCharacter(this,
(int) chars[random.nextInt(chars.length)]);
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
nextCharacter();
cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (!done)
cv.signal();
} finally {
lock.unlock();
}
}
}
interface CharacterListener {
public void newCharacter(CharacterEvent ce);
}
interface CharacterSource {
public void addCharacterListener(CharacterListener cl);
public void removeCharacterListener(CharacterListener cl);
public void nextCharacter();
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas implements
CharacterListener, Runnable {
private volatile boolean done = false;
private int curX;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
private Thread timer = null;
public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
super(cs);
}
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint();
}
public synchronized void paintComponent(Graphics gc) {
if (tmpChar[0] == 0)
return;
Dimension d = getSize();
int charWidth = fm.charWidth(tmpChar[0]);
gc.clearRect(0, 0, d.width, d.height);
gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
if (curX > d.width - charWidth)
curX = 0;
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
repaint();
cv.await(100, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (timer == null) {
timer = new Thread(this);
timer.start();
}
if (!done)
cv.signal();
} finally {
lock.unlock();
}
}
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
protected FontMetrics fm;
protected char[] tmpChar = new char[1];
protected int fontHeight;
public CharacterDisplayCanvas(CharacterSource cs) {
setFont(new Font("Monospaced", Font.BOLD, 18));
fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
fontHeight = fm.getHeight();
cs.addCharacterListener(this);
}
public void setCharacterListener(CharacterSource cs) {
cs.addCharacterListener(this);
}
public Dimension preferredSize() {
return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10);
}
public synchronized void newCharacter(CharacterEvent ce) {
tmpChar[0] = (char) ce.character;
repaint();
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize();
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0)
return;
int charWidth = fm.charWidth((int) tmpChar[0]);
gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight);
}
}
class CharacterEvent {
public CharacterSource source;
public int character;
public CharacterEvent(CharacterSource cs, int c) {
source = cs;
character = c;
}
}
class CharacterEventHandler {
private Vector listeners = new Vector();
public void addCharacterListener(CharacterListener cl) {
listeners.add(cl);
}
public void removeCharacterListener(CharacterListener cl) {
listeners.remove(cl);
}
public void fireNewCharacter(CharacterSource source, int c) {
CharacterEvent ce = new CharacterEvent(source, c);
CharacterListener[] cl = (CharacterListener[]) listeners
.toArray(new CharacterListener[0]);
for (int i = 0; i < cl.length; i++)
cl[i].newCharacter(ce);
}
}
SwingUtilities.invokeLater and swing thread
/*
* Copyright (c) 2007, Romain Guy
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
/**
* @author Romain Guy
*/
public class SwingThreadingWait extends JFrame implements ActionListener {
private JLabel counter;
private long start = 0;
public SwingThreadingWait() {
super("Invoke & Wait");
JButton freezer = new JButton("Open File");
freezer.addActionListener(this);
counter = new JLabel("Time elapsed: 0s");
add(freezer, BorderLayout.CENTER);
add(counter, BorderLayout.SOUTH);
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
start = System.currentTimeMillis();
new Thread(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
final int elapsed = (int) ((System.currentTimeMillis() - start) / 1000);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
counter.setText("Time elapsed: " + elapsed + "s");
}
});
if (elapsed == 4) {
try {
final int[] answer = new int[1];
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
answer[0] = JOptionPane.showConfirmDialog(SwingThreadingWait.this,
"Abort long operation?", "Abort?", JOptionPane.YES_NO_OPTION);
}
});
if (answer[0] == JOptionPane.YES_OPTION) {
return;
}
} catch (InterruptedException e1) {
} catch (InvocationTargetException e1) {
}
}
}
}
}).start();
}
public static void main(String... args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
SwingThreadingWait edt = new SwingThreadingWait();
edt.setVisible(true);
}
});
}
}
Thread accuracy: Swing and threads
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class SecondCounterMain extends JPanel {
private SecondCounter sc = new SecondCounter();
private JButton startB = new JButton("Start");
private JButton stopB = new JButton("Stop");
public SecondCounterMain() {
stopB.setEnabled(false);
startB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startB.setEnabled(false);
Thread counterThread = new Thread(sc, "Counter");
counterThread.start();
stopB.setEnabled(true);
stopB.requestFocus();
}
});
stopB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
stopB.setEnabled(false);
sc.stopClock();
startB.setEnabled(true);
startB.requestFocus();
}
});
JPanel innerButtonP = new JPanel();
innerButtonP.setLayout(new GridLayout(0, 1, 0, 3));
innerButtonP.add(startB);
innerButtonP.add(stopB);
JPanel buttonP = new JPanel();
buttonP.setLayout(new BorderLayout());
buttonP.add(innerButtonP, BorderLayout.NORTH);
this.setLayout(new BorderLayout(10, 10));
this.setBorder(new EmptyBorder(20, 20, 20, 20));
this.add(buttonP, BorderLayout.WEST);
this.add(sc, BorderLayout.CENTER);
}
public static void main(String[] args) {
SecondCounterMain scm = new SecondCounterMain();
JFrame f = new JFrame("Second Counter");
f.setContentPane(scm);
f.setSize(320, 200);
f.setVisible(true);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
class SecondCounter extends JComponent implements Runnable {
private volatile boolean keepRunning;
private Font paintFont = new Font("SansSerif", Font.BOLD, 14);
private volatile String timeMsg = "never started";
private volatile int arcLen = 0;
public SecondCounter() {
}
public void run() {
runClock();
}
public void runClock() {
DecimalFormat fmt = new DecimalFormat("0.000");
long normalSleepTime = 100;
long nextSleepTime = normalSleepTime;
int counter = 0;
long startTime = System.currentTimeMillis();
keepRunning = true;
while (keepRunning) {
try {
Thread.sleep(nextSleepTime);
} catch (InterruptedException x) {
// ignore
}
counter++;
double counterSecs = counter / 10.0;
double elapsedSecs = (System.currentTimeMillis() - startTime) / 1000.0;
double diffSecs = counterSecs - elapsedSecs;
nextSleepTime = normalSleepTime + ((long) (diffSecs * 1000.0));
if (nextSleepTime < 0) {
nextSleepTime = 0;
}
timeMsg = fmt.format(counterSecs) + " - "
+ fmt.format(elapsedSecs) + " = "
+ fmt.format(diffSecs);
arcLen = (((int) counterSecs) % 60) * 360 / 60;
repaint();
}
}
public void stopClock() {
keepRunning = false;
}
public void paint(Graphics g) {
g.setColor(Color.black);
g.setFont(paintFont);
g.drawString(timeMsg, 0, 15);
g.fillOval(0, 20, 100, 100); // black border
g.setColor(Color.white);
g.fillOval(3, 23, 94, 94); // white for unused portion
g.setColor(Color.blue); // blue for used portion
g.fillArc(2, 22, 96, 96, 90, -arcLen);
}
}
}
Thread and Swing 1
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester7 extends JFrame implements CharacterSource {
protected RandomCharacterGenerator producer;
private AnimatedCharacterDisplayCanvas displayCanvas;
private CharacterDisplayCanvas feedbackCanvas;
private JButton quitButton;
private JButton startButton;
private JButton stopButton;
private CharacterEventHandler handler;
private ScoreLabel score;
public SwingTypeTester7() {
initComponents();
}
private void initComponents() {
handler = new CharacterEventHandler();
producer = new RandomCharacterGenerator();
producer.setDone(true);
producer.start();
displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
feedbackCanvas = new CharacterDisplayCanvas(this);
quitButton = new JButton();
startButton = new JButton();
stopButton = new JButton();
score = new ScoreLabel(producer, this);
Container pane = getContentPane();
JPanel p1 = new JPanel();
p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
p1.add(displayCanvas);
p1.add(feedbackCanvas);
JPanel p2 = new JPanel();
score.setText(" ");
score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
p2.add(score);
startButton.setText("Start");
p2.add(startButton);
stopButton.setText("Stop");
stopButton.setEnabled(false);
p2.add(stopButton);
quitButton.setText("Quit");
p2.add(quitButton);
p1.add(p2);
pane.add(p1, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
quit();
}
});
feedbackCanvas.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent ke) {
char c = ke.getKeyChar();
if (c != KeyEvent.CHAR_UNDEFINED)
newCharacter((int) c);
}
});
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
displayCanvas.setDone(false);
producer.setDone(false);
score.resetScore();
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus();
}
});
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
startButton.setEnabled(true);
stopButton.setEnabled(false);
producer.setDone(true);
displayCanvas.setDone(true);
feedbackCanvas.setEnabled(false);
}
});
quitButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
quit();
}
});
}
private void quit() {
System.exit(0);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void newCharacter(int c) {
handler.fireNewCharacter(this, c);
}
public void nextCharacter() {
throw new IllegalStateException("We don"t produce on demand");
}
public static void main(String args[]) {
new SwingTypeTester7().show();
}
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
implements CharacterListener, Runnable {
private volatile boolean done = false;
private int curX;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
private Thread timer = null;
public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
super(cs);
}
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint();
}
public synchronized void paintComponent(Graphics gc) {
if (tmpChar[0] == 0)
return;
Dimension d = getSize();
int charWidth = fm.charWidth(tmpChar[0]);
gc.clearRect(0, 0, d.width, d.height);
gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
if (curX > d.width - charWidth)
curX = 0;
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
repaint();
cv.await(100, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (timer == null) {
timer = new Thread(this);
timer.start();
}
if (!done) cv.signal();
} finally {
lock.unlock();
}
}
}
class ScoreLabel extends JLabel implements CharacterListener {
private volatile int score = 0;
private int char2type = -1;
private CharacterSource generator = null, typist = null;
private Lock scoreLock = new ReentrantLock();
public ScoreLabel(CharacterSource generator, CharacterSource typist) {
this.generator = generator;
this.typist = typist;
if (generator != null)
generator.addCharacterListener(this);
if (typist != null)
typist.addCharacterListener(this);
}
public ScoreLabel() {
this(null, null);
}
public void resetGenerator(CharacterSource newGenerator) {
try {
scoreLock.lock();
if (generator != null)
generator.removeCharacterListener(this);
generator = newGenerator;
if (generator != null)
generator.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetTypist(CharacterSource newTypist) {
try {
scoreLock.lock();
if (typist != null)
typist.removeCharacterListener(this);
typist = newTypist;
if (typist != null)
typist.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetScore() {
try {
scoreLock.lock();
score = 0;
char2type = -1;
setScore();
} finally {
scoreLock.unlock();
}
}
private void setScore() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setText(Integer.toString(score));
}
});
}
public void newCharacter(CharacterEvent ce) {
scoreLock.lock();
try {
if (ce.source == generator) {
if (char2type != -1) {
score--;
setScore();
}
char2type = ce.character;
}
else {
if (char2type != ce.character) {
score--;
} else {
score++;
char2type = -1;
}
}
setScore();
} finally {
scoreLock.unlock();
}
}
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
private static char[] chars;
private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
static {
chars = charArray.toCharArray();
}
private Random random;
private CharacterEventHandler handler;
private boolean done = true;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
public RandomCharacterGenerator() {
random = new Random();
handler = new CharacterEventHandler();
}
public int getPauseTime(int minTime, int maxTime) {
return (int) (minTime + ((maxTime-minTime)*random.nextDouble()));
}
public int getPauseTime() {
return getPauseTime(2000, 5500);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void nextCharacter() {
handler.fireNewCharacter(this,
(int) chars[random.nextInt(chars.length)]);
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
nextCharacter();
cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (!done) cv.signal();
} finally {
lock.unlock();
}
}
}
interface CharacterSource {
public void addCharacterListener(CharacterListener cl);
public void removeCharacterListener(CharacterListener cl);
public void nextCharacter();
}
class CharacterEvent {
public CharacterSource source;
public int character;
public CharacterEvent(CharacterSource cs, int c) {
source = cs;
character = c;
}
}
class CharacterEventHandler {
private Vector listeners = new Vector();
public void addCharacterListener(CharacterListener cl) {
listeners.add(cl);
}
public void removeCharacterListener(CharacterListener cl) {
listeners.remove(cl);
}
public void fireNewCharacter(CharacterSource source, int c) {
CharacterEvent ce = new CharacterEvent(source, c);
CharacterListener[] cl = (CharacterListener[] )
listeners.toArray(new CharacterListener[0]);
for (int i = 0; i < cl.length; i++)
cl[i].newCharacter(ce);
}
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
protected FontMetrics fm;
protected char[] tmpChar = new char[1];
protected int fontHeight;
public CharacterDisplayCanvas(CharacterSource cs) {
setFont(new Font("Monospaced", Font.BOLD, 18));
fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
fontHeight = fm.getHeight();
cs.addCharacterListener(this);
}
public void setCharacterListener(CharacterSource cs) {
cs.addCharacterListener(this);
}
public Dimension preferredSize() {
return new Dimension(fm.getMaxAscent() + 10,
fm.getMaxAdvance() + 10);
}
public synchronized void newCharacter(CharacterEvent ce) {
tmpChar[0] = (char) ce.character;
repaint();
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize();
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0)
return;
int charWidth = fm.charWidth((int) tmpChar[0]);
gc.drawChars(tmpChar, 0, 1,
(d.width - charWidth) / 2, fontHeight);
}
}
interface CharacterListener {
public void newCharacter(CharacterEvent ce);
}
Thread and Swing 2
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester8 extends JFrame implements CharacterSource {
protected RandomCharacterGenerator producer;
private AnimatedCharacterDisplayCanvas displayCanvas;
private CharacterDisplayCanvas feedbackCanvas;
private JButton quitButton;
private JButton startButton;
private JButton stopButton;
private CharacterEventHandler handler;
private ScoreLabel score;
public SwingTypeTester8() {
initComponents();
}
private void initComponents() {
handler = new CharacterEventHandler();
producer = new RandomCharacterGenerator();
producer.setDone(true);
producer.start();
displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
feedbackCanvas = new CharacterDisplayCanvas(this);
quitButton = new JButton();
startButton = new JButton();
stopButton = new JButton();
score = new ScoreLabel(producer, this);
Container pane = getContentPane();
JPanel p1 = new JPanel();
p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
p1.add(displayCanvas);
p1.add(feedbackCanvas);
JPanel p2 = new JPanel();
score.setText(" ");
score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
p2.add(score);
startButton.setText("Start");
p2.add(startButton);
stopButton.setText("Stop");
stopButton.setEnabled(false);
p2.add(stopButton);
quitButton.setText("Quit");
p2.add(quitButton);
p1.add(p2);
pane.add(p1, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
quit();
}
});
feedbackCanvas.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent ke) {
char c = ke.getKeyChar();
if (c != KeyEvent.CHAR_UNDEFINED)
newCharacter((int) c);
}
});
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
displayCanvas.setDone(false);
producer.setDone(false);
score.resetScore();
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus();
}
});
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
startButton.setEnabled(true);
stopButton.setEnabled(false);
producer.setDone(true);
displayCanvas.setDone(true);
feedbackCanvas.setEnabled(false);
}
});
quitButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
quit();
}
});
}
private void quit() {
System.exit(0);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void newCharacter(int c) {
handler.fireNewCharacter(this, c);
}
public void nextCharacter() {
throw new IllegalStateException("We don"t produce on demand");
}
public static void main(String args[]) {
new SwingTypeTester8().show();
}
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
private static char[] chars;
private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
static {
chars = charArray.toCharArray();
}
private Random random;
private CharacterEventHandler handler;
private boolean done = true;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
public RandomCharacterGenerator() {
random = new Random();
handler = new CharacterEventHandler();
}
public int getPauseTime(int minTime, int maxTime) {
return (int) (minTime + ((maxTime-minTime)*random.nextDouble()));
}
public int getPauseTime() {
return getPauseTime(2000, 5500);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void nextCharacter() {
handler.fireNewCharacter(this,
(int) chars[random.nextInt(chars.length)]);
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
nextCharacter();
cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (!done) cv.signal();
} finally {
lock.unlock();
}
}
}
class ScoreLabel extends JLabel implements CharacterListener {
private volatile int score = 0;
private int char2type = -1;
private CharacterSource generator = null, typist = null;
private Lock scoreLock = new ReentrantLock();
public ScoreLabel(CharacterSource generator, CharacterSource typist) {
this.generator = generator;
this.typist = typist;
if (generator != null)
generator.addCharacterListener(this);
if (typist != null)
typist.addCharacterListener(this);
}
public ScoreLabel() {
this(null, null);
}
public void resetGenerator(CharacterSource newGenerator) {
try {
scoreLock.lock();
if (generator != null)
generator.removeCharacterListener(this);
generator = newGenerator;
if (generator != null)
generator.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetTypist(CharacterSource newTypist) {
try {
scoreLock.lock();
if (typist != null)
typist.removeCharacterListener(this);
typist = newTypist;
if (typist != null)
typist.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetScore() {
try {
scoreLock.lock();
score = 0;
char2type = -1;
setScore();
} finally {
scoreLock.unlock();
}
}
private void setScore() {
if (SwingUtilities.isEventDispatchThread())
setText(Integer.toString(score));
else try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
setText(Integer.toString(score));
}
});
} catch (InterruptedException ie) {
} catch (InvocationTargetException ite) {}
}
public void newCharacter(CharacterEvent ce) {
scoreLock.lock();
try {
if (ce.source == generator) {
if (char2type != -1) {
score--;
setScore();
}
char2type = ce.character;
}
else {
if (char2type != ce.character) {
score--;
} else {
score++;
char2type = -1;
}
}
setScore();
} finally {
scoreLock.unlock();
}
}
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
implements CharacterListener, Runnable {
private volatile boolean done = false;
private int curX;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
private Thread timer = null;
public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
super(cs);
}
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint();
}
public synchronized void paintComponent(Graphics gc) {
if (tmpChar[0] == 0)
return;
Dimension d = getSize();
int charWidth = fm.charWidth(tmpChar[0]);
gc.clearRect(0, 0, d.width, d.height);
gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
if (curX > d.width - charWidth)
curX = 0;
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
repaint();
cv.await(100, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (timer == null) {
timer = new Thread(this);
timer.start();
}
if (!done) cv.signal();
} finally {
lock.unlock();
}
}
}
interface CharacterSource {
public void addCharacterListener(CharacterListener cl);
public void removeCharacterListener(CharacterListener cl);
public void nextCharacter();
}
class CharacterEvent {
public CharacterSource source;
public int character;
public CharacterEvent(CharacterSource cs, int c) {
source = cs;
character = c;
}
}
class CharacterEventHandler {
private Vector listeners = new Vector();
public void addCharacterListener(CharacterListener cl) {
listeners.add(cl);
}
public void removeCharacterListener(CharacterListener cl) {
listeners.remove(cl);
}
public void fireNewCharacter(CharacterSource source, int c) {
CharacterEvent ce = new CharacterEvent(source, c);
CharacterListener[] cl = (CharacterListener[] )
listeners.toArray(new CharacterListener[0]);
for (int i = 0; i < cl.length; i++)
cl[i].newCharacter(ce);
}
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
protected FontMetrics fm;
protected char[] tmpChar = new char[1];
protected int fontHeight;
public CharacterDisplayCanvas(CharacterSource cs) {
setFont(new Font("Monospaced", Font.BOLD, 18));
fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
fontHeight = fm.getHeight();
cs.addCharacterListener(this);
}
public void setCharacterListener(CharacterSource cs) {
cs.addCharacterListener(this);
}
public Dimension preferredSize() {
return new Dimension(fm.getMaxAscent() + 10,
fm.getMaxAdvance() + 10);
}
public synchronized void newCharacter(CharacterEvent ce) {
tmpChar[0] = (char) ce.character;
repaint();
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize();
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0)
return;
int charWidth = fm.charWidth((int) tmpChar[0]);
gc.drawChars(tmpChar, 0, 1,
(d.width - charWidth) / 2, fontHeight);
}
}
interface CharacterListener {
public void newCharacter(CharacterEvent ce);
}
Thread and Swing 3
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004
ISBN: 0-596-00782-5
*/
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SwingTypeTester9 extends JFrame implements CharacterSource {
protected RandomCharacterGenerator producer;
private AnimatedCharacterDisplayCanvas displayCanvas;
private CharacterDisplayCanvas feedbackCanvas;
private JButton quitButton;
private JButton startButton;
private JButton stopButton;
private CharacterEventHandler handler;
private ScoreLabel score;
private SwingTypeTester9 parent;
public SwingTypeTester9() {
initComponents();
}
private void initComponents() {
parent = this;
handler = new CharacterEventHandler();
producer = new RandomCharacterGenerator();
producer.setDone(true);
producer.start();
displayCanvas = new AnimatedCharacterDisplayCanvas(producer);
feedbackCanvas = new CharacterDisplayCanvas(this);
quitButton = new JButton();
startButton = new JButton();
stopButton = new JButton();
score = new ScoreLabel(producer, this);
Container pane = getContentPane();
JPanel p1 = new JPanel();
p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS));
p1.add(displayCanvas);
p1.add(feedbackCanvas);
JPanel p2 = new JPanel();
score.setText(" ");
score.setFont(new Font("MONOSPACED", Font.BOLD, 30));
p2.add(score);
startButton.setText("Start");
p2.add(startButton);
stopButton.setText("Stop");
stopButton.setEnabled(false);
p2.add(stopButton);
quitButton.setText("Quit");
p2.add(quitButton);
p1.add(p2);
pane.add(p1, BorderLayout.NORTH);
pack();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
quit();
}
});
feedbackCanvas.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent ke) {
char c = ke.getKeyChar();
if (c != KeyEvent.CHAR_UNDEFINED)
newCharacter((int) c);
}
});
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
new FeedbackFrame(parent).show();
}
});
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
startButton.setEnabled(true);
stopButton.setEnabled(false);
producer.setDone(true);
displayCanvas.setDone(true);
feedbackCanvas.setEnabled(false);
}
});
quitButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
quit();
}
});
}
void setupDone() {
displayCanvas.setDone(false);
producer.setDone(false);
score.resetScore();
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus();
}
void setupCancelled() {
}
private void quit() {
System.exit(0);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void newCharacter(int c) {
handler.fireNewCharacter(this, c);
}
public void nextCharacter() {
throw new IllegalStateException("We don"t produce on demand");
}
public static void main(String args[]) {
new SwingTypeTester9().show();
}
}
class RandomCharacterGenerator extends Thread implements CharacterSource {
private static char[] chars;
private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
static {
chars = charArray.toCharArray();
}
private Random random;
private CharacterEventHandler handler;
private boolean done = true;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
public RandomCharacterGenerator() {
random = new Random();
handler = new CharacterEventHandler();
}
public int getPauseTime(int minTime, int maxTime) {
return (int) (minTime + ((maxTime - minTime) * random.nextDouble()));
}
public int getPauseTime() {
return getPauseTime(2000, 5500);
}
public void addCharacterListener(CharacterListener cl) {
handler.addCharacterListener(cl);
}
public void removeCharacterListener(CharacterListener cl) {
handler.removeCharacterListener(cl);
}
public void nextCharacter() {
handler.fireNewCharacter(this,
(int) chars[random.nextInt(chars.length)]);
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
nextCharacter();
cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (!done)
cv.signal();
} finally {
lock.unlock();
}
}
}
class FeedbackFrame extends JFrame implements Runnable {
private SwingTypeTester9 stt;
private Thread t;
private JLabel label;
private int state;
static String[] stateMessages = { "Connecting to server...",
"Logging into server...", "Waiting for data...", "Complete" };
public FeedbackFrame(SwingTypeTester9 stt) {
this.stt = stt;
setupFrame();
t = new Thread(this);
t.start();
pack();
show();
}
private void setupFrame() {
label = new JLabel();
label.setPreferredSize(new Dimension(200, 200));
Container c = getContentPane();
JButton stopButton = new JButton("Stop");
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
error();
}
});
c.add(label, BorderLayout.NORTH);
c.add(stopButton, BorderLayout.SOUTH);
}
private void setText(final String s) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
label.setText(s);
}
});
} catch (InterruptedException ie) {
error();
} catch (InvocationTargetException ite) {
error();
}
}
private void error() {
t.interrupt();
if (SwingUtilities.isEventDispatchThread())
closeDown();
else
SwingUtilities.invokeLater(new Runnable() {
public void run() {
closeDown();
}
});
}
private void closeDown() {
stt.setupCancelled();
hide();
dispose();
}
public void run() {
// Simulate connecting to server
for (int i = 0; i < stateMessages.length; i++) {
setText(stateMessages[i]);
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException ie) {
}
if (Thread.currentThread().isInterrupted())
return;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
stt.setupDone();
hide();
dispose();
}
});
}
}
class ScoreLabel extends JLabel implements CharacterListener {
private volatile int score = 0;
private int char2type = -1;
private CharacterSource generator = null, typist = null;
private Lock scoreLock = new ReentrantLock();
public ScoreLabel(CharacterSource generator, CharacterSource typist) {
this.generator = generator;
this.typist = typist;
if (generator != null)
generator.addCharacterListener(this);
if (typist != null)
typist.addCharacterListener(this);
}
public ScoreLabel() {
this(null, null);
}
public void resetGenerator(CharacterSource newGenerator) {
try {
scoreLock.lock();
if (generator != null)
generator.removeCharacterListener(this);
generator = newGenerator;
if (generator != null)
generator.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetTypist(CharacterSource newTypist) {
try {
scoreLock.lock();
if (typist != null)
typist.removeCharacterListener(this);
typist = newTypist;
if (typist != null)
typist.addCharacterListener(this);
} finally {
scoreLock.unlock();
}
}
public void resetScore() {
try {
scoreLock.lock();
score = 0;
char2type = -1;
setScore();
} finally {
scoreLock.unlock();
}
}
private void setScore() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setText(Integer.toString(score));
}
});
}
public void newCharacter(CharacterEvent ce) {
scoreLock.lock();
try {
if (ce.source == generator) {
if (char2type != -1) {
score--;
setScore();
}
char2type = ce.character;
} else {
if (char2type != ce.character) {
score--;
} else {
score++;
char2type = -1;
}
}
setScore();
} finally {
scoreLock.unlock();
}
}
}
class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas implements
CharacterListener, Runnable {
private volatile boolean done = false;
private int curX;
private Lock lock = new ReentrantLock();
private Condition cv = lock.newCondition();
private Thread timer = null;
public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
super(cs);
}
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint();
}
public synchronized void paintComponent(Graphics gc) {
if (tmpChar[0] == 0)
return;
Dimension d = getSize();
int charWidth = fm.charWidth(tmpChar[0]);
gc.clearRect(0, 0, d.width, d.height);
gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
if (curX > d.width - charWidth)
curX = 0;
}
public void run() {
try {
lock.lock();
while (true) {
try {
if (done) {
cv.await();
} else {
repaint();
cv.await(100, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ie) {
return;
}
}
} finally {
lock.unlock();
}
}
public void setDone(boolean b) {
try {
lock.lock();
done = b;
if (timer == null) {
timer = new Thread(this);
timer.start();
}
if (!done)
cv.signal();
} finally {
lock.unlock();
}
}
}
interface CharacterSource {
public void addCharacterListener(CharacterListener cl);
public void removeCharacterListener(CharacterListener cl);
public void nextCharacter();
}
class CharacterEvent {
public CharacterSource source;
public int character;
public CharacterEvent(CharacterSource cs, int c) {
source = cs;
character = c;
}
}
class CharacterEventHandler {
private Vector listeners = new Vector();
public void addCharacterListener(CharacterListener cl) {
listeners.add(cl);
}
public void removeCharacterListener(CharacterListener cl) {
listeners.remove(cl);
}
public void fireNewCharacter(CharacterSource source, int c) {
CharacterEvent ce = new CharacterEvent(source, c);
CharacterListener[] cl = (CharacterListener[]) listeners
.toArray(new CharacterListener[0]);
for (int i = 0; i < cl.length; i++)
cl[i].newCharacter(ce);
}
}
class CharacterDisplayCanvas extends JComponent implements CharacterListener {
protected FontMetrics fm;
protected char[] tmpChar = new char[1];
protected int fontHeight;
public CharacterDisplayCanvas(CharacterSource cs) {
setFont(new Font("Monospaced", Font.BOLD, 18));
fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
fontHeight = fm.getHeight();
cs.addCharacterListener(this);
}
public void setCharacterListener(CharacterSource cs) {
cs.addCharacterListener(this);
}
public Dimension preferredSize() {
return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10);
}
public synchronized void newCharacter(CharacterEvent ce) {
tmpChar[0] = (char) ce.character;
repaint();
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize();
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0)
return;
int charWidth = fm.charWidth((int) tmpChar[0]);
gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight);
}
}
interface CharacterListener {
public void newCharacter(CharacterEvent ce);
}
User interface responsiveness
// : c13:ResponsiveUI.java
// User interface responsiveness.
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
class UnresponsiveUI {
private volatile double d = 1;
public UnresponsiveUI() throws Exception {
while (d > 0)
d = d + (Math.PI + Math.E) / d;
System.in.read(); // Never gets here
}
}
public class ResponsiveUI extends Thread {
private static volatile double d = 1;
public ResponsiveUI() {
setDaemon(true);
start();
}
public void run() {
while (true) {
d = d + (Math.PI + Math.E) / d;
}
}
public static void main(String[] args) throws Exception {
//! new UnresponsiveUI(); // Must kill this process
new ResponsiveUI();
Thread.sleep(300);
System.in.read(); // "monitor" provides input
System.out.println(d); // Shows progress
}
} ///:~
Using the Runnable interface
// : c14:ColorBoxes.java
// Using the Runnable interface.
// <applet code=ColorBoxes width=500 height=400>
// <param name=grid value="12">
// <param name=pause value="50"></applet>
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.util.Random;
import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JPanel;
class CBox extends JPanel implements Runnable {
private Thread t;
private int pause;
private static final Color[] colors = { Color.BLACK, Color.BLUE,
Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN,
Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK,
Color.RED, Color.WHITE, Color.YELLOW };
private static Random rand = new Random();
private static final Color newColor() {
return colors[rand.nextInt(colors.length)];
}
private Color cColor = newColor();
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(cColor);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
public CBox(int pause) {
this.pause = pause;
t = new Thread(this);
t.start();
}
public void run() {
while (true) {
cColor = newColor();
repaint();
try {
t.sleep(pause);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ColorBoxes extends JApplet {
private boolean isApplet = true;
private int grid = 12;
private int pause = 50;
public void init() {
// Get parameters from Web page:
if (isApplet) {
String gsize = getParameter("grid");
if (gsize != null)
grid = Integer.parseInt(gsize);
String pse = getParameter("pause");
if (pse != null)
pause = Integer.parseInt(pse);
}
Container cp = getContentPane();
cp.setLayout(new GridLayout(grid, grid));
for (int i = 0; i < grid * grid; i++)
cp.add(new CBox(pause));
}
public static void main(String[] args) {
ColorBoxes applet = new ColorBoxes();
applet.isApplet = false;
if (args.length > 0)
applet.grid = Integer.parseInt(args[0]);
if (args.length > 1)
applet.pause = Integer.parseInt(args[1]);
run(applet, 500, 400);
}
public static void run(JApplet applet, int width, int height) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(applet);
frame.setSize(width, height);
applet.init();
applet.start();
frame.setVisible(true);
}
} ///:~
Write your Beans this way so they can run in a multithreaded environment
// : c14:BangBean2.java
// You should write your Beans this way so they
// can run in a multithreaded environment.
// From "Thinking in Java, 3rd ed." (c) Bruce Eckel 2002
// www.BruceEckel.ru. See copyright notice in CopyRight.txt.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.Serializable;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BangBean2 extends JPanel implements Serializable {
private int xm, ym;
private int cSize = 20; // Circle size
private String text = "Bang!";
private int fontSize = 48;
private Color tColor = Color.RED;
private ArrayList actionListeners = new ArrayList();
public BangBean2() {
addMouseListener(new ML());
addMouseMotionListener(new MM());
}
public synchronized int getCircleSize() {
return cSize;
}
public synchronized void setCircleSize(int newSize) {
cSize = newSize;
}
public synchronized String getBangText() {
return text;
}
public synchronized void setBangText(String newText) {
text = newText;
}
public synchronized int getFontSize() {
return fontSize;
}
public synchronized void setFontSize(int newSize) {
fontSize = newSize;
}
public synchronized Color getTextColor() {
return tColor;
}
public synchronized void setTextColor(Color newColor) {
tColor = newColor;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.drawOval(xm - cSize / 2, ym - cSize / 2, cSize, cSize);
}
// This is a multicast listener, which is more typically
// used than the unicast approach taken in BangBean.java:
public synchronized void addActionListener(ActionListener l) {
actionListeners.add(l);
}
public synchronized void removeActionListener(ActionListener l) {
actionListeners.remove(l);
}
// Notice this isn"t synchronized:
public void notifyListeners() {
ActionEvent a = new ActionEvent(BangBean2.this,
ActionEvent.ACTION_PERFORMED, null);
ArrayList lv = null;
// Make a shallow copy of the List in case
// someone adds a listener while we"re
// calling listeners:
synchronized (this) {
lv = (ArrayList) actionListeners.clone();
}
// Call all the listener methods:
for (int i = 0; i < lv.size(); i++)
((ActionListener) lv.get(i)).actionPerformed(a);
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
Graphics g = getGraphics();
g.setColor(tColor);
g.setFont(new Font("TimesRoman", Font.BOLD, fontSize));
int width = g.getFontMetrics().stringWidth(text);
g.drawString(text, (getSize().width - width) / 2,
getSize().height / 2);
g.dispose();
notifyListeners();
}
}
class MM extends MouseMotionAdapter {
public void mouseMoved(MouseEvent e) {
xm = e.getX();
ym = e.getY();
repaint();
}
}
public static void main(String[] args) {
BangBean2 bb = new BangBean2();
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("ActionEvent" + e);
}
});
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("BangBean2 action");
}
});
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("More action");
}
});
run(bb, 300, 300);
}
public static void run(JPanel panel, int width, int height) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.setSize(width, height);
frame.setVisible(true);
}
} ///:~