Display labels from array in different locations in Java - java

I am trying to make elements of an array to be displayed in different locations with random time periods (with fade in and fade out effect).
What i ve done so far, made an array of text labels. Made transitions. But i can't figure out, how to create a for loop that will display other labels in different locations on JFrame. And they should not appear all at the same time but one after another.
Please, help out?
Here is my code:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class FadingLabel {
private int alpha = 255;
private int increment = -5;
public JLabel label = new JLabel("Fading Label");
public JLabel label2 = new JLabel("Fading Label 2");
public JLabel label3 = new JLabel("Fading Label 3");
public JLabel label4 = new JLabel("Fading Label 4");
JLabel labels[] = new JLabel[]{ label, label2, label3 };
Dimension size = label.getPreferredSize();
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new FadingLabel().makeUI();
}
});
}
public void makeUI() {
new Timer(80, new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (int i = 0; i <= 3; i++){
alpha += increment;
if (alpha >= 255) {
alpha = 255;
increment = -increment;
}
if (alpha <= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
alpha = 0;
increment = -increment;
}
label3.setForeground(new Color(0, 0, 0, alpha));
label3.setLocation(50,60);
}
}
}).start();
JFrame frame = new JFrame();
frame.add(labels[2]);
frame.setPreferredSize(new Dimension(700,500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}

Animation is hard, good animation is harder.
A stepped animation (like you've done) is not particularly efficient and can suffer from interruptions (from the OS or other parts of the system) and can be difficult to scale.
Instead, you should aim for a "duration" based animation. Where something happens over a given period of time, this way, you can more easily drop frames you can't render.
One of the difficult concepts to get around is the idea that you can't perform long running or blocking operations in the "main thread" of the GUI, but neither can you update UI from outside of the "main thread" (in Swing this is known as the Event Dispatching Thread).
So, instead, you need some way monitor each label and as it fades out, start the next label fading in. This is where a good observer pattern (AKA listener) comes in.
import java.awt.AlphaComposite;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import java.util.EventListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private FadableLabel[] labels = new FadableLabel[]{
new FadableLabel("A long time ago"),
new FadableLabel("in a galaxy far, far, away..."),
new FadableLabel("It is a period of civil war."),
new FadableLabel("Rebel spaceships striking from a hidden base,"),
new FadableLabel("have won their first victory against the evil Galactic Empire"),
new FadableLabel("During the battle,"),
new FadableLabel("Rebel spies managed to steal secret plans to the Empire's ultimate weapon,"),
new FadableLabel("the Death Star")
};
private int labelIndex = -1;
public TestPane() {
setBorder(new EmptyBorder(50, 50, 50, 50));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
for (FadableLabel label : labels) {
label.setAlpha(0);
add(label, gbc);
}
}
#Override
public void addNotify() {
super.addNotify();
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
nextLabel();
}
});
}
#Override
public void removeNotify() {
super.removeNotify();
}
protected void nextLabel() {
labelIndex++;
if (labelIndex >= labels.length) {
return;
}
FadableLabel label = labels[labelIndex];
label.addFadableLableListener(new FadableLableListener() {
#Override
public void didFadeLabelIn(FadableLabel label) {
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
label.fadeOut();
}
});
timer.setRepeats(false);
timer.start();
}
#Override
public void didFadeLabelOut(FadableLabel label) {
label.removeFadableLableListener(this);
nextLabel();
}
});
label.fadeIn();
}
}
public interface FadableLableListener extends EventListener {
public void didFadeLabelIn(FadableLabel label);
public void didFadeLabelOut(FadableLabel label);
}
public class FadableLabel extends JLabel {
private float alpha = 1.0f;
private Timer fadeTimer;
private FadeRange fadeRange;
private Instant fadeStartedAt;
private Duration desiredFadeTime = Duration.ofMillis(1000);
public FadableLabel() {
super();
}
public FadableLabel(String text) {
super(text);
}
public void addFadableLableListener(FadableLableListener listener) {
listenerList.add(FadableLableListener.class, listener);
}
public void removeFadableLableListener(FadableLableListener listener) {
listenerList.remove(FadableLableListener.class, listener);
}
public float getAlpha() {
return alpha;
}
public void setAlpha(float alpha) {
this.alpha = alpha;
repaint();
}
protected void fireDidFadeOut() {
FadableLableListener[] listeners = listenerList.getListeners(FadableLableListener.class);
if (listeners.length == 0) {
return;
}
for (FadableLableListener listener : listeners) {
listener.didFadeLabelOut(this);
}
}
protected void fireDidFadeIn() {
FadableLableListener[] listeners = listenerList.getListeners(FadableLableListener.class);
if (listeners.length == 0) {
return;
}
for (FadableLableListener listener : listeners) {
listener.didFadeLabelIn(this);
}
}
protected void stopFadeTimer() {
if (fadeTimer != null) {
fadeStartedAt = null;
fadeTimer.stop();
}
}
protected void startFadeTimer() {
if (fadeRange == null) {
throw new RuntimeException("Fade range can not be null when starting animation");
}
fadeStartedAt = Instant.now();
fadeTimer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Duration runTime = Duration.between(fadeStartedAt, Instant.now());
double progress = Math.min(1d, Math.max(0d, runTime.toMillis() / (double) desiredFadeTime.toMillis()));
setAlpha(fadeRange.valueAt(progress));
if (progress >= 1.0) {
stopFadeTimer();
if (getAlpha() >= 1.0) {
fireDidFadeIn();
} else {
fireDidFadeOut();
}
}
}
});
fadeTimer.start();
}
public void fadeIn() {
stopFadeTimer();
if (alpha < 1.0) {
fadeRange = new FadeRange(alpha, 1.0f);
startFadeTimer();
}
}
public void fadeOut() {
stopFadeTimer();
if (alpha > 0.0) {
fadeRange = new FadeRange(alpha, 0);
startFadeTimer();
}
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
super.paintComponent(g2d);
g2d.dispose();
}
protected class FadeRange {
private float from;
private float to;
public FadeRange(float from, float to) {
this.from = from;
this.to = to;
}
public float getFrom() {
return from;
}
public float getTo() {
return to;
}
public float getDistance() {
return getTo() - getFrom();
}
public float valueAt(double progress) {
double distance = getDistance();
double value = distance * progress;
value += getFrom();
return (float) value;
}
}
}
}
Now, to your next problem. A poor approach might be to use an "absolute" or "null" layout
A better choice would be to either make use of something like GridBagLayout (and possibly EmptyLayout) and randomise the Insets of the GridBagConstraints. Alternatively, you could create your own layout manager to do the job for you
See Absolute Positioning Graphic JPanel Inside JFrame Blocked by Blank Sections and Moving JPasswordField to absolute position for some ideas
A cray example
When I say animation is "hard" and it can become complicated very fast, I'm not kidding. To that I end I wrote myself an animation library, which does all the things I keep finding myself doing.
https://github.com/RustyKnight/SuperSimpleSwingAnimationFramework
So, this is an example, based on the above library, which moves the label across a random range, while it's been faded in/out. Without the above library, this kind of work would be, a lot.
import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.util.EventListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import org.kaizen.animation.Animatable;
import org.kaizen.animation.AnimatableAdapter;
import org.kaizen.animation.DefaultAnimatableDuration;
import org.kaizen.animation.curves.AnimationCurve;
import org.kaizen.animation.curves.Curves;
import org.kaizen.animation.ranges.AnimatableRange;
import org.kaizen.animation.ranges.FloatAnimatableRange;
import org.kaizen.animation.ranges.FloatRange;
import org.kaizen.animation.ranges.PointAnimatableRange;
import org.kaizen.animation.ranges.PointRange;
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private String[] textValues = new String[] {
"A long time ago",
"in a galaxy far, far, away...",
"It is a period of civil war.",
"Rebel spaceships striking from a hidden base,",
"have won their first victory against the evil Galactic Empire",
"During the battle,",
"Rebel spies managed to steal secret plans to the Empire's ultimate weapon,",
"the Death Star"
};
private int labelIndex = -1;
// You'd need two if you wanted to do cross fades
private FadableLabel label;
private Random rnd = new Random();
// The desired duration of the animation, 1 second for fade in,
// 1 second for fade out and 1 second for delay between swicthing state
private Duration desiredDuration = Duration.ofSeconds(3);
// The desired animation curve (ease in/out)
private AnimationCurve curve = Curves.SINE_IN_OUT.getCurve();
// The movement animator
private DefaultAnimatableDuration animator;
public TestPane() {
setLayout(null);
label = new FadableLabel();
label.setAlpha(0);
add(label);
label.addFadableLableListener(new FadableLableListener() {
#Override
public void didFadeLabelIn(FadableLabel label) {
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
label.fadeOut();
}
});
timer.setRepeats(false);
timer.start();
}
#Override
public void didFadeLabelOut(FadableLabel label) {
nextText();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 400);
}
#Override
public void addNotify() {
super.addNotify();
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
nextText();
}
});
}
#Override
public void removeNotify() {
stopAnimation();
super.removeNotify();
}
protected void stopAnimation() {
if (animator != null) {
animator.stop();
}
}
protected void nextText() {
stopAnimation();
labelIndex++;
if (labelIndex >= textValues.length) {
return;
}
String text = textValues[labelIndex];
label.setText(text);
label.setSize(label.getPreferredSize());
// Randomise the from and to locations
Point from = new Point(rnd.nextInt(getWidth() - label.getSize().width), rnd.nextInt(getHeight() - label.getSize().height));
Point to = new Point(rnd.nextInt(getWidth() - label.getSize().width), rnd.nextInt(getHeight() - label.getSize().height));
// Generate the range
PointRange range = new PointRange(from, to);
// Setup an animatable range of the PointRange
animator = new PointAnimatableRange(range, desiredDuration, curve, new AnimatableAdapter<Point>() {
#Override
public void animationChanged(AnimatableRange<Point> animatable) {
label.setLocation(animatable.getValue());
}
});
label.setLocation(from);
// Make it so
label.fadeIn();
animator.start();
}
}
public interface FadableLableListener extends EventListener {
public void didFadeLabelIn(FadableLabel label);
public void didFadeLabelOut(FadableLabel label);
}
public class FadableLabel extends JLabel {
private FloatAnimatableRange animator;
private AnimationCurve curve = Curves.SINE_IN_OUT.getCurve();
private Duration desiredDuration = Duration.ofSeconds(1);
private float alpha = 1.0f;
public FadableLabel() {
super();
}
public FadableLabel(String text) {
super(text);
}
public void addFadableLableListener(FadableLableListener listener) {
listenerList.add(FadableLableListener.class, listener);
}
public void removeFadableLableListener(FadableLableListener listener) {
listenerList.remove(FadableLableListener.class, listener);
}
public float getAlpha() {
return alpha;
}
public void setAlpha(float alpha) {
this.alpha = alpha;
repaint();
}
protected void fireDidFadeOut() {
FadableLableListener[] listeners = listenerList.getListeners(FadableLableListener.class);
if (listeners.length == 0) {
return;
}
for (FadableLableListener listener : listeners) {
listener.didFadeLabelOut(this);
}
}
protected void fireDidFadeIn() {
FadableLableListener[] listeners = listenerList.getListeners(FadableLableListener.class);
if (listeners.length == 0) {
return;
}
for (FadableLableListener listener : listeners) {
listener.didFadeLabelIn(this);
}
}
protected void stopFadeTimer() {
if (animator != null) {
animator.stop();
}
}
protected void startFadeTimer(FloatRange range, AnimationListener animationListener) {
stopFadeTimer();
animator = new FloatAnimatableRange(range, desiredDuration, curve, new AnimatableAdapter<Float>() {
#Override
public void animationChanged(AnimatableRange<Float> animatable) {
alpha = animatable.getValue();
repaint();
}
#Override
public void animationCompleted(Animatable animator) {
if (animationListener != null) {
animationListener.animationCompleted();
}
}
});
animator.start();
}
public void fadeIn() {
stopFadeTimer();
startFadeTimer(new FloatRange(alpha, 1f), new AnimationListener() {
#Override
public void animationCompleted() {
fireDidFadeIn();
}
});
}
public void fadeOut() {
stopFadeTimer();
startFadeTimer(new FloatRange(alpha, 0f), new AnimationListener() {
#Override
public void animationCompleted() {
fireDidFadeOut();
}
});
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
super.paintComponent(g2d);
g2d.dispose();
}
protected interface AnimationListener {
public void animationCompleted();
}
}
}
The library is based on Netbeans, it wouldn't be hard to just extract the source into some other IDE.
Swing based "perform after delay"
made up silly solution, #MadProgrammer. added Thread.sleep(ms); in removeFadeableLabelListener. it works but i believe there is much brighter and smart solution. could you show please how to use delay timer for such task?
Don't, ever, use Thread.sleep from within the Event Dispatching Thread. This is going to cause a cascade of issues which will basically make you program look like it's frozen (because, essentially, it is)
Instead, you need to become familiar with the mechanisms you have available to you via the API. You could use a SwingWorker, but a simpler solution might be to just use a non-repeating Swing Timer, which is demonstrated above.
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
JButton btn = new JButton("Click me");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
btn.setText("...");
SwingHelper.after(Duration.ofSeconds(1), new Runnable() {
#Override
public void run() {
btn.setEnabled(false);
btn.setText("Don't do that");
}
});
}
});
setBorder(new EmptyBorder(10, 10, 10, 10));
setLayout(new GridBagLayout());
add(btn);
}
}
public class SwingHelper {
public static void after(Duration duration, Runnable runnable) {
Timer timer = new Timer((int)duration.toMillis(), new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
runnable.run();
}
});
timer.setRepeats(false);
timer.start();
}
}
}

