I am trying to code a sorting algorithm visualizer. I put almost everything into one class called visualizer and I was wondering how I can move my "insertionSort" and "bubbleSort" methods into separate classes without breaking my code?
I want to do this because when I integrate merge sort and quick sort I want to keep them in the same class as there helper methods.
public class Visualizer extends JPanel implements ActionListener{
//Initialize Variables
final int SCREEN_WIDTH = 1280;
final int SCREEN_HEIGHT = 720;
final int BAR_HEIGHT = SCREEN_HEIGHT * 4/5;
final int BAR_WIDTH = 5;
final int NUM_BARS = SCREEN_WIDTH/BAR_WIDTH;
JButton bubbleSort = new JButton();
JButton insertSort = new JButton();
SwingWorker<Void,Void> shuffler, sorter;
int[] array = new int[NUM_BARS];
int current;
int traverse;
public Visualizer() {
setBackground(Color.darkGray);
setPreferredSize(new Dimension());
//Initialize Bar Height
for (int i = 0; i < NUM_BARS; i++) {
array[i] = i * BAR_HEIGHT / NUM_BARS;
}
//InsertionSort Button
insertSort.setText("Insertion Sort");
this.add(insertSort);
insertSort.addActionListener(this);
//BubbleSort Button
bubbleSort.setText("Bubble Sort");
this.add(bubbleSort);
bubbleSort.addActionListener(this);
}
public void shuffleArray() {
shuffler = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws InterruptedException {
Random random = new Random();
for (int i = 0; i < NUM_BARS; i++) {
int swap = random.nextInt(NUM_BARS - 1);
swap(i, swap);
Thread.sleep(10);
repaint();
}
return null;
}
#Override
public void done() {
super.done();
sorter.execute();
}
};
shuffler.execute();
}
public void insertionSort() {
sorter = new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() throws InterruptedException {
for (current = 1; current < NUM_BARS; current++) {
traverse = current;
while (traverse > 0 && array[traverse] < array[traverse - 1]) {
swap(traverse, traverse - 1);
traverse--;
Thread.sleep(1);
repaint();
}
}
current = 0;
traverse = 0;
return null;
}
};
}
public void bubbleSort() {
sorter = new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() throws InterruptedException {
for(current = 0; current < NUM_BARS; current++) {
for(traverse = 1; traverse < (NUM_BARS - current); traverse++) {
if(array[traverse-1] > array[traverse]) {
swap(traverse, traverse-1);
traverse--;
Thread.sleep(1);
repaint();
}
}
}
current = 0;
traverse = 0;
return null;
}
};
}
public void quickSort() {
sorter = new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() throws InterruptedException {
return null;
}
};
}
public void swap(int indexOne, int indexTwo) {
int temp = array[indexOne];
array[indexOne] = array[indexTwo];
array[indexTwo] = temp;
}
#Override
public void paintComponent(Graphics g) {
Graphics2D graphics = (Graphics2D)g;
super.paintComponent(graphics);
g.setColor(Color.white);
for(int i = 0; i < NUM_BARS; i++) {
graphics.fillRect(i * BAR_WIDTH,SCREEN_HEIGHT-array[i],BAR_WIDTH,array[i]);
}
g.setColor(Color.green);
graphics.fillRect(current*BAR_WIDTH,SCREEN_HEIGHT-array[current], BAR_WIDTH, array[current]);
g.setColor(Color.red);
graphics.fillRect(traverse*BAR_WIDTH,SCREEN_HEIGHT-array[traverse], BAR_WIDTH, array[traverse]);
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == insertSort) {
insertionSort();
shuffleArray();
}
else if (e.getSource() == bubbleSort) {
bubbleSort();
shuffleArray();
}
}
}
This is not as easy as it might seem, but, the basic answer comes down to making use of models and observers (something like the "model-view-controller" concept), so you can decouple the workflows in a more meaningful way.
One important note to make is, Swing is NOT thread save. This means that you should not be modifying the UI or any state the UI relies on from out side the context the Event Dispatching Thread. Under your current workflow it's possible for the SwingWorker to modify the state of the array (and other state values) while the UI is been painted, this could cause no end of issues.
The core functionality of a sorter is basically the same, it needs some values, needs to be able to swap those values and needs to deliver notifications to interested parties that some kind of state has changed.
public interface Sorter {
public interface Observer {
public void swap(int from, int to);
public void setCurrent(int current);
public void setTraverse(int traverse);
}
public int[] sort(int[] values, Observer observer);
}
Okay, pretty basic, but the nice idea behind this anything that changes the values can be used, for example, we can shuffle the values through the interface...
public class ShuffleSorter extends AbstractSorter {
#Override
public int[] sort(int[] original, Observer observer) {
int[] values = Arrays.copyOf(original, original.length);
Random random = new Random();
for (int i = 0; i < values.length; i++) {
int swap = random.nextInt(values.length - 1);
fireSetCurrent(observer, i);
fireSetTraverse(observer, i);
Helper.swap(values, i, swap);
fireSwap(observer, i, swap);
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
}
}
return values;
}
}
And the insertion sorter...
public class InsertionSorter extends AbstractSorter {
#Override
public int[] sort(int[] original, Observer observer) {
int[] values = Arrays.copyOf(original, original.length);
for (int current = 1; current < values.length; current++) {
int traverse = current;
fireSetCurrent(observer, current);
fireSetTraverse(observer, traverse);
while (traverse > 0 && values[traverse] < values[traverse - 1]) {
Helper.swap(values, traverse, traverse - 1);
fireSwap(observer, traverse, traverse - 1);
traverse--;
fireSetTraverse(observer, traverse);
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
fireSetCurrent(observer, values.length - 1);
fireSetTraverse(observer, values.length - 1);
return values;
}
}
The idea here is the sorters will generate events telling what's changed, so you can apply those changes to the view independently of the sorters, so you don't risk dirty updates.
And finally, the SwingWorker
public class SortWorker extends SwingWorker<Void, Void> {
private Sorter sorter;
private int[] values;
private Sorter.Observer observer;
public SortWorker(int[] values, Sorter sorter, Sorter.Observer observer) {
this.sorter = sorter;
this.values = values;
this.observer = observer;
}
#Override
protected Void doInBackground() throws Exception {
int[] shuffled = new ShuffleSorter().sort(values, observer);
sorter.sort(shuffled, observer);
return null;
}
}
Now, this will shuffle the values and the re-sort them as a single unit of work.
Runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new SortPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class SortPane extends JPanel {
final int SCREEN_WIDTH = 1280;
final int SCREEN_HEIGHT = 720;
final int BAR_HEIGHT = SCREEN_HEIGHT * 4 / 5;
final int BAR_WIDTH = 5;
final int NUM_BARS = SCREEN_WIDTH / BAR_WIDTH;
private JButton bubbleSort = new JButton();
private JButton insertSort = new JButton();
private int[] values = new int[NUM_BARS];
private int current = 0;
private int traverse = 0;
public SortPane() {
for (int i = 0; i < NUM_BARS; i++) {
values[i] = i * BAR_HEIGHT / NUM_BARS;
}
//InsertionSort Button
insertSort.setText("Insertion Sort");
this.add(insertSort);
insertSort.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
insertSort.setEnabled(false);
bubbleSort.setEnabled(false);
current = 0;
traverse = 0;
SortWorker sortWorker = new SortWorker(values, new InsertionSorter(), new Sorter.Observer() {
#Override
public void swap(int from, int to) {
SwingUtilities.invokeLater(() -> {
Helper.swap(values, from, to);
repaint();
});
}
#Override
public void setCurrent(int current) {
SwingUtilities.invokeLater(() -> {
SortPane.this.current = current;
repaint();
});
}
#Override
public void setTraverse(int traverse) {
SwingUtilities.invokeLater(() -> {
SortPane.this.traverse = traverse;
repaint();
});
}
});
sortWorker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (sortWorker.getState() == SwingWorker.StateValue.DONE) {
insertSort.setEnabled(true);
bubbleSort.setEnabled(true);
}
}
});
sortWorker.execute();
}
});
//BubbleSort Button
bubbleSort.setText("Bubble Sort");
this.add(bubbleSort);
bubbleSort.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g.setColor(Color.white);
for (int i = 0; i < values.length; i++) {
g2d.fillRect(i * BAR_WIDTH, SCREEN_HEIGHT - values[i], BAR_WIDTH, values[i]);
}
g2d.setColor(Color.green);
g2d.fillRect(current * BAR_WIDTH, SCREEN_HEIGHT - values[current], BAR_WIDTH, values[current]);
g2d.setColor(Color.red);
g2d.fillRect(traverse * BAR_WIDTH, SCREEN_HEIGHT - values[traverse], BAR_WIDTH, values[traverse]);
g2d.dispose();
}
}
public class Helper {
public static void swap(int[] values, int indexOne, int indexTwo) {
int temp = values[indexOne];
values[indexOne] = values[indexTwo];
values[indexTwo] = temp;
}
}
public interface Sorter {
public interface Observer {
public void swap(int from, int to);
public void setCurrent(int current);
public void setTraverse(int traverse);
}
public int[] sort(int[] values, Observer observer);
}
public abstract class AbstractSorter implements Sorter {
protected void fireSwap(Observer obserer, int from, int to) {
obserer.swap(from, to);
}
protected void fireSetCurrent(Observer obserer, int current) {
obserer.setCurrent(current);
}
protected void fireSetTraverse(Observer obserer, int traverse) {
obserer.setTraverse(traverse);
}
}
public class ShuffleSorter extends AbstractSorter {
#Override
public int[] sort(int[] original, Observer observer) {
int[] values = Arrays.copyOf(original, original.length);
Random random = new Random();
for (int i = 0; i < values.length; i++) {
int swap = random.nextInt(values.length - 1);
fireSetCurrent(observer, i);
fireSetTraverse(observer, i);
Helper.swap(values, i, swap);
fireSwap(observer, i, swap);
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
}
}
return values;
}
}
public class InsertionSorter extends AbstractSorter {
#Override
public int[] sort(int[] original, Observer observer) {
int[] values = Arrays.copyOf(original, original.length);
for (int current = 1; current < values.length; current++) {
int traverse = current;
fireSetCurrent(observer, current);
fireSetTraverse(observer, traverse);
while (traverse > 0 && values[traverse] < values[traverse - 1]) {
Helper.swap(values, traverse, traverse - 1);
fireSwap(observer, traverse, traverse - 1);
traverse--;
fireSetTraverse(observer, traverse);
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
fireSetCurrent(observer, values.length - 1);
fireSetTraverse(observer, values.length - 1);
return values;
}
}
public class SortWorker extends SwingWorker<Void, Void> {
private Sorter sorter;
private int[] values;
private Sorter.Observer observer;
public SortWorker(int[] values, Sorter sorter, Sorter.Observer observer) {
this.sorter = sorter;
this.values = values;
this.observer = observer;
}
#Override
protected Void doInBackground() throws Exception {
int[] shuffled = new ShuffleSorter().sort(values, observer);
sorter.sort(shuffled, observer);
return null;
}
}
}
Food for thought...
A different approach would be to sort the values and in the process generate a series of "events" which describe what actions are been carried out. This would allow you to take the same starting data and apply those actions in a more controlled manner (using a Swing Timer for example). This would reduce some of the overhead of having to copy the array data each time an update occurs.
If you're interested, I've done an example here
An slightly simpler alternative to (though based on an intermediate state of) MadProgrammer's answer - the intention is to use as few custom notification patterns/classes as possible (== zero :)
The collaborators:
an immutable SorterModel to encapsulate the current sort progress (same as intermediate in the other answer)
a custom Worker does the sorting in the background and publishes intermediate sort states as property changes: there's a base class for doing the bookkeeping and notification, subclasses are specialized on a concrete actual (un-) sort algorithm
a custom panel to visualize a SorterModel and logic to trigger a sort and to update itself according to the changes reported from the worker
The Worker is very similar to the intermediate in the other answer, differs in how it publishes its values: here it uses its own property change api to notify interested parties.
#Override
protected void process(List<SorterModel> chunks) {
SorterModel last = chunks.get(chunks.size() - 1);
firePropertyChange("value", null, last);
}
Subclasses simply use the standard mechanism to publish intermediate result
#Override
protected SorterModel doInBackground() throws Exception {
// do sorting step
publish(new SorterModel(<current state>));
..
}
The custom panel is very similar to the other answer, except for the logic to start sorting / update visual state. Now that's extracted (from the button's action listeners) into a separate method to configure a specialized worker, register a property change listener and update the view's model (and related state like button enable) in that listener:
private void process(SortWorker worker, SortWorker next) {
updateEnabled(false);
worker.setModel(getSorterModel());
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("value".equals(evt.getPropertyName())) {
setSorterModel((SorterModel) evt.getNewValue());
}
if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
try {
setSorterModel(worker.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if (next != null) {
process(next, null);
} else {
updateEnabled(true);
}
}
}
});
worker.execute();
}
It's usage from a buttons action might be:
insertSort.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
process(new ShuffleWorker(), new InsertionSortWorker());
}
});
Note:
the sort classes (worker/model) are unaware of who is using them and do not require a custom observer/n
the sort trigger/update logic is decoupled from the view - all it needs is a source/target for the model and some enablement toggle. It still resides in the view, though - being lazy, and we had to subclass anyway for custom painting.
Pulling all together into a runnable example:
package so.notfx.swing;
public class SortAnimation {
public class SortPane extends JPanel {
final int SCREEN_WIDTH = 800;
final int SCREEN_HEIGHT = 600;
final int BAR_HEIGHT = SCREEN_HEIGHT * 4 / 5;
final int BAR_WIDTH = 5;
final int NUM_BARS = SCREEN_WIDTH / BAR_WIDTH;
private JButton shuffle = new JButton();
private JButton bubbleSort = new JButton();
private JButton insertSort = new JButton();
private int[] values = new int[NUM_BARS];
private SorterModel model;
public SortPane() {
for (int i = 0; i < NUM_BARS; i++) {
values[i] = i * BAR_HEIGHT / NUM_BARS;
}
model = new SorterModel(values, 0, 0);
// shuffle button to trigger random values
shuffle.setText("Shuffle Values");
add(shuffle);
shuffle.addActionListener(e -> {
process(new ShuffleWorker(), null);
});
// InsertionSort Button
insertSort.setText("Insertion Sort");
this.add(insertSort);
insertSort.addActionListener(e -> {
process(new ShuffleWorker(), new InsertionSortWorker());
});
// BubbleSort Button
bubbleSort.setText("Bubble Sort");
this.add(bubbleSort);
bubbleSort.addActionListener(e -> {
// NYI
});
}
//--------------- sort related
public void setSorterModel(SorterModel model) {
this.model = model;
repaint();
}
public SorterModel getSorterModel() {
return model;
}
private void updateEnabled(boolean enabled) {
shuffle.setEnabled(enabled);
insertSort.setEnabled(enabled);
bubbleSort.setEnabled(enabled);
}
private void process(SortWorker worker, SortWorker next) {
updateEnabled(false);
worker.setModel(getSorterModel());
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("value".equals(evt.getPropertyName())) {
setSorterModel((SorterModel) evt.getNewValue());
}
if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
try {
setSorterModel(worker.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if (next != null) {
process(next, null);
} else {
updateEnabled(true);
}
}
}
});
worker.execute();
}
//------------------------
#Override
public Dimension getPreferredSize() {
return new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g.setColor(Color.white);
for (int i = 0; i < model.length(); i++) {
g2d.fillRect(i * BAR_WIDTH, SCREEN_HEIGHT - model.valueAt(i), BAR_WIDTH, model.valueAt(i));
}
int current = model.getCurrent();
g2d.setColor(Color.green);
g2d.fillRect(current * BAR_WIDTH, SCREEN_HEIGHT - model.valueAt(current), BAR_WIDTH,
model.valueAt(current));
int traverse = model.getTraverse();
g2d.setColor(Color.red);
g2d.fillRect(traverse * BAR_WIDTH, SCREEN_HEIGHT - model.valueAt(traverse), BAR_WIDTH,
model.valueAt(traverse));
g2d.dispose();
}
}
public static void main(String[] args) {
new SortAnimation();
}
public SortAnimation() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new SortPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
which is using the support class:
package so.notfx.swing;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import javax.swing.SwingWorker;
/**
* Utility class for sort animation.
* Contains a SorterModel with current sort state and workers that are doing
* the sorting on the background thread and publishing intermediate results
* as property changes with name "value".
*/
public class SortAnimationSupport {
/**
* Base class: bookkeeping, helper and publish a property change for its value. Subclasses are supposed to
* implement the actual sorting in the background and publish a new SorterModel with the current sort
* state when appropriate.
*/
public static abstract class SortWorker extends SwingWorker<SorterModel, SorterModel> {
int[] values;
public void setModel(SorterModel model) {
if (getState() != StateValue.PENDING)
throw new IllegalStateException("model must not be modified after starting the worker");
values = Arrays.copyOf(model.getValues(), model.getValues().length);
}
#Override
protected void process(List<SorterModel> chunks) {
SorterModel last = chunks.get(chunks.size() - 1);
firePropertyChange("value", null, last);
}
protected void swap(int indexOne, int indexTwo) {
int temp = values[indexOne];
values[indexOne] = values[indexTwo];
values[indexTwo] = temp;
}
}
/**
* Use insertion sort.
*/
public static class InsertionSortWorker extends SortWorker {
#Override
protected SorterModel doInBackground() throws Exception {
for (int current = 1; current < values.length; current++) {
int traverse = current;
while (traverse > 0 && values[traverse] < values[traverse - 1]) {
swap(traverse, traverse - 1);
traverse--;
publish(new SorterModel(values, current, traverse));
Thread.sleep(5);
}
}
SorterModel model = new SorterModel(values, values.length - 1, values.length - 1);
publish(model);
return model;
}
}
/**
* Unsort.
*/
public static class ShuffleWorker extends SortWorker {
#Override
protected SorterModel doInBackground() throws Exception {
Random random = new Random();
for (int i = 0; i < values.length; i++) {
int swap = random.nextInt(values.length - 1);
swap(i, swap);
publish(new SorterModel(values, i, 0));
Thread.sleep(5);
}
SorterModel model = new SorterModel(values, values.length - 1, 0);
publish(model);
return model;
}
}
/**
* SorterModel: encapsulates the state of a sort process.
* Note: it has to keep its values immutable, so copying the array
*/
public static class SorterModel {
protected int[] values;
protected int current;
protected int traverse;
public SorterModel(int[] array, int current, int traverse) {
this.values = Arrays.copyOf(array, array.length);
this.current = current;
this.traverse = traverse;
}
public int[] getValues() {
return Arrays.copyOf(values, values.length);
}
public int length() {
return values.length;
}
public int valueAt(int index) {
return values[index];
}
public int getCurrent() {
return current;
}
public int getTraverse() {
return traverse;
}
}
private SortAnimationSupport() {
}
}
Related
While developing a small task manager, I have noticed that columns aren't sorted correctly. To discard problems with my program, I have created a minimal version but it still fails to order the unique column right.
import java.awt.BorderLayout;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
public class TableSortTest extends JFrame
{
private final JTable table;
private final ATableModel model;
public TableSortTest ()
{
setDefaultCloseOperation (EXIT_ON_CLOSE);
setSize (1366, 768);
setLocationRelativeTo (null);
model = new ATableModel ();
table = new JTable ();
table.setFillsViewportHeight (true);
table.setAutoCreateRowSorter (true);
table.setModel (model);
add (new JScrollPane (table), BorderLayout.CENTER);
setVisible (true);
Worker worker = new Worker ();
worker.execute ();
}
private class Pair
{
int index;
int value;
}
private class Worker extends SwingWorker <Void, Pair>
{
#Override
protected Void doInBackground ()
{
while (!isCancelled ())
{
Random r = new Random ();
for (int i = 0; i < 100; i++)
{
int indice = getIndexInRange (0, 99);
Pair p = new Pair ();
p.index = indice;
p.value = Math.abs (r.nextInt ());
publish (p);
}
try
{
Thread.sleep (1000);
}
catch (InterruptedException ie)
{
ie.printStackTrace ();
}
}
return null;
}
#Override
public void process (List <Pair> items)
{
for (Pair p : items)
{
model.setValueAt (p.value, p.index, 0);
}
}
}
public static int getIndexInRange (int min, int max)
{
return (min + (int) (Math.random () * ((max - min) + 1)));
}
private class ATableModel extends AbstractTableModel
{
private final Integer [] data;
public ATableModel ()
{
data = new Integer [100];
Random r = new Random ();
for (int i = 0; i < 100; i++)
{
data [i] = Math.abs (r.nextInt ());
}
}
#Override
public int getColumnCount ()
{
return 1;
}
#Override
public int getRowCount ()
{
return data.length;
}
#Override
public Object getValueAt (int rowIndex, int columnIndex)
{
return data [rowIndex];
}
#Override
public void setValueAt (Object value, int rowIndex, int columnIndex)
{
data [rowIndex] = (Integer) value;
fireTableRowUpdated (rowIndex, columnIndex);
}
#Override
public Class getColumnClass (int columnIndex)
{
return Integer.class;
}
#Override
public String getColumnName (int col)
{
return "Column";
}
}
public static final void main (String [] args)
{
SwingUtilities.invokeLater (() ->
{
try
{
new TableSortTest ();
}
catch (Exception e)
{
e.printStackTrace ();
}
});
}
}
I have tried with a ScheduledExecutorService + Runnable and a Timer + TimerTask just to test if it was a threading problem, but the behavior is the same. I have also read the Java Tutorial page about the subject. Given that my table only uses standard types I think that a simple table.setAutoCreateRowSorter (true); should do the job, shouldn't it?
Shouldn't the table be sorted after every modification/addition/removal even is fired?
Thanks for your quick answer trashgod. You're right, I meant fireTableRowsUpdated () but I made a mistake when I wrote the code, sorry. The point is that fireTableRowsUpdated (rowIndex, rowIndex) and fireTableCellUpdated (rowIndex, columnIndex) both fail to sort the column correctly. In the real program most of the table rows do change from one iteration to the next so calling fireTableDataChanged () makes perfect sense. But I didn't want to use it because if I select one or more rows to send a signal to the processes or whatever the selection is lost on every update. I have explored this way and found two forms of preserving the selection but it's a bit annoying and one of them breaks the selection with the keyboard. I show the necessary additions to the original code next.
The first form saves the selection before modifying the model and restores it after every update:
...
private class Worker extends SwingWorker <Void, Pair>
{
private int [] selectedRows;
#Override
protected Void doInBackground ()
{
while (!isCancelled ())
{
// Save the selection before modifying the model
int x = table.getSelectedRowCount ();
if (x > 0)
{
selectedRows = new int [x];
int [] tableSelection = table.getSelectedRows ();
for (int i = 0; i < x; i++)
{
selectedRows [i] = table.convertRowIndexToModel (tableSelection [i]);
}
}
Random r = new Random ();
for (int i = 0; i < table.getRowCount (); i++)
{
int indice = getIndexInRange (0, table.getRowCount () - 1);
Pair p = new Pair ();
p.index = indice;
p.value = Math.abs (r.nextInt ());
publish (p);
}
// If I put the code to restore the selection here, it doesn't work...
try
{
Thread.sleep (1000);
}
catch (InterruptedException ie)
{
ie.printStackTrace ();
}
}
return null;
}
#Override
public void process (List <Pair> items)
{
for (Pair p : items)
{
model.setValueAt (p.value, p.index, 1);
}
// Restore the selection on every update
if (selectedRows != null && selectedRows.length > 0)
{
for (int i = 0; i < selectedRows.length; i++)
{
table.addRowSelectionInterval (table.convertRowIndexToView (selectedRows [i]), table.convertRowIndexToView (selectedRows [i]));
}
}
}
}
...
The second form uses a ListSelectionListener, a KeyListener, and a flag. Selection with the keyboard doesn't work. To be honest, I don't know how did I come to get this solution. It probably was by chance:
public class TableSortTestSolucionConSelectionListener extends JFrame implements KeyListener
{
...
private boolean ctrlOrShiftDown = false;
private int [] selectedRows;
#Override
public void keyPressed (KeyEvent e)
{
ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
}
#Override
public void keyReleased (KeyEvent e)
{
ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
}
#Override
public void keyTyped (KeyEvent e)
{
ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
}
public TableSortTestSolucionConSelectionListener ()
{
...
ListSelectionListener lsl = new ListSelectionListener ()
{
#Override
public void valueChanged (ListSelectionEvent e)
{
if (!e.getValueIsAdjusting ())
{
if (!ctrlOrShiftDown)
{
int x = table.getSelectedRowCount ();
if (x > 0)
{
selectedRows = new int [x];
int [] tableSelection = table.getSelectedRows ();
for (int i = 0; i < x; i++)
{
selectedRows [i] = table.convertRowIndexToModel (tableSelection [i]);
}
}
}
// Disable the listener to avoid infinite recursion
table.getSelectionModel ().removeListSelectionListener (this);
if (selectedRows != null && selectedRows.length > 0)
{
for (int i = 0; i < selectedRows.length; i++)
{
table.addRowSelectionInterval (table.convertRowIndexToView (selectedRows [i]), table.convertRowIndexToView (selectedRows [i]));
}
}
table.getSelectionModel ().addListSelectionListener (this);
}
}
};
table.getSelectionModel ().addListSelectionListener (lsl);
...
}
Fortunately today I have found a simple way to get the column sorted correctly and keep the current selection. You only have to add the following to your code:
TableRowSorter trs = (TableRowSorter) table.getRowSorter ();
trs.setSortsOnUpdates (true);
With this both fireTableCellUpdated () and fireTableRowsUpdated () work as I expected. To my understanding, setAutoCreateRowSorter () is only used to sort the rows when you click on the table header.
Greetings.
Using setSortsOnUpdates(), suggested here by #trcs, is the best general solution, but you may be able to optimize updates by the choice of TableModelEvent available to subclasses of AbstractTableModel.
The critical issue is the implementation of setValueAt(). If you meant fireTableRowsUpdated(), instead of fireTableRowUpdated(), note that the parameters represent a range of rows, not a row & column. In this case, because "all cell values in the table's rows may have changed," the revised example below invokes fireTableDataChanged(). I've also changed the model to manage a List<Integer> and normalized the size, N.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
/** #see https://stackoverflow.com/a/36522182/230513 */
public class TableSortTest extends JFrame {
private final JTable table;
private final ATableModel model;
public TableSortTest() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
model = new ATableModel();
table = new JTable(model){
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(200, 500);
}
};
table.setFillsViewportHeight(true);
table.setAutoCreateRowSorter(true);
add(new JScrollPane(table), BorderLayout.CENTER);
pack();
setLocationRelativeTo(null);
setVisible(true);
Worker worker = new Worker();
worker.execute();
}
private class Pair {
int index;
int value;
}
private class Worker extends SwingWorker<Void, Pair> {
private static final int N = 100;
private final Random r = new Random();
#Override
protected Void doInBackground() {
while (!isCancelled()) {
for (int i = 0; i < N; i++) {
int index = r.nextInt(N);
Pair p = new Pair();
p.index = index;
p.value = Math.abs(r.nextInt());
publish(p);
}
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
return null;
}
#Override
public void process(List<Pair> items) {
for (Pair p : items) {
model.setValueAt(p.value, p.index, 0);
}
}
}
private class ATableModel extends AbstractTableModel {
private static final int N = 100;
private final List<Integer> data = new ArrayList<>(N);
public ATableModel() {
final Random r = new Random();
for (int i = 0; i < N; i++) {
data.add(Math.abs(r.nextInt()));
}
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return data.get(rowIndex);
}
#Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
data.set(rowIndex, (Integer) value);
fireTableDataChanged();
}
#Override
public Class getColumnClass(int columnIndex) {
return Integer.class;
}
#Override
public String getColumnName(int col) {
return "Column";
}
}
public static final void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new TableSortTest();
});
}
}
Recognizing that this is just an example, the variation below optimizes updates by publishing a List<Integer>, which is passed en bloc to the TableModel via process().
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
/**
* # see https://stackoverflow.com/a/36522182/230513
*/
public class TableSortTest extends JFrame {
private final JTable table;
private final ATableModel model;
public TableSortTest() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
model = new ATableModel();
table = new JTable(model) {
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(200, 500);
}
};
table.setFillsViewportHeight(true);
table.setAutoCreateRowSorter(true);
add(new JScrollPane(table), BorderLayout.CENTER);
pack();
setLocationRelativeTo(null);
setVisible(true);
Worker worker = new Worker();
worker.execute();
}
private class Worker extends SwingWorker<List<Integer>, List<Integer>> {
private static final int N = 100;
private final Random r = new Random();
private final List<Integer> data = new ArrayList<>(N);
#Override
protected List<Integer> doInBackground() throws Exception {
while (!isCancelled()) {
data.clear();
for (int i = 0; i < N; i++) {
data.add(Math.abs(r.nextInt()));
}
publish(data);
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace(System.err);
}
}
return data;
}
#Override
protected void process(List<List<Integer>> chunks) {
for (List<Integer> chunk : chunks) {
model.update(chunk);
}
}
}
private class ATableModel extends AbstractTableModel {
private List<Integer> data = new ArrayList<>();
public void update(List<Integer> data) {
this.data = data;
fireTableDataChanged();
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return data.get(rowIndex);
}
#Override
public Class getColumnClass(int columnIndex) {
return Integer.class;
}
#Override
public String getColumnName(int col) {
return "Column";
}
}
public static final void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new TableSortTest();
});
}
}
I want to do a sorting animation. I've got a JFrame with GridLayout(1,3) because I have 3 sorting alghorithms. In each cell I have a JPanel with BorderLayout(). In the center of the JPanel I have the numbers and for each number a stick with different widths(I placed them horizontally). For each type of sorting I need a thread to call them. So I created class SortThread which does my sorting (it extends JThread). Each pair of numbers and sticks are painted on the center panel with class StickNumber which extends JLabel. The problem is when I start the thread. The repaint() method doesn't seem to work.
Here is class SortThread:
public class SortThread extends Thread{
private JPanel sortPanel;
private StickNumber[] listOfNumbers;
public SortThread(JPanel sortPanel)
{
this.sortPanel = sortPanel;
Component[] components = sortPanel.getComponents();
listOfNumbers = new StickNumber[components.length];
for(int i = 0; i<components.length; i++)
listOfNumbers[i] = (StickNumber) components[i];
}
public SortThread(){}
public void bubbleSort()
{
boolean swapped = true;
int j = 0;
StickNumber tmp;
while (swapped) {
swapped = false;
j++;
for (int i = 0; i < listOfNumbers.length - j; i++) {
if (listOfNumbers[i].getValue() > listOfNumbers[i+1].getValue()) {
tmp = listOfNumbers[i];
listOfNumbers[i]=listOfNumbers[i+1];
listOfNumbers[i+1]=tmp;
swapped = true;
sortPanel.validate();
sortPanel.repaint();
}
}
}
}
public void run(){
bubbleSort();
}
Here is the listener for my button which starts the thread:
ActionListener sortListener = new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
if(sortFlag == false)
{
sortFlag = true;
SortThread thread1= new SortThread(centerPanel1);
thread1.start();
appFrame.validate();
appFrame.repaint();
}
}
};
sortButton.addActionListener(sortListener);
StickNumber class:
private void doDrawing(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
String number = Integer.toString(value);
if(dimension!=0)
setText(number);
g2.setStroke(new BasicStroke(5));
g2.draw(new Line2D.Float(dimension*10, 5, height+dimension*10, 5));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
Using SwingWorker, assuming your code does what it should and your problems is the EDT is blocked:
import java.awt.Component;
import java.util.List;
import javax.swing.*;
public class SortSwingWorker extends SwingWorker<Object, Object> {
private JPanel sortPanel;
private StickNumber[] listOfNumbers;
public SortSwingWorker(JPanel sortPanel) {
this.sortPanel = sortPanel;
Component[] components = sortPanel.getComponents();
listOfNumbers = new StickNumber[components.length];
for (int i = 0; i < components.length; i++) {
listOfNumbers[i] = (StickNumber) components[i];
}
}
#Override
protected Object doInBackground() throws Exception {
boolean swapped = true;
int j = 0;
StickNumber tmp;
while (swapped) {
swapped = false;
j++;
for (int i = 0; i < listOfNumbers.length - j; i++) {
if (listOfNumbers[i].getValue() > listOfNumbers[i + 1].getValue()) {
tmp = listOfNumbers[i];
listOfNumbers[i] = listOfNumbers[i + 1];
listOfNumbers[i + 1] = tmp;
swapped = true;
publish(null);
}
}
}
return null;
}
#Override
protected void process(List<Object> list) {
sortPanel.validate();
sortPanel.repaint();
}
//dummy class
private class StickNumber extends Component {
public Integer getValue() {
return null;
}
}
}
I'm found a solution, only on Bprolog, and ask for a help
how to translate it by JPL on SWI PROLOG? OR maybe you can take me solution by jpl libriry using
// by Nobukuni Kino
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import bprolog.plc.Plc;
public
class Queens extends Applet implements Runnable {
static int nqueens = 8;
/* Run as an application */
public static void main(String args[]) {
Frame f = new Frame("Queens");
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}});
Queens qb = new Queens();
qb.init();
f.add("Center",qb);
f.setSize(321,321);
f.show();
qb.run1();
System.exit(0);
}
int w,h;
Color pieceColor = new Color(255,150,150);
Thread runner;
public void start() {
if (runner == null) {
runner = new Thread(this);
System.out.println("Start");
runner.start();
} else {
runner.resume();
}
}
public void stop() {
if (runner != null) {
runner.suspend();
}
}
public void run(){}
public void run1() {
Plc.startPlc(new String []{});
Integer[] queens = new Integer[nqueens];
for (int i=0; i<nqueens; i++) queens[i] = new Integer(i+1);
Plc goal = new Plc("callQueens", new Object[] {queens,this});
Plc.exec("load('queens')");
goal.call();
}
public void paint(Graphics g) {
board(g);
}
public void update(Graphics g) {
}
public void board(Graphics g) {
w = (getSize().width-1)/nqueens;
h = (getSize().height-1)/nqueens;
g.setColor(Color.black);
g.drawRect(0, 0, nqueens*w+1, nqueens*h+1);
for (int i = 1; i <= nqueens; i++) {
for (int j = 1; j <= nqueens; j++) {
clearSquare(i,j);
}
}
}
public void putSquare(Integer row, Integer col) {
putSquare(row.intValue(), col.intValue());
}
public void putSquare(int row, int col) {
Graphics g = getGraphics();
g.setColor(pieceColor);
g.fillRect(w*(row-1)+1, h*(col-1)+1, w, h);
Thread.yield();
}
public void clearSquare(Integer row, Integer col) {
clearSquare(row.intValue(), col.intValue());
}
public void clearSquare(int row, int col) {
Graphics g = getGraphics();
if ((row+col)%2 == 1) {
g.setColor(Color.black);
}
else {
g.setColor(Color.white);
}
g.fillRect(w*(row-1)+1, h*(col-1)+1, w, h);
Thread.yield();
}
public void sleep(Integer mill) {
try {
Thread.sleep(mill.intValue(),0);
} catch(InterruptedException e) {}
}
}
:-module queens.
:-public queens/2.
draw(M,N):-
global_get(board,Qb),
javaMethod(Qb,putSquare(M,N)).
draw(M,N):-
global_get(board,Qb),
javaMethod(Qb,clearSquare(M,N)),
fail.
callQueens(Q,Qb):-
cputime(Start),
queens(Q,Qb),
cputime(End),
T is End-Start,
write(executionTime(T)),nl,
statistics.
queens(Q,Qb):-
javaMethod(Qb,sleep(500)),
global_set(board,Qb),
put(Q,[],R),write(R),nl,
javaMethod(Qb,sleep(1000)),
fail.
queens(Q,Qb):-
global_set(board,[]). % Qb is not valid after return to Java
put([Q1|Qs],Board,Result):-!,
sel([Q1|Qs],Q,Rs),
safe(Board,Q,Q),
length(Qs,L),N is 1+L,
draw(Q,N),
put(Rs,[Q|Board],Result).
put([],Result,Result).
safe([Q|Rs],P,M):-!,
PP is P+1,
Q\==PP,
MM is M-1,
Q\==MM,
safe(Rs,PP,MM).
safe([],P,M).
sel([X|Y],X,Y).
sel([X|Y],Z,[X|W]):-sel(Y,Z,W).
A literal translation is probably not worth the effort. The code you show encodes a very specific search strategy which is extremely difficult to modify. The actual logic and side effects are completely interwoven.
However, there is an animation for library(clpfd) of SWI which is far more interesting. There, it is possible to exchange the labeling strategies directly. So you can see why simple naive labeling turns out to be ineffective. Equally, you can add your own strategy, without any modification of the viewer's code.
Invoke javax.swing.Timer#start() same time,
7u25 is not problem.
but 7u40 is big problem.
Too laggy invoke ActionListener#actionPerformed. (basically same time invoke u25)
Totally different move between u25 and u40. (I use Windows 8)
I bug report but still not add bug tracking system. Oracle crushing swing apps?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TimerProblem extends JComponent {
int red = 0;
TimerProblem(final long startMs) {
setPreferredSize(new Dimension(10, 10));
Timer t = new Timer(16, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
red = (int)(System.currentTimeMillis() - startMs) % 255;
repaint();
}
});
t.setInitialDelay(1000);
t.start();
}
#Override
protected void paintComponent(Graphics g) {
g.setColor(new Color(red, 255 - red, 0));
g.fillRect(0, 0, getWidth(), getHeight());
}
public static void main(String[] args) {
JFrame f = new JFrame();
Container c = f.getContentPane();
c.setLayout(new GridLayout(10, 10));
long startMs = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
c.add(new TimerProblem(startMs));
}
f.pack();
f.setVisible(true);
}
}
Several issues arise in your example:
Swing GUI objects should be constructed and manipulated only on the event dispatch thread.
All Swing Timer instances share a common thread, which is being saturated.
Depending on the goal, some alternatives are possible:
Use a single Timer instance, and select some fraction for update at a proportionally higher rate. The example below randomly selects N of the components and updates them every 100 ms.
Use TexturePaint, as shown here.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.*;
/** #see https://stackoverflow.com/a/18936444/230513 */
public class BlinkenLights {
private static final int S = 24;
private static final int N = 10;
private static final Random r = new Random();
private static final List<MyComponent> list = new ArrayList<MyComponent>();
private static final class MyComponent extends JComponent {
public MyComponent() {
this.setOpaque(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(S, S);
}
#Override
protected void paintComponent(Graphics g) {
g.setColor(Color.getHSBColor(r.nextFloat() / 6, 1, 1));
g.fillRect(0, 0, getWidth(), getHeight());
}
}
private static JPanel createPanel() {
final JPanel p = new JPanel();
p.setLayout(new GridLayout(N, N));
for (int i = 0; i < N * N; i++) {
MyComponent c = new MyComponent();
p.add(c);
list.add(c);
}
Timer t = new Timer(1000 / N, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Collections.shuffle(list, r);
for (int i = 0; i < N; i++) {
list.get(i).repaint();
}
}
});
t.start();
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(createPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}
Finally I write DIY repaint management class .. :(
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
/**
* EffectTimer
*/
public class EffectTimer {
/**
* All of effect timers in instance of this class.
*/
static class GlobalTimer implements ActionListener {
List<EffectTimer> registeredEffects = new ArrayList<>();
Timer timer = new Timer(16, this);
public void start(final EffectTimer t) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
internalStart(t);
}
});
}
void internalStart(EffectTimer t) {
int initialDelay = Math.max(0, (int) (t.getEffectStartTime() - System.currentTimeMillis()));
if(timer.getInitialDelay() >= initialDelay) {
timer.setInitialDelay(initialDelay);
}
if(!registeredEffects.contains(t)) {
registeredEffects.add(t);
if(registeredEffects.size() == 1) {
timer.start();
}
}
}
void stop(final EffectTimer t) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
registeredEffects.remove(t);
checkStop();
}
});
}
#Override
public void actionPerformed(ActionEvent e) {
long now = e.getWhen();
Iterator<EffectTimer> iter = registeredEffects.iterator();
while(iter.hasNext()) {
EffectTimer t = iter.next();
long elapsedMs = now - t.getEffectStartTime();
if(elapsedMs > 0) {
float p = elapsedMs / (float)t.getEffectLengthMs();
if(p >= 1.0f) {
iter.remove();
t.stop();
} else {
if(t.isReversed()) {
p = 1.0f - p;
}
t.progressChanged(p);
}
}
}
checkStop();
}
void checkStop() {
if(registeredEffects.isEmpty()) {
timer.stop();
}
}
public int getRunningTimerCount() {
return registeredEffects.size();
}
public void stopAll() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
registeredEffects.clear();
checkStop();
}
});
}
}
static final GlobalTimer GTIMER = new GlobalTimer();
int effectLengthMs = -1;
long effectStartMs = -1;
float progress = 0.0f;
boolean reversed = true;
public long getEffectStartTime() {
return effectStartMs;
}
public int getEffectLengthMs() {
return effectLengthMs;
}
public void start(int lengthMs) {
start(lengthMs, System.currentTimeMillis());
}
public void start(int lengthMs, long startMs) {
effectLengthMs = lengthMs;
effectStartMs = startMs;
reversed = false;
progress = 0.0f;
GTIMER.start(this);
}
public boolean isReversed() {
return reversed;
}
public void reverse(final int lengthMs) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
internalReverse(lengthMs);
}
});
}
void internalReverse(int lengthMs) {
reversed = !reversed;
effectLengthMs = lengthMs;
int adjust = reversed ? (int)(lengthMs * (1.0f - progress)) : (int)(lengthMs * progress);
effectStartMs = System.currentTimeMillis() - adjust;
GTIMER.start(this);
}
final public void progressChanged(float p) {
progress = p;
run(p);
}
/**
* 0.0f to 1.0f effect progress.
* <code>Float.compare(progress, 1.0f) >= 0</code> to end progress.
*/
protected void run(float p) {}
public void stop() {
progress = reversed ? 0.0f : 1.0f;
GTIMER.stop(this);
}
public boolean isRunning() {
return 0.0f < progress && progress < 1.0f;
}
public float getProgress() {
return progress;
}
public static int getRunningTimerCount() {
return GTIMER.getRunningTimerCount();
}
public static void stopAll() {
GTIMER.stopAll();
}
}
My program visually demonstrates a sequential version of the well known QuickSort algorithm, with two new visual demonstrations: (I) a parallel version of QuickSort, implemented using low level Thread API and SwingUtilities, and (II) a parallel version of QuickSort, implemented using SwingWorker API.
I am trying to have a facility to restart the program after a successful run. Currently, the buttons are disabled when the sorting operation starts, which is correct, but they never re-enable and so I was wondering if there is a way to enable all the buttons after the successful run? Some of the code is as follows:
// http://www.java2s.com/Code/Java/Collections-Data-Structure/Animationforquicksort.htm
// http://www.sorting-algorithms.com/quick-sort
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.util.*;
import javax.swing.*;
import java.util.concurrent.atomic.*;
public class QuickSortVisualizer implements Runnable {
static int LENGTH = 32;
static int LEFT = 500;
static int RIGHT = 500;
static int SWAP = 1000;
int[] Values;
AtomicInteger WorkerThreads = new AtomicInteger();
public static void main(String[] args) {
try {
if (args.length == 0) {
LENGTH = 32;
LEFT = 500;
RIGHT = 500;
SWAP = 1000;
} else if (args.length == 4) {
//dw about this
} else {
throw new Exception("incorrect command-line argument count");
}
System.err.format("... LENGTH=%d LEFT=%d RIGHT=%d SWAP=%d%n", LENGTH, LEFT, RIGHT, SWAP);
SwingUtilities.invokeAndWait(new QuickSortVisualizer());
System.err.format("... GUI started%n");
} catch (Exception ex) {
System.err.format("*** %s%n", ex.getMessage());
}
}
JButton BoredButton;
JButton WorkerButtonSequential;
JButton WorkerButtonThreads;
JButton WorkerButtonSwingWorkers;
SorterPanel MySortPanel;
JLabel StatusBar;
public void run() {
JFrame frame = new JFrame();
frame.setTitle("My Quick Sort Visualizer");
Font font = new Font("Monospaced", Font.BOLD, 18);
BoredButton = new JButton("I am bored");
BoredButton.setFont(font);
BoredButton.setPreferredSize(new Dimension(180, 30));
BoredButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
BoredButton_Click();
}
});
WorkerButtonSequential = new JButton("QS Sequential");
WorkerButtonSequential.setFont(font);
WorkerButtonSequential.setPreferredSize(new Dimension(185, 30));
WorkerButtonSequential.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
WorkerButtonSequential_Click();
}
});
WorkerButtonThreads = new JButton("QS Threads");
WorkerButtonThreads.setFont(font);
WorkerButtonThreads.setPreferredSize(new Dimension(185, 30));
WorkerButtonThreads.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
WorkerButtonThreads_Click();
}
});
WorkerButtonSwingWorkers = new JButton("QS SwingWorkers");
WorkerButtonSwingWorkers.setFont(font);
WorkerButtonSwingWorkers.setPreferredSize(new Dimension(200, 30));
WorkerButtonSwingWorkers.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
WorkerButtonSwingWorkers_Click();
}
});
JPanel strip = new JPanel(new FlowLayout(FlowLayout.CENTER));
strip.add(BoredButton);
strip.add(WorkerButtonSequential);
strip.add(WorkerButtonThreads);
strip.add(WorkerButtonSwingWorkers);
frame.getContentPane().add(strip, BorderLayout.NORTH);
StatusBar = new JLabel();
StatusBar.setFont(font);
StatusBar.setPreferredSize(new Dimension(800, 20));
frame.getContentPane().add(StatusBar, BorderLayout.SOUTH);
MySortPanel = new SorterPanel();
frame.getContentPane().add(MySortPanel, BorderLayout.CENTER);
frame.getRootPane().setDefaultButton(BoredButton);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 400);
frame.setResizable(true);
frame.setVisible(true);
}
public void BoredButton_Click() {
String text = Calendar.getInstance().getTime().toString();
StatusBar.setText(text);
System.err.format("... now %s%n", text);
}
public void WorkerButtonSequential_Click() {
WorkerButtonSequential.setEnabled(false);
WorkerButtonThreads.setEnabled(false);
WorkerButtonSwingWorkers.setEnabled(false);
System.err.format("... sequential%n");
QSSequential();
}
public void WorkerButtonThreads_Click() {
WorkerButtonSequential.setEnabled(false);
WorkerButtonThreads.setEnabled(false);
WorkerButtonSwingWorkers.setEnabled(false);
int processors = Runtime.getRuntime().availableProcessors();
int threshold = processors * 2;
System.err.format("... parallel threads: processors=%d threshold=%d%n", processors, threshold);
QSThreads(threshold);
}
public void WorkerButtonSwingWorkers_Click() {
WorkerButtonSequential.setEnabled(false);
WorkerButtonThreads.setEnabled(false);
WorkerButtonSwingWorkers.setEnabled(false);
int processors = Runtime.getRuntime().availableProcessors();
int threshold = processors * 2;
System.err.format("... parallel swingworkers: processors=%d threshold=%d%n", processors, threshold);
QSSwingWorkers(threshold);
}
void QSInit() {
Values = new int[LENGTH];
for (int i = 0; i < Values.length; i++) {
Values[i] = (int)Math.round(Math.random() * (MySortPanel.getHeight()-10));
}
print("... initial values");
MySortPanel.setValues(Values);
}
void QSSequential() {
QSInit();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
QuickSortSequential qss = new QuickSortSequential(Values, 0, Values.length - 1, MySortPanel);
System.err.format("... started%n");
qss.run();
DoneAll();
}
});
}
void QSThreads(int threshold) {
QSInit();
QuickSortThread qst = new QuickSortThread(Values, 0, Values.length - 1, threshold, MySortPanel);
WorkerThreads.set(0);
incWorkerThreads();
System.err.format("... started%n");
qst.start();
}
void QSSwingWorkers(int threshold) {
QSInit();
QuickSortWorker qsw = new QuickSortWorker(Values, 0, Values.length - 1, threshold, MySortPanel);
WorkerThreads.set(0);
incWorkerThreads();
System.err.format("... started%n");
qsw.execute();
}
void print(String caption) {
System.err.format("%s%n", caption);
for (int i=0; i<Values.length; i++) {
System.err.format(" %d:%d", i, Values[i]);
}
System.err.format("%n");
}
void incWorkerThreads() {
int w = WorkerThreads.incrementAndGet();
System.err.format("... workers=%d%n", w);
}
void decWorkerThreads() {
int w = WorkerThreads.decrementAndGet();
System.err.format("... workers=%d%n", w);
if (w <= 0) DoneAll();
}
void DoneAll() {
print("... sorted values");
WorkerButtonSequential.setEnabled(true);
WorkerButtonThreads.setEnabled(true);
WorkerButtonSwingWorkers.setEnabled(true);
System.err.format("%n");
}
// === SorterPanel
/* colour codes
pivot : YELLOW
left item : GREEN
right item : BLUE
left item just before swap : PINK
right item just before swap : PINK
left item just after swap : RED
right item just after swap : RED
*/
class SorterPanel extends JComponent {
int[] Values;
int width;
Graphics2D g2;
Color pen;
Color back;
public void setValues(int[] Values) {
this.Values = Values;
width = super.getWidth() / Values.length;
repaint();
}
#Override
public void paintComponent(Graphics g) {
if (Values == null) return;
g2 = (Graphics2D) g;
pen = Color.BLACK; // g2.getColor();
back = g2.getBackground();
for (int i = 0; i < Values.length; i++) {
g2.draw(new Rectangle2D.Double(width*i+1, 0, width-2, Values[i]));
}
}
public void mark(int i, int value, Color m) {
g2 = (Graphics2D) super.getGraphics();
pen = g2.getColor();
g2.setColor(m);
//g2.fill(new Rectangle2D.Double(width*i+2, 1, width-4, Values[i]-2));
g2.fill(new Rectangle2D.Double(width*i+2, 1, width-4, value-2));
g2.setColor(pen);
}
public void unmark(final int i, final int value) {
mark(i, value, back);
}
public void erase(int i, int value) {
g2 = (Graphics2D) super.getGraphics();
//g2.clearRect(width*i+1, 0, width-1, Values[i]+1);
g2.clearRect(width*i+1, 0, width-1, value+1);
}
public void redraw(int i, int value) {
g2 = (Graphics2D) super.getGraphics();
//g2.draw(new Rectangle2D.Double(width*i+1, 0, width-2, Values[i]));
g2.draw(new Rectangle2D.Double(width*i+1, 0, width-2, value));
mark(i, value, back);
}
}
// === QuickSort Sequential
class QuickSortSequential implements Runnable {
int[] array;
int left;
int right;
// === GUI stuff
SorterPanel sortpan;
void publish(Runnable gui_update) {
gui_update.run();
}
void mark(final int idx, final Color color) {
final int value = array[idx];
publish(new Runnable() {
public void run() {
sortpan.mark(idx, value, color);
}
});
}
void unmark(final int idx) {
final int value = array[idx];
publish(new Runnable() {
public void run() {
sortpan.unmark(idx, value);
}
});
}
void erase(final int idx) {
final int value = array[idx];
publish(new Runnable() {
public void run() {
sortpan.erase(idx, value);
}
});
}
void redraw(final int idx) {
final int value = array[idx];
publish(new Runnable() {
public void run() {
sortpan.redraw(idx, value);
}
});
}
void sleep(int period) {
try {
Thread.sleep(period);
} catch (Exception ex) {
System.err.format("%s%n", ex.getMessage());
}
}
// === stuff
public QuickSortSequential(final int array[], final int left, final int right, final SorterPanel sortpan) {
this.array = array;
this.left = left;
this.right = right;
this.sortpan = sortpan;
}
public void run() {
QuickSort();
}
// === QuickSort stuff
void QuickSort() {
if (left >= right) return;
final int pivot = Partition();
if (pivot < 0) return;
QuickSortSequential lquick = new QuickSortSequential(array, left, pivot-1, sortpan);
QuickSortSequential rquick = new QuickSortSequential(array, pivot, right, sortpan);
lquick.run();
rquick.run();
}
int Partition() {
int leftIdx = left;
int rightIdx = right;
int pivotIdx = (left + right) / 2;
final int pivot = array[pivotIdx];
while (true) {
if (leftIdx > rightIdx) break;
mark(pivotIdx, Color.YELLOW);
mark(leftIdx, Color.GREEN);
mark(rightIdx, Color.BLUE);
sleep(LEFT);
while (true) {
if (array[leftIdx] >= pivot) break;
else {
unmark(leftIdx);
leftIdx += 1;
mark(pivotIdx, Color.YELLOW);
mark(leftIdx, Color.GREEN);
sleep(LEFT);
}
}
while (true) {
if (pivot >= array[rightIdx]) break;
else {
unmark(rightIdx);
rightIdx -= 1;
mark(pivotIdx, Color.YELLOW);
mark(rightIdx, Color.BLUE);
sleep(RIGHT);
}
}
unmark(pivotIdx);
unmark(leftIdx);
unmark(rightIdx);
if (leftIdx <= rightIdx) {
if (leftIdx < rightIdx) {
mark(pivotIdx, Color.YELLOW);
mark(leftIdx, Color.PINK);
mark(rightIdx, Color.PINK);
sleep(SWAP);
erase(leftIdx);
erase(rightIdx);
int temp = array[rightIdx];
array[rightIdx] = array[leftIdx];
array[leftIdx] = temp;
if (pivotIdx == leftIdx) pivotIdx = rightIdx;
else if (pivotIdx == rightIdx) pivotIdx = leftIdx;
redraw(leftIdx);
redraw(rightIdx);
mark(pivotIdx, Color.YELLOW);
mark(leftIdx, Color.RED);
mark(rightIdx, Color.RED);
sleep(SWAP);
}
unmark(pivotIdx);
unmark(leftIdx);
unmark(rightIdx);
leftIdx += 1;
rightIdx -= 1;
}
}
return leftIdx;
}
}
// === QuickSort with Threads
class QuickSortThread extends Thread {
int[] array;
int left;
int right;
int threshold;
// === GUI stuff
SorterPanel sortpan;
// === Thread etc stuff
public QuickSortThread(final int array[], final int left, final int right, final int threshold, final SorterPanel sortpan) {
this.array = array;
this.left = left;
this.right = right;
this.sortpan = sortpan;
this.threshold = threshold;
}
#Override
public void run() {
decWorkerThreads();
}
}
// === QuickSort with SwingWorkers
class QuickSortWorker extends SwingWorker<Boolean, Runnable> {
int[] array;
int left;
int right;
int threshold;
// === GUI stuff
SorterPanel sortpan;
// === SwingWorker stuff
public QuickSortWorker(final int array[], final int left, final int right, final int threshold, final SorterPanel sortpan) {
this.array = array;
this.left = left;
this.right = right;
this.threshold = threshold;
this.sortpan = sortpan;
}
#Override
public Boolean doInBackground() {
return true;
}
#Override
public void process(java.util.List<Runnable> gui_updates) {
}
#Override
public void done() {
decWorkerThreads();
}
}
}
not directly the answers to your question, but there I see three areas, I think that your code missed
implements SwingWorker#cancel(), for restart of processes
implements PropertyChangeListener for listening changes from SwingWorker
invoke SwingWorker from Executor,
notice please read How to get Exception from SwingWorker