Repaint() doesn't work when called from new thread - java

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;
}
}
}

Related

Separating Swingworker Functions into different Classes Java

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() {
}
}

Why aren't the array objects changing when changed in the JFrame they were added to?

I am adding an array of objects onto a JFrame. When I change the object's state in the JFrame, it is not changing in the array. The object's class is called connect2. The change that I am making is increasing connect2's arrayPosition field by 1. The change is made on the object that was added to the JFrame, but not the corresponding array.
The algorithm is as follows:
connect1 extends JFrame and contains the main method which invokes the connect1 constructor. The connect1 constructor set the size for the JFrame, set the JFrame to visible, set a GridLayout, and instantiates 100 connect2 objects which are also JPanels and adds them to an array. The 100 connect2 JPanels are added to the JFrame via for loop.
The connect2 constructor takes in a connect1 argument. When connect2 is clicked, it will increment connect1's static counter field. The counter's value is passed to connect2's arrayposition variable.
For some reason when I access the connect2 object in the array in connect1, the arrayposition variable has not changed.
Can someone please give me a hand with this issue? Why aren't the objects in the array changing as well. Java arrays store the memory locations of the objects, don't they?
Here is my code:
package connect;
import java.awt.*;
import javax.swing.JFrame;
public class connect1 extends JFrame
{
static int counter = 0;
int M = 10;
int N = 10;
int Grid = M*N;
connect2 array[] = new connect2 [Grid];
private static final long serialVersionUID = 1L;
public static void main(String[] args)
{
new connect1();
}
public connect1()
{
setVisible(true);
setSize(1000, 1000);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new GridLayout(M, N));
//add(new Welcome(this));
//int Grid = M*N;
//connect2 array[] = new connect2 [Grid];
for (int j = 0; j < Grid; j++)
{
array[j] = new connect2(this);
add(array[j]);
}
}
public void toRefresh()
{
if (counter > 0)
{
bubbleSort(array);
}
repaint();
}
public void bubbleSort(connect2[] x)
{
connect2 temp;
for (int i = 0; i < /*x.length*/ counter - 1; i++)
{
for (int j = 1; j < /*x.length*/ counter - i; j++)
{
//System.out.println(x[j - 1].arrayPosition+" j - 1 "+x[j].arrayPosition+" j ");
if (x[j - 1].arrayPosition > x[j].arrayPosition)
{
temp = x[j - 1];
x[j - 1] = x[j];
x[j] = temp;
}
}
}
}
public void undo()
{
for (int i = 0; i < array.length; i++)
{
System.out.println(array[i].arrayPosition);
}
}
}
Connect2 class:
package connect;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JPanel;
public class connect2 extends JPanel
{
private static final long serialVersionUID = 1L;
int arrayPosition;
int whenClicked = 0;
connect1 refr = null;
public connect2(connect1 refresh)
{
addMouseListener(new MouseListener()
{
#SuppressWarnings("static-access")
#Override
public void mouseClicked(MouseEvent arg0)
{
if(arg0.getModifiers() == MouseEvent.BUTTON1_MASK)
{
whenClicked++;
refr.counter++;
arrayPosition = refr.counter;
refr.toRefresh();
}
if(arg0.getModifiers() == MouseEvent.BUTTON3_MASK)
{
System.out.println(arrayPosition);
refr.undo();
}
}
#Override
public void mouseEntered(MouseEvent arg0)
{
// Not used
}
#Override
public void mouseExited(MouseEvent arg0)
{
// Not used
}
#Override
public void mousePressed(MouseEvent arg0)
{
// Not used
}
#Override
public void mouseReleased(MouseEvent arg0)
{
// Not used
}
});
refr = refresh;
}
#Override
protected void paintComponent(Graphics g)
{
switch(whenClicked)
{
case 1:
g.setColor(Color.red);
break;
case 2:
g.setColor(Color.blue);
break;
}
g.fillOval(0, 0, 80, 80);
}
}
Look at the refr = refresh; statement. This statement is located below arrayPosition = refr.counter.

How to have a MotionListener called on a JPanel that is covered