Related

Troubling with showing Graphics in bubble sort visualization in Java [duplicate]

Here is the code for displaying circles with varying radius on a panel inside a frame with a given delay rate, but the code is showing the final output not the intermediate stages i.e., the circles are not appearing one by one but all the circles are coming at once as a final output. There may be some errors related to button action listeners and panel threads. The code is taking initial circle radius and the total number of iterations (the total number of circles to be displayed), radius of each next circle gets incremented by 10.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ControlCircle extends JFrame {
private JButton jbtEnlarge = new JButton("Start");
private JButton jbtShrink = new JButton("Stop");
private CirclePanel canvas = new CirclePanel();
private int radius = 0;
private int iter;
public ControlCircle() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtEnlarge);
panel.add(jbtShrink);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
final JTextField f1 = new JTextField(8),f2 = new JTextField(8);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
f1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
radius = Integer.parseInt(new String(f1.getText()));
}
});
f2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
iter = Integer.parseInt(new String(f2.getText()));
}
});
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
public static void main(String[] args) {
JFrame frame = new ControlCircle();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class EnlargeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
canvas.enlarge();
}
}
class ShrinkListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//canvas.shrink();
}
}
class CirclePanel extends JPanel {
private int r = radius;
public void enlarge() {
//radius += 2;
repaint();
}
public void shrink() {
radius -= 2;
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < iter; i++) {
g.drawOval(getWidth() / 2 - r, getHeight() / 2 - r, 2 * r, 2 * r);
try {
Thread.sleep(100);
} catch (Exception exp) {
}
r = r + 10;
}
r = 0;
}
}
}
The problem you're having is far to common.
Swing is a single threaded framework. This means that all UI related interactions must occur within the context of this thread (AKA the Event Dispatching Thread).
The EDT is responsible for, amongst other things, dispatching repaint requests. If any part of your code stops this thread (block I/O, time consuming process, Thread.sleep), the EDT will be unable to process any new events.
Have a read through Concurrency in Swing for more details.
You now face two issues...
You can't block the EDT
You can't update the UI from any thread other then the EDT.
Luckily, there are a number of solutions. The simplest is using a javax.swing.Timer.
This timer triggers it's tick events within the EDT but waits within it's own thread...
import com.sun.org.apache.bcel.internal.generic.LSTORE;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Droplets {
public static void main(String[] args) {
new Droplets();
}
public Droplets() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new DropletPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected static final int MAX_RADIUS = 50;
protected static final int GROWTH_RATE = 1;
public class DropletPane extends JPanel {
private List<Droplet> droplets;
public DropletPane() {
droplets = new ArrayList<>(25);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
droplets.add(new Droplet(e.getPoint()));
}
});
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
droplet.grow();
if (droplet.getRadius() >= MAX_RADIUS) {
droplets.remove(droplet);
}
}
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Composite comp = g2d.getComposite();
for (Droplet droplet : droplets) {
float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
Point p = droplet.getLocation();
int radius = droplet.getRadius();
g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
g2d.setComposite(comp);
}
g2d.dispose();
}
}
public class Droplet {
private Point p;
private int radius;
public Droplet(Point p) {
this.p = p;
}
public Point getLocation() {
return p;
}
public int getRadius() {
return radius;
}
public void grow() {
radius += GROWTH_RATE;
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
}
}
}
Extended Example
This example will, when you click the "Start" button, create a random number of droplets at a random interval (between each droplet). You can press start multiple times and it will compound the output.
import static droplets.Droplets.MAX_RADIUS;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Droplets02 {
public static void main(String[] args) {
new Droplets02();
}
public Droplets02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new DropletPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected static final int MAX_RADIUS = 50;
protected static final int GROWTH_RATE = 1;
public interface Pool {
public void addDroplet(Droplet droplet);
public Dimension getSize();
}
public class DropletPane extends JPanel implements Pool {
private List<Droplet> droplets;
private Timer timer;
public DropletPane() {
setLayout(new GridBagLayout());
JButton button = new JButton("Start");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new DropletWorker(DropletPane.this).execute();
}
});
add(button);
droplets = new ArrayList<>(25);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!droplets.isEmpty()) {
for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
droplet.grow();
if (droplet.getRadius() >= MAX_RADIUS) {
droplets.remove(droplet);
}
}
if (droplets.isEmpty()) {
((Timer) e.getSource()).stop();
}
repaint();
}
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Composite comp = g2d.getComposite();
for (Droplet droplet : droplets) {
float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
Point p = droplet.getLocation();
int radius = droplet.getRadius();
g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
g2d.setComposite(comp);
}
g2d.dispose();
}
#Override
public void addDroplet(Droplet droplet) {
if (!timer.isRunning()) {
timer.start();
}
droplets.add(droplet);
}
}
public class Droplet {
private Point p;
private int radius;
public Droplet(Point p) {
this.p = p;
}
public Point getLocation() {
return p;
}
public int getRadius() {
return radius;
}
public void grow() {
radius += GROWTH_RATE;
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
}
}
public class DropletWorker extends SwingWorker<Void, Droplet> {
private Pool pool;
public DropletWorker(Pool pool) {
this.pool = pool;
}
public Pool getPool() {
return pool;
}
protected int random(int minRange, int maxRange) {
return minRange + (int) (Math.round(Math.random() * (maxRange - minRange)));
}
#Override
protected Void doInBackground() throws Exception {
int dropCount = random(1, 100);
Pool pool = getPool();
Dimension size = pool.getSize();
for (int index = 0; index < dropCount; index++) {
Thread.sleep(random(10, 1000));
int x = random(0, size.width);
int y = random(0, size.height);
Droplet droplet = new Droplet(new Point(x, y));
publish(droplet);
}
return null;
}
#Override
protected void process(List<Droplet> chunks) {
for (Droplet droplet : chunks) {
getPool().addDroplet(droplet);
}
}
}
}
Animation Basics
You need three things to perform animation.
A Start state
A Target state
A delta or time range.
(You also need some way to store the current state)
The start and target states are self explanatory, they describe where you are now and where you want to change to.
The delta would be the amount to apply to the current state at each "time interval" (or tick) until you reach the delta.
Or
The time range would be the amount of time you want to use to move from the start state to the end state.
The delta approach is the simpler mechanism, but isn't nearly as flexible as the time range approach...
Once you have these basic elements set up, you need some kind of "tick" that is triggered at regular intervals which allows you to calculate the current state, which is either a linear movement from the start state to the target state (delta) or a progression of change of over time (time range)
A final, full working rework
Apart from you're attempt to block the EDT within the paint method and failing to following the Initial Thread requirements of Swing, the only other, significant, problem I found was your reliance on the radius and iter values.
Basically, these were never getting set UNLESS you pressed the Enter key...which I wasn't.
This example uses the code that you posted and the ideas from the first example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ControlCircles extends JFrame {
private JButton jbtEnlarge = new JButton("Start");
private JButton jbtShrink = new JButton("Stop");
private CirclePanel canvas = new CirclePanel();
private JTextField f1 = new JTextField(8);
private JTextField f2 = new JTextField(8);
public ControlCircles() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtEnlarge);
panel.add(jbtShrink);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new ControlCircles();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class EnlargeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
int radius = Integer.parseInt(f1.getText());
int iter = Integer.parseInt(f2.getText());
canvas.start(radius, iter);
}
}
class ShrinkListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//canvas.shrink();
}
}
class CirclePanel extends JPanel {
private int radius;
private int iterations;
private int iteration;
private List<Integer> circles;
private Timer timer;
public CirclePanel() {
circles = new ArrayList<>(25);
timer= new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
iteration++;
if (iteration < iterations) {
circles.add(radius);
radius += 10;
} else {
((Timer)e.getSource()).stop();
}
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth() - 1;
int height = getHeight()- 1;
g.drawRect(0, 0, width, height);
for (Integer radius : circles) {
int x = (width - radius) / 2;
int y = (height - radius) / 2;
g.drawOval(x, y, radius, radius);
}
}
public void start(int radius, int iter) {
timer.stop();
circles.clear();
this.radius = radius;
iterations = iter;
iteration = 0;
System.out.println("radius = " + radius);
System.out.println("iterations = " + iterations);
timer.start();
}
}
}
This code works based on the description of your problem by correcting the common mistakes with animation in Swing but some of your code didn't quite make sense to me (ie enlarge and shrink) so I focused on the description your provided.
The idea is to control the drawing animation on the panel used as a canvas with the buttons Start, Stop and I added Continue and Reset additional controls to better explain the idea. These buttons control the animation thread execution thus drawing circles on the drawing surface. the drawing surface I separated as inner class that has only function to draw whatever performed. Another idea that the approach is taken to draw the circles one by one incrementally until it finishes drawing thus used incremental painting.
I have used the code from the above and changed it a little to support my ideas. If you need more and usually better examples look at this article.
The code is below, I didn't polish it enough to have a production wise look and feel but for demonstration purpose only.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
public class ControlCircle extends JFrame implements Runnable {
private JButton jbtStart = new JButton("Start");
private JButton jbtStop = new JButton("Stop");
private JButton jbtContinue = new JButton("Continue");
private JButton jbtReset = new JButton("Reset");
private CirclePanel canvas = new CirclePanel();
private JTextField f1;
private int radius = 0;
private JTextField f2;
private int iter;
protected boolean incrementalPainting;
/**
* Flag indicates that a thread is suspended
*/
private boolean suspended = false;
/**An instance of the class Thread.*/
private Thread thread = null;
public ControlCircle() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtStart);
panel.add(jbtStop);
panel.add(jbtContinue);
panel.add(jbtReset);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
f1 = new JTextField(8);
f2 = new JTextField(8);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
jbtStart.addActionListener(new StartListener());
jbtStop.addActionListener(new StopListener());
jbtContinue.addActionListener(new ContinueListener());
jbtReset.addActionListener(new ResetListener());
}
public static void main(String[] args) {
JFrame frame = new ControlCircle();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread == null) {
repaint();
startThread();
}
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread != null){
mySuspend();
}
}
}
class ContinueListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
myResume();
}
}
class ResetListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread != null) {
stopThread();
}
repaint();
}
}
/**
* my Suspend
*/
private void mySuspend() {
System.out.println("mySyspend()");
suspended = true;
}
/**
* my Resume
*/
private synchronized void myResume(){
System.out.println("myResume()");
suspended = false;
notifyAll();
}
public void run(){
System.out.println("run() - started");
Thread me = Thread.currentThread();
while (thread == me) {
radius = Integer.parseInt(f1.getText());
iter = Integer.parseInt(f2.getText());
for (int i = 0; i < iter; i++) {
if (thread == null) return;
incrementalPainting = true;
myRepaint();
try {
Thread.sleep(1000);
}
catch(InterruptedException e){}
radius += 10;
}
if(thread != null) thread = null; // exiting while
}
System.out.println("run() - exiting");
}
/**
* start Thread
*/
private void startThread(){
System.out.println("startThread()");
if(thread == null){
thread = new Thread(this);
thread.start();
}
}
/**
* stop Thread
*/
private synchronized void stopThread() {
System.out.println("stopThread()");
thread = null; // exiting from while
if (suspended) {
suspended = false;
notify();
}
}
/**
* This is called from the run method to invoke painting.
*/
private void myRepaint() {
System.out.println("myRepaint()");
incrementalPainting = true;
repaint();
synchronized (this) {
while (incrementalPainting) {
System.out.println("wait while incremental painting");
try {
wait();
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
suspend();
}
/**
* This method should place somewhere when run() has started. Perfectly
* when repaint() performed.
*/
private void suspend(){
System.out.println("suspend()");
synchronized (this) {
while (suspended) {
System.out.println("wait while suspended");
try {
wait();
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
}
public synchronized void myPaint(Graphics g) {
if (g == null){
if (incrementalPainting){
incrementalPainting = false;
notifyAll();
}
return;
}
if (incrementalPainting){
myDraw(g);
incrementalPainting = false;
notifyAll();
}
else {
myDraw(g);
}
}
public void myDraw(Graphics g){
g.drawOval(getWidth() / 2 - radius, getHeight() / 2 - radius, 2 * radius, 2 * radius);
}
protected final class CirclePanel extends JPanel {
//Offscreen buffer of this canvas
private BufferedImage backBuffer = null;
public void paintComponent (Graphics g) {
System.out.println("incrementalPainting="+incrementalPainting);
// First paint background
super.paintComponent(g);
Dimension d = this.getSize();
if (! incrementalPainting)
backBuffer = (BufferedImage) this.createImage(d.width, d.height);
Graphics2D g2 = backBuffer.createGraphics();
if (! incrementalPainting){
g2.setColor(Color.WHITE);
g2.fillRect(0,0, d.width, d.height);
}
myPaint(g2);
g.drawImage(backBuffer, 0, 0, this);
}
}
}

JButton doesn't show up in JFrame

I'm currently working on a schoolproject, where I have to code the game snake. Now I`m finished with the biggest part and tryed to make the game menue. I tryed to place a JButton for starting the game (startPlay). However, the button won't show up and I can't figure out why. Can someone help? Thanks in advance!!
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;
public class Main extends JPanel implements ActionListener, KeyListener{
public static int field[][];
public static GenerateField genField;
public static Snake snake;
public static GenerateFood food;
public static GenerateBarrier barrier;
public int difficultness;
public static int widthField;
public static int heightField;
public static TimerTask move, genBarriers;
public static Timer snakeTimer, barrierTimer;
public JButton startPlay;
public static boolean gameStarted;
public Main ()
{
startPlay = new JButton("Starte Spiel");
startPlay.setBounds(0,0,300,200);
startPlay.addActionListener(this);
add(startPlay);
difficultness = 15;
gameStarted = false;
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
widthField = 150;
heightField = 95;
genField = new GenerateField();
snake = new Snake();
food = new GenerateFood();
barrier = new GenerateBarrier();
barrierTimer = new Timer("Timer");
snakeTimer = new Timer("Timer");
genBarriers = new TimerTask() {
#Override
public void run() {
barrier.clearBarrier();
barrier.multiSpawnBarrier(difficultness);
}
};
move = new TimerTask()
{
public void run()
{
if(GenerateField.inGame)
{
snake.moveSnake();
repaint();
}
}
};
}
private static void startGame()
{
genField.generateField();
field = genField.getField();
snake.generateSnake(40, 75);
food.spawnFood();
snakeTimer.schedule(move,0,50);
barrierTimer.schedule(genBarriers, 0, 25000);
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setSize(1520,1000);
frame.getContentPane().add(new Main());
frame.setLocationRelativeTo(null);
frame.setBackground(Color.LIGHT_GRAY);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
#Override
public void paint(Graphics g)
{
if(gameStarted) {
for (int iy = 0; iy < heightField; iy++) {
for (int ix = 0; ix < widthField; ix++) {
//Zeichnet schwarzen Hintergrund
if (genField.field[iy][ix] == 0) {
g.setColor(Color.BLACK);
g.fillRect(ix * 10, iy * 10, 10, 10);
}
//Zeichnet die Grenze an den Rändern
if (genField.field[iy][ix] == 1) {
g.setColor(Color.red);
g.fillRect(ix * 10, iy * 10, 10, 10);
}
//Zeichnet die Schlange
if (genField.field[iy][ix] == 2) {
g.setColor(Color.green);
g.fillRect(ix * 10, iy * 10, 10, 10);
}
//Zeichnet das "Futter"
if (genField.field[iy][ix] == 3) {
g.setColor(Color.orange);
g.fillRect(ix * 10, iy * 10, 10, 10);
}
//Zeichte die Hindernisse
if (genField.field[iy][ix] == 4) {
g.setColor(Color.blue);
g.fillRect(ix * 10, iy * 10, 10, 10);
}
}
}
}
}
#Override
public void actionPerformed(ActionEvent e)
{
startPlay.setVisible(false);
startGame();
gameStarted = true;
}
#Override
public void keyPressed (KeyEvent e)
{
int code = e.getKeyCode();
if ( code == KeyEvent.VK_LEFT)
{
if (snake.mRight == false)
{
snake.mLeft = true;
snake.mRight = false;
snake.mUp = false;
snake.mDown = false;
}
}
if ( code == KeyEvent.VK_RIGHT)
{
if (snake.mLeft == false)
{
snake.mLeft = false;
snake.mRight = true;
snake.mUp = false;
snake.mDown = false;
}
}
if ( code == KeyEvent.VK_UP)
{
if (snake.mDown == false)
{
snake.mLeft = false;
snake.mRight = false;
snake.mUp = true;
snake.mDown = false;
}
}
if ( code == KeyEvent.VK_DOWN)
{
if (snake.mUp == false)
{
snake.mLeft = false;
snake.mRight = false;
snake.mUp = false;
snake.mDown = true;
}
}
}
#Override
public void keyReleased(KeyEvent e)
{
}
#Override
public void keyTyped(KeyEvent e)
{
}
}
Immediate Problem
The over use of static highlights issues with your design. static is not your friend, you should use it sparingly and wisely.
You're trying to put all your eggs in single basket. This is just going to make the state management harder to handle.
Instead, start by separating your menu and game into separate classes and managing them independently of each other.
This then allows you to use a CardLayout to manage the navigation between the different views.
The following is simple example to demonstrate how you might use CardLayout to perform decoupled navigation
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
CardLayout cardLayout = new CardLayout();
JPanel base = new JPanel(cardLayout);
NavigationController controller = new NavigationController() {
#Override
public void show(Screen screen) {
cardLayout.show(base, screen.name());
}
};
base.add(new MainMenuPane(controller), Screen.MENU.name());
base.add(new GamePane(controller), Screen.GAME.name());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(base);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum Screen {
MENU, GAME;
}
public interface NavigationController {
public void show(Screen scren);
}
public class MainMenuPane extends JPanel {
public MainMenuPane(NavigationController controller) {
setLayout(new GridBagLayout());
JButton start = new JButton("Start");
add(start);
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
controller.show(Screen.GAME);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class GamePane extends JPanel {
private NavigationController controller;
public GamePane(NavigationController controller) {
this.controller = controller;
setLayout(new GridBagLayout());
add(new JLabel("Ready Player One"));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Every thing else you'll need to fix
Don't use KeyListener, use Key bindings instead, they will fix the focus related issues in a more reliable way
You're violating the requirements of the paint chain, which is part of your immediate problem - See Performing Custom Painting and Painting in Swing for more details about how painting works and how you should work with it
Swing is single thread AND not thread safe - see Concurrency in Swing for more details. Essentially the use of java.util.Timer is running the risk of dirty read/writes which could lead to any number of weird and near impossible to diagnose issues. Instead, you should be using Swing Timer instead, which will ensure that any updates you make are made within the context of the Event Dispatching Thread. You should also be using a single Timer and scheduling everything in a simple update pass - this will generally improve performance

paintComponent using old instances

I'm making a dice rolling program in java using swing. I've got 4 classes:
Die
public class Die{
private int faceValue;
public Die(){
System.out.println("Creating new Dice Object");
setValue(roll());
}
public int roll() {
int val = (int) (6 * Math.random() + 1);
setValue(val);
return val;
}
public int getValue() {
return faceValue;
}
public void setValue(int spots) {
faceValue = spots;
}
}
DieFace
public class DieFace {
private int spotDiam,wOffset,hOffset,w,h;
public int faceValue;
public DieFace(){
Die die = new Die();
this.faceValue = die.getValue();
}
public void draw(Graphics g, int paneWidth, int paneHeight){
//draw information
}
}
DieFaceComponent
public class DieFaceComponent extends JComponent{
private static final long serialVersionUID = 1L;
DieFace face;
public DieFaceComponent(){
face = new DieFace();
System.out.println("DIEFACE" + face.faceValue);
repaint();
}
public void paintComponent(Graphics g){
revalidate();
face.draw(g,super.getWidth(),super.getHeight());
}
}
DieFaceViewer
public class DieFaceViewer{
static DieFaceComponent component;
static JFrame frame = new JFrame(); // Create a new JFrame object
public static void main(String[] args){
final int FRAME_WIDTH = 500;
final int FRAME_HEIGHT = 500;
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT); // Set initial size
frame.setTitle("Dice Simulator Version 1.0"); // Set title
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Set default close operation
component = new DieFaceComponent(); // Create a new DieFaceComponent object
frame.setLayout(new BorderLayout());
JButton btnRoll = new JButton("Roll!");
btnRoll.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
component = new DieFaceComponent();
}
});
frame.add(component, BorderLayout.CENTER); // Add DieFaceComponent object to frame
frame.add(btnRoll, BorderLayout.SOUTH);
frame.setVisible(true); // Set frame to visible
}
}
My problem is that even though a new Die, DieFace and DieFaceComponent object is created every time I press my btnRoll, the value used to draw the component stays the same as the initial instance. Is there something I've done wrong? Thanks in advance
You create a new instance of DieFaceComponent in your ActionListener but do nothing with it, it's never added to anything, so it's never visible. A better solution would allow you to trigger a change to DieFaceComponent, which triggered a change to DieFace which triggered a change to Die and have the whole thing just repaint itself, for example...
public class Die {
private int faceValue;
public Die() {
System.out.println("Creating new Dice Object");
//setValue(roll());
roll(); // Roll sets the value any way :P
}
public int roll() {
int val = (int) (6 * Math.random() + 1);
setValue(val);
return val;
}
public int getValue() {
return faceValue;
}
public void setValue(int spots) {
faceValue = spots;
}
}
public class DieFace {
private int spotDiam, wOffset, hOffset, w, h;
//public int faceValue;
private Die die;
public DieFace() {
die = new Die();
//Die die = new Die();
// This is pointless, as you should simply as die for it's value
// when ever you need it...
//this.faceValue = die.getValue();
}
public void roll() {
die.roll();
}
public void draw(Graphics g, int paneWidth, int paneHeight) {
//draw information
}
}
public class DieFaceComponent extends JComponent {
private static final long serialVersionUID = 1L;
DieFace face;
public DieFaceComponent() {
face = new DieFace();
//System.out.println("DIEFACE" + face.faceValue);
// Pointless, as you've probably not actually been added to anything
// that could actuallyt paint you anyway...
//repaint();
}
public void roll() {
face.roll();
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//revalidate();
face.draw(g, super.getWidth(), super.getHeight());
}
}
Now, you can call roll on DieFaceComponent, which will call roll on DieFace which will call roll on Die, which will update the actual value. DieFaceComponent will then schedule a repaint to ensure that it's update on the screen.
And then you could use it something like...
import java.awt.BorderLayout;
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 javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DiceRoller {
public static void main(String[] args) {
new DiceRoller();
}
public DiceRoller() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DiePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DiePane extends JPanel {
public DiePane() {
setLayout(new BorderLayout());
DieFaceComponent component = new DieFaceComponent();
JButton roll = new JButton("Roll");
roll.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
component.roll();
}
});
add(component);
add(roll, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Now, a better solution would be to have Die as your primary entry point, allowing it to generate notifications to interested parties and having them update themselves
Maybe something like...
import java.awt.BorderLayout;
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.beans.PropertyChangeSupport;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DiceRoller {
public static void main(String[] args) {
new DiceRoller();
}
public DiceRoller() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DiePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DiePane extends JPanel {
public DiePane() {
setLayout(new BorderLayout());
Die die = new Die();
DieFaceComponent component = new DieFaceComponent(die);
JButton roll = new JButton("Roll");
roll.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
die.roll();
}
});
add(component);
add(roll, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.dispose();
}
}
public class Die {
private PropertyChangeSupport propertyChangeSupport;
private int faceValue;
public Die() {
propertyChangeSupport = new PropertyChangeSupport(this);
System.out.println("Creating new Dice Object");
//setValue(roll());
roll(); // Roll sets the value any way :P
}
public int roll() {
int val = (int) (6 * Math.random() + 1);
setValue(val);
return val;
}
public int getValue() {
return faceValue;
}
public void setValue(int spots) {
int old = faceValue;
faceValue = spots;
propertyChangeSupport.firePropertyChange("value", old, faceValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
}
public class DieFace {
private int spotDiam, wOffset, hOffset, w, h;
private Die die;
public DieFace(Die die) {
this.die = die
}
public void draw(Graphics g, int paneWidth, int paneHeight) {
//draw information
}
}
public class DieFaceComponent extends JComponent {
private static final long serialVersionUID = 1L;
private DieFace face;
public DieFaceComponent(Die die) {
face = new DieFace(die);
die.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//revalidate();
face.draw(g, super.getWidth(), super.getHeight());
}
}
}
This is a simple example of an Observer Pattern, where the Die is the generator of information, to which every body else is interested in knowing when it changes. It's also a variant of the model-view-controller paradigm

Java - (NetBeans) Make a JTextArea Slide Out From Behind JFrame

I have just written a program in Netbeans that moves/copies/deletes files, and I wanted to give it a "diagnostic mode" where information about selected files, folders, variables, etc is displayed in a Text Area. Now, I could set this to only be visible when the "diagnostic mode" toggle is selected, but I think it would look awesome if the text area started behind the program, and "slid" out from behind the JFrame when the button is toggled. Is there any way to do this?
Thanks!
-Sean
Here is some starter code for you. This will right-slide a panel
of just about any content type. Tweak as necessary. Add error
checking and exception handling.
Tester:
static public void main(final String[] args) throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
final JPanel slider = new JPanel();
slider.setLayout(new FlowLayout());
slider.setBackground(Color.RED);
slider.add(new JButton("test"));
slider.add(new JButton("test"));
slider.add(new JTree());
slider.add(new JButton("test"));
slider.add(new JButton("test"));
final CpfJFrame42 cpfJFrame42 = new CpfJFrame42(slider, 250, 250);
cpfJFrame42.slide(CpfJFrame42.CLOSE);
cpfJFrame42.setSize(300, 300);
cpfJFrame42.setLocationRelativeTo(null);
cpfJFrame42.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
cpfJFrame42.setVisible(true);
}
});
}
Use GAP & MIN to adjust spacing from JFrame and closed size.
The impl is a little long...it uses a fixed slide speed but
that's one the tweaks you can make ( fixed FPS is better ).
package com.java42.example.code;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
public class CpfJFrame42 extends JFrame {
public static int GAP = 5;
public static int MIN = 1;
public static final int OPEN = 0x01;
public static final int CLOSE = 0x02;
private JDialog jWindow = null;
private final JPanel basePanel;
private final int w;
private final int h;
private final Object lock = new Object();
private final boolean useSlideButton = true;
private boolean isSlideInProgress = false;
private final JPanel glassPane;
{
glassPane = new JPanel();
glassPane.setOpaque(false);
glassPane.addMouseListener(new MouseAdapter() {
});
glassPane.addMouseMotionListener(new MouseMotionAdapter() {
});
glassPane.addKeyListener(new KeyAdapter() {
});
}
public CpfJFrame42(final Component component, final int w, final int h) {
this.w = w;
this.h = h;
component.setSize(w, h);
addComponentListener(new ComponentListener() {
#Override
public void componentShown(final ComponentEvent e) {
}
#Override
public void componentResized(final ComponentEvent e) {
locateSlider(jWindow);
}
#Override
public void componentMoved(final ComponentEvent e) {
locateSlider(jWindow);
}
#Override
public void componentHidden(final ComponentEvent e) {
}
});
jWindow = new JDialog(this) {
#Override
public void doLayout() {
if (isSlideInProgress) {
}
else {
super.doLayout();
}
}
};
jWindow.setModal(false);
jWindow.setUndecorated(true);
jWindow.setSize(component.getWidth(), component.getHeight());
jWindow.getContentPane().add(component);
locateSlider(jWindow);
jWindow.setVisible(true);
if (useSlideButton) {
basePanel = new JPanel();
basePanel.setLayout(new BorderLayout());
final JPanel statusPanel = new JPanel();
basePanel.add(statusPanel, BorderLayout.SOUTH);
statusPanel.add(new JButton("Open") {
private static final long serialVersionUID = 9204819004142223529L;
{
setMargin(new Insets(0, 0, 0, 0));
}
{
addActionListener(new ActionListener() {
#Override
public void actionPerformed(final ActionEvent e) {
slide(OPEN);
}
});
}
});
statusPanel.add(new JButton("Close") {
{
setMargin(new Insets(0, 0, 0, 0));
}
private static final long serialVersionUID = 9204819004142223529L;
{
addActionListener(new ActionListener() {
#Override
public void actionPerformed(final ActionEvent e) {
slide(CLOSE);
}
});
}
});
{
//final BufferedImage bufferedImage = ImageFactory.getInstance().createTestImage(200, false);
//final ImageIcon icon = new ImageIcon(bufferedImage);
//basePanel.add(new JButton(icon), BorderLayout.CENTER);
}
getContentPane().add(basePanel);
}
}
private void locateSlider(final JDialog jWindow) {
if (jWindow != null) {
final int x = getLocation().x + getWidth() + GAP;
final int y = getLocation().y + 10;
jWindow.setLocation(x, y);
}
}
private void enableUserInput() {
getGlassPane().setVisible(false);
}
private void disableUserInput() {
setGlassPane(glassPane);
}
public void slide(final int slideType) {
if (!isSlideInProgress) {
isSlideInProgress = true;
final Thread t0 = new Thread(new Runnable() {
#Override
public void run() {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
disableUserInput();
slide(true, slideType);
enableUserInput();
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
isSlideInProgress = false;
}
});
t0.setDaemon(true);
t0.start();
}
else {
Toolkit.getDefaultToolkit().beep();
}
}
private void slide(final boolean useLoop, final int slideType) {
synchronized (lock) {
for (int x = 0; x < w; x += 25) {
if (slideType == OPEN) {
jWindow.setSize(x, h);
}
else {
jWindow.setSize(getWidth() - x, h);
}
jWindow.repaint();
try {
Thread.sleep(42);
} catch (final Exception e) {
e.printStackTrace();
}
}
if (slideType == OPEN) {
jWindow.setSize(w, h);
}
else {
jWindow.setSize(MIN, h);
}
jWindow.repaint();
}
}
}

JPanel listeners and threads issues

Here is the code for displaying circles with varying radius on a panel inside a frame with a given delay rate, but the code is showing the final output not the intermediate stages i.e., the circles are not appearing one by one but all the circles are coming at once as a final output. There may be some errors related to button action listeners and panel threads. The code is taking initial circle radius and the total number of iterations (the total number of circles to be displayed), radius of each next circle gets incremented by 10.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ControlCircle extends JFrame {
private JButton jbtEnlarge = new JButton("Start");
private JButton jbtShrink = new JButton("Stop");
private CirclePanel canvas = new CirclePanel();
private int radius = 0;
private int iter;
public ControlCircle() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtEnlarge);
panel.add(jbtShrink);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
final JTextField f1 = new JTextField(8),f2 = new JTextField(8);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
f1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
radius = Integer.parseInt(new String(f1.getText()));
}
});
f2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
iter = Integer.parseInt(new String(f2.getText()));
}
});
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
public static void main(String[] args) {
JFrame frame = new ControlCircle();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class EnlargeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
canvas.enlarge();
}
}
class ShrinkListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//canvas.shrink();
}
}
class CirclePanel extends JPanel {
private int r = radius;
public void enlarge() {
//radius += 2;
repaint();
}
public void shrink() {
radius -= 2;
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < iter; i++) {
g.drawOval(getWidth() / 2 - r, getHeight() / 2 - r, 2 * r, 2 * r);
try {
Thread.sleep(100);
} catch (Exception exp) {
}
r = r + 10;
}
r = 0;
}
}
}
The problem you're having is far to common.
Swing is a single threaded framework. This means that all UI related interactions must occur within the context of this thread (AKA the Event Dispatching Thread).
The EDT is responsible for, amongst other things, dispatching repaint requests. If any part of your code stops this thread (block I/O, time consuming process, Thread.sleep), the EDT will be unable to process any new events.
Have a read through Concurrency in Swing for more details.
You now face two issues...
You can't block the EDT
You can't update the UI from any thread other then the EDT.
Luckily, there are a number of solutions. The simplest is using a javax.swing.Timer.
This timer triggers it's tick events within the EDT but waits within it's own thread...
import com.sun.org.apache.bcel.internal.generic.LSTORE;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Droplets {
public static void main(String[] args) {
new Droplets();
}
public Droplets() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new DropletPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected static final int MAX_RADIUS = 50;
protected static final int GROWTH_RATE = 1;
public class DropletPane extends JPanel {
private List<Droplet> droplets;
public DropletPane() {
droplets = new ArrayList<>(25);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
droplets.add(new Droplet(e.getPoint()));
}
});
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
droplet.grow();
if (droplet.getRadius() >= MAX_RADIUS) {
droplets.remove(droplet);
}
}
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Composite comp = g2d.getComposite();
for (Droplet droplet : droplets) {
float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
Point p = droplet.getLocation();
int radius = droplet.getRadius();
g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
g2d.setComposite(comp);
}
g2d.dispose();
}
}
public class Droplet {
private Point p;
private int radius;
public Droplet(Point p) {
this.p = p;
}
public Point getLocation() {
return p;
}
public int getRadius() {
return radius;
}
public void grow() {
radius += GROWTH_RATE;
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
}
}
}
Extended Example
This example will, when you click the "Start" button, create a random number of droplets at a random interval (between each droplet). You can press start multiple times and it will compound the output.
import static droplets.Droplets.MAX_RADIUS;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Droplets02 {
public static void main(String[] args) {
new Droplets02();
}
public Droplets02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new DropletPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected static final int MAX_RADIUS = 50;
protected static final int GROWTH_RATE = 1;
public interface Pool {
public void addDroplet(Droplet droplet);
public Dimension getSize();
}
public class DropletPane extends JPanel implements Pool {
private List<Droplet> droplets;
private Timer timer;
public DropletPane() {
setLayout(new GridBagLayout());
JButton button = new JButton("Start");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new DropletWorker(DropletPane.this).execute();
}
});
add(button);
droplets = new ArrayList<>(25);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!droplets.isEmpty()) {
for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
droplet.grow();
if (droplet.getRadius() >= MAX_RADIUS) {
droplets.remove(droplet);
}
}
if (droplets.isEmpty()) {
((Timer) e.getSource()).stop();
}
repaint();
}
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Composite comp = g2d.getComposite();
for (Droplet droplet : droplets) {
float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
Point p = droplet.getLocation();
int radius = droplet.getRadius();
g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
g2d.setComposite(comp);
}
g2d.dispose();
}
#Override
public void addDroplet(Droplet droplet) {
if (!timer.isRunning()) {
timer.start();
}
droplets.add(droplet);
}
}
public class Droplet {
private Point p;
private int radius;
public Droplet(Point p) {
this.p = p;
}
public Point getLocation() {
return p;
}
public int getRadius() {
return radius;
}
public void grow() {
radius += GROWTH_RATE;
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
}
}
public class DropletWorker extends SwingWorker<Void, Droplet> {
private Pool pool;
public DropletWorker(Pool pool) {
this.pool = pool;
}
public Pool getPool() {
return pool;
}
protected int random(int minRange, int maxRange) {
return minRange + (int) (Math.round(Math.random() * (maxRange - minRange)));
}
#Override
protected Void doInBackground() throws Exception {
int dropCount = random(1, 100);
Pool pool = getPool();
Dimension size = pool.getSize();
for (int index = 0; index < dropCount; index++) {
Thread.sleep(random(10, 1000));
int x = random(0, size.width);
int y = random(0, size.height);
Droplet droplet = new Droplet(new Point(x, y));
publish(droplet);
}
return null;
}
#Override
protected void process(List<Droplet> chunks) {
for (Droplet droplet : chunks) {
getPool().addDroplet(droplet);
}
}
}
}
Animation Basics
You need three things to perform animation.
A Start state
A Target state
A delta or time range.
(You also need some way to store the current state)
The start and target states are self explanatory, they describe where you are now and where you want to change to.
The delta would be the amount to apply to the current state at each "time interval" (or tick) until you reach the delta.
Or
The time range would be the amount of time you want to use to move from the start state to the end state.
The delta approach is the simpler mechanism, but isn't nearly as flexible as the time range approach...
Once you have these basic elements set up, you need some kind of "tick" that is triggered at regular intervals which allows you to calculate the current state, which is either a linear movement from the start state to the target state (delta) or a progression of change of over time (time range)
A final, full working rework
Apart from you're attempt to block the EDT within the paint method and failing to following the Initial Thread requirements of Swing, the only other, significant, problem I found was your reliance on the radius and iter values.
Basically, these were never getting set UNLESS you pressed the Enter key...which I wasn't.
This example uses the code that you posted and the ideas from the first example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ControlCircles extends JFrame {
private JButton jbtEnlarge = new JButton("Start");
private JButton jbtShrink = new JButton("Stop");
private CirclePanel canvas = new CirclePanel();
private JTextField f1 = new JTextField(8);
private JTextField f2 = new JTextField(8);
public ControlCircles() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtEnlarge);
panel.add(jbtShrink);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new ControlCircles();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class EnlargeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
int radius = Integer.parseInt(f1.getText());
int iter = Integer.parseInt(f2.getText());
canvas.start(radius, iter);
}
}
class ShrinkListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//canvas.shrink();
}
}
class CirclePanel extends JPanel {
private int radius;
private int iterations;
private int iteration;
private List<Integer> circles;
private Timer timer;
public CirclePanel() {
circles = new ArrayList<>(25);
timer= new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
iteration++;
if (iteration < iterations) {
circles.add(radius);
radius += 10;
} else {
((Timer)e.getSource()).stop();
}
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth() - 1;
int height = getHeight()- 1;
g.drawRect(0, 0, width, height);
for (Integer radius : circles) {
int x = (width - radius) / 2;
int y = (height - radius) / 2;
g.drawOval(x, y, radius, radius);
}
}
public void start(int radius, int iter) {
timer.stop();
circles.clear();
this.radius = radius;
iterations = iter;
iteration = 0;
System.out.println("radius = " + radius);
System.out.println("iterations = " + iterations);
timer.start();
}
}
}
This code works based on the description of your problem by correcting the common mistakes with animation in Swing but some of your code didn't quite make sense to me (ie enlarge and shrink) so I focused on the description your provided.
The idea is to control the drawing animation on the panel used as a canvas with the buttons Start, Stop and I added Continue and Reset additional controls to better explain the idea. These buttons control the animation thread execution thus drawing circles on the drawing surface. the drawing surface I separated as inner class that has only function to draw whatever performed. Another idea that the approach is taken to draw the circles one by one incrementally until it finishes drawing thus used incremental painting.
I have used the code from the above and changed it a little to support my ideas. If you need more and usually better examples look at this article.
The code is below, I didn't polish it enough to have a production wise look and feel but for demonstration purpose only.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
public class ControlCircle extends JFrame implements Runnable {
private JButton jbtStart = new JButton("Start");
private JButton jbtStop = new JButton("Stop");
private JButton jbtContinue = new JButton("Continue");
private JButton jbtReset = new JButton("Reset");
private CirclePanel canvas = new CirclePanel();
private JTextField f1;
private int radius = 0;
private JTextField f2;
private int iter;
protected boolean incrementalPainting;
/**
* Flag indicates that a thread is suspended
*/
private boolean suspended = false;
/**An instance of the class Thread.*/
private Thread thread = null;
public ControlCircle() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtStart);
panel.add(jbtStop);
panel.add(jbtContinue);
panel.add(jbtReset);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
f1 = new JTextField(8);
f2 = new JTextField(8);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
jbtStart.addActionListener(new StartListener());
jbtStop.addActionListener(new StopListener());
jbtContinue.addActionListener(new ContinueListener());
jbtReset.addActionListener(new ResetListener());
}
public static void main(String[] args) {
JFrame frame = new ControlCircle();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread == null) {
repaint();
startThread();
}
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread != null){
mySuspend();
}
}
}
class ContinueListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
myResume();
}
}
class ResetListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread != null) {
stopThread();
}
repaint();
}
}
/**
* my Suspend
*/
private void mySuspend() {
System.out.println("mySyspend()");
suspended = true;
}
/**
* my Resume
*/
private synchronized void myResume(){
System.out.println("myResume()");
suspended = false;
notifyAll();
}
public void run(){
System.out.println("run() - started");
Thread me = Thread.currentThread();
while (thread == me) {
radius = Integer.parseInt(f1.getText());
iter = Integer.parseInt(f2.getText());
for (int i = 0; i < iter; i++) {
if (thread == null) return;
incrementalPainting = true;
myRepaint();
try {
Thread.sleep(1000);
}
catch(InterruptedException e){}
radius += 10;
}
if(thread != null) thread = null; // exiting while
}
System.out.println("run() - exiting");
}
/**
* start Thread
*/
private void startThread(){
System.out.println("startThread()");
if(thread == null){
thread = new Thread(this);
thread.start();
}
}
/**
* stop Thread
*/
private synchronized void stopThread() {
System.out.println("stopThread()");
thread = null; // exiting from while
if (suspended) {
suspended = false;
notify();
}
}
/**
* This is called from the run method to invoke painting.
*/
private void myRepaint() {
System.out.println("myRepaint()");
incrementalPainting = true;
repaint();
synchronized (this) {
while (incrementalPainting) {
System.out.println("wait while incremental painting");
try {
wait();
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
suspend();
}
/**
* This method should place somewhere when run() has started. Perfectly
* when repaint() performed.
*/
private void suspend(){
System.out.println("suspend()");
synchronized (this) {
while (suspended) {
System.out.println("wait while suspended");
try {
wait();
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
}
public synchronized void myPaint(Graphics g) {
if (g == null){
if (incrementalPainting){
incrementalPainting = false;
notifyAll();
}
return;
}
if (incrementalPainting){
myDraw(g);
incrementalPainting = false;
notifyAll();
}
else {
myDraw(g);
}
}
public void myDraw(Graphics g){
g.drawOval(getWidth() / 2 - radius, getHeight() / 2 - radius, 2 * radius, 2 * radius);
}
protected final class CirclePanel extends JPanel {
//Offscreen buffer of this canvas
private BufferedImage backBuffer = null;
public void paintComponent (Graphics g) {
System.out.println("incrementalPainting="+incrementalPainting);
// First paint background
super.paintComponent(g);
Dimension d = this.getSize();
if (! incrementalPainting)
backBuffer = (BufferedImage) this.createImage(d.width, d.height);
Graphics2D g2 = backBuffer.createGraphics();
if (! incrementalPainting){
g2.setColor(Color.WHITE);
g2.fillRect(0,0, d.width, d.height);
}
myPaint(g2);
g.drawImage(backBuffer, 0, 0, this);
}
}
}

Categories

Resources