This question is to help me solve my other question. I have a JPanel that is completely covered by JLabels. I have added a MotionListener to the panel but it is never called since the labels are in the way. If I remove the labels my MotionnListener is called properly.
Is there a way I can have the covered panel see the motion events?
public class ChangesAttempt extends JPanel {
private static final long serialVersionUID = -8031881678612431401L;
static JFrame frame;
static JPanel grid;
static JLabel[][] squares;
static boolean[][] litSquares;
static int size, boardSize;
MouseEvent listener = new MouseEvent();
MotionListener mListerner = new MotionListener();
Color backGround = Color.BLUE;
Color selected = Color.PINK;
public ChangesAttempt(int size) {
ChangesAttempt.size = size;
squares = new JLabel[size][size];
litSquares = new boolean[size][size];
grid = new JPanel(new GridLayout(size, size));
grid.addMouseMotionListener(mListerner);
/*Coment out setBoard(); below to see the mousedDragged method actually being called
since the JLabels will not be in the way.*/
setBoard();
frame = new JFrame();
frame.setLayout(new BorderLayout());
frame.add(grid, BorderLayout.CENTER);
frame.setTitle("ChangedLife");
if (25 * size < 525)
boardSize = 525;
else
boardSize = 25 * size;
frame.setSize(boardSize, boardSize);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
class MouseEvent implements MouseListener {
#Override
public void mousePressed(java.awt.event.MouseEvent e) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (e.getSource() == squares[i][j]) {
if (litSquares[i][j] == false) {
squares[i][j].setBackground(selected);
litSquares[i][j] = true;
} else {
squares[i][j].setBackground(backGround);
litSquares[i][j] = false;
}
}
}
}
}
#Override
public void mouseClicked(java.awt.event.MouseEvent e) {
}
#Override
public void mouseEntered(java.awt.event.MouseEvent e) {
}
#Override
public void mouseExited(java.awt.event.MouseEvent e) {
}
#Override
public void mouseReleased(java.awt.event.MouseEvent e) {
}
}
class MotionListener implements MouseMotionListener {
#Override
public void mouseDragged(java.awt.event.MouseEvent e) {
Point p = e.getPoint();
System.out.println("mouse Dragged to " + p);
}
#Override
public void mouseMoved(java.awt.event.MouseEvent e) {
// TODO Auto-generated method stub
}
}
public void setBoard() {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
squares[i][j] = new JLabel();
squares[i][j].addMouseListener(listener);
squares[i][j].setOpaque(true);
squares[i][j].setBackground(backGround);
grid.add(squares[i][j]);
}
}
}
class MyLabel extends JLabel {
private static final long serialVersionUID = -1414933339546989142L;
}
public static void main(String args[]) {
new ChangesAttempt(20);
}
}
There's no "easy" way to achieve this, it might be better to move all you mouse listeners to the panel instead.
This would require you to look up the component (that's within the parent component) at the specified click point, for example...
#Override
public void mousePressed(java.awt.event.MouseEvent e) {
Component component = e.getComponent();
Component source = component.getComponentAt(e.getPoint());
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (source == squares[i][j]) {
if (litSquares[i][j] == false) {
squares[i][j].setBackground(selected);
litSquares[i][j] = true;
} else {
squares[i][j].setBackground(backGround);
litSquares[i][j] = false;
}
}
}
}
}
This will work, if you remove the MouseListener from the labels and instead, add it to the grid, allowing with your MouseMotionListener

Threads and Buttons: How to restart the program after its finished running

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

graphic.addAnimation calling addAnimation

I run this code:
graphic.addAnimation("standart",new int[] {0,1},1.0,true);
which calls the graphic.addAnimation(String,int[],float,boolean) Method:
public void addAnimation(String nme,int[] frmes,float time,boolean loop) {
animations.push(new Aim(nme, frmes, time, loop));
}
but I get this error:
the function addAnimation(String,int[],float,boolean) does not exist.
SpriteSheet:
package progame;
import java.util.Stack;
import processing.core.PImage;
public class SpriteSheet extends Graphic {
public int height,width,index;
public int timer;
public String playing;
public Stack<PImage> sheet = new Stack<PImage>();
public Stack<Aim> animations = new Stack<Aim>();
public SpriteSheet(String path,int h,int w) {
super(path);
height = h;
width = w;
for(int i = 0; i < Math.floor(source.height/height); i++) {
for(int j = 0; j < Math.floor(source.width/width); j++) {
sheet.push(source.get(j*width, i*height, width, height));
}
}
}
public SpriteSheet(String path,Entity e,int h,int w) {
super(path,e);
height = h;
width = w;
for(int i = 0; i < Math.floor(source.height/height); i++) {
for(int j = 0; j < Math.floor(source.width/width); j++) {
sheet.push(source.get(j*width, i*height, width, height));
}
}
}
public Aim getAnimation(String name) {
for(int i = 0; i< animations.size(); i++)
{
if(animations.get(i).name == name) {
return(animations.get(i));
}
}
return null;
}
public void play(String name) {
for(int i = 0; i< animations.size(); i++)
{
if(animations.get(i).name == name) {
playing = name;
index = 0;
timer = 0;
}else
{
playing = null;
return;
}
}
}
public void update() {
timer ++;
Aim aim = getAnimation(playing);
if( timer > aim.frameRate)
{
timer = 0;
if(index == aim.frames.length)
{
if(aim.looping) {
index = 0;
}else
{
playing = null;
}
}else
{
index++;
}
}
source = sheet.get(index);
}
public void render() {
update();
super.render();
}
public void addAnimation(String nme,int[] frmes,float time,boolean loop) {
animations.push(new Aim(nme, frmes, time, loop));
}
private class Aim
{
String name;
int[] frames;
float frameRate;
boolean looping;
public Aim(String nme,int[] frmes,float time,boolean loop)
{
name = nme;
frames = frmes;
frameRate = time;
looping = loop;
}
}
}
Where did you obtain the instance 'graphic' from in the following line?
graphic.addAnimation("standart",new int[] {0,1},1.0,true);
Or more importantly, what is its declaration? You can't call addAnimation on a variable of type Graphic. As it's SpriteSheet that defined this method.
Based on OP's comment: its declared in the Entity class, like this: public Graphic graphic;
((SpriteSheet)graphic).addAnimation("standart",new int[] {0,1},1.0,true);
would fix the problem.
You said
its declared in the Entity class, like this: public Graphic graphic;
Declare it as:
public SpriteSheet graphic;

Categories