Update LookAndFeel Values On The Fly - java

I want to be able to update the LookAndFeel attributes of my Swing GUI on the fly. In this case, I have a simple Swing/Awt game running what starts out as the Nimbus LookAndFeel. At various points after start up I want to change (let us say) just one detail: the background color of my application.
I can change the background color by doing this:
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
UIManager.getLookAndFeelDefaults().put("Panel.background", Color.RED);
SwingUtilities.updateComponentTreeUI(SomeGame.this);
break;
}
}
This "works" in that the background color of the app changes correctly and the program doesn't crash. But on the command line I get error:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at javax.swing.plaf.synth.SynthLookAndFeel.paintRegion(SynthLookAndFeel.java:371)
at javax.swing.plaf.synth.SynthLookAndFeel.update(SynthLookAndFeel.java:335)
Obviously, something is null, but I can not figure out what it is or how to fix it. There must be something I don't understand. I have looked at other StackOverflow questions about setting background colors in Nimbus and overriding the LookAndFeel information after startup.
When I call getLookAndFeelDefaults() do I need to specify the rest of the defaults as well?
Have there been changes in how this works between Java 1.6 and 1.7?

all code for Java6, have to change Nimbus imports for Java7
Nimbus has a few suprises, one of them is that is possible (without dirty hacks, I'm leaving comments for those hacks) to change Background for JPanel only one time
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Color;
import java.awt.AlphaComposite;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.border.EmptyBorder;
public class ButtonTest {
private JFrame frame;
private JPanel panel;
private JButton opaqueButton1;
private JButton opaqueButton2;
private SoftJButton softButton1;
private SoftJButton softButton2;
private Timer alphaChanger;
public void createAndShowGUI() {
opaqueButton1 = new JButton("Opaque Button");
opaqueButton2 = new JButton("Opaque Button");
softButton1 = new SoftJButton("Transparent Button");
softButton2 = new SoftJButton("Transparent Button");
opaqueButton1.setBackground(Color.GREEN);
softButton1.setBackground(Color.GREEN);
panel = new JPanel();
panel.setLayout(new java.awt.GridLayout(2, 2, 10, 10));
panel.add(opaqueButton1);
panel.add(softButton1);
panel.add(opaqueButton2);
panel.add(softButton2);
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
frame = new JFrame();
frame.add(panel);
frame.setLocation(150, 100);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
alphaChanger = new Timer(50, new ActionListener() {
private float incrementer = -.03f;
#Override
public void actionPerformed(ActionEvent e) {
float newAlpha = softButton1.getAlpha() + incrementer;
if (newAlpha < 0) {
newAlpha = 0;
incrementer = -incrementer;
} else if (newAlpha > 1f) {
newAlpha = 1f;
incrementer = -incrementer;
}
softButton1.setAlpha(newAlpha);
softButton2.setAlpha(newAlpha);
}
});
alphaChanger.start();
Timer uiChanger = new Timer(3500, new ActionListener() {
private final LookAndFeelInfo[] laf = UIManager.getInstalledLookAndFeels();
private int index = 1;
#Override
public void actionPerformed(ActionEvent e) {
try {
UIManager.setLookAndFeel(laf[index].getClassName());
if (laf[index].getClassName().equals("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel")) {
UIManager.getLookAndFeelDefaults().put("Panel.background", Color.orange);
SwingUtilities.updateComponentTreeUI(frame);
} else {
panel.setBackground(Color.yellow);
SwingUtilities.updateComponentTreeUI(frame);
}
opaqueButton1.setText(laf[index].getClassName());
softButton1.setText(laf[index].getClassName());
frame.pack();
} catch (Exception exc) {
exc.printStackTrace();
}
index = (index + 1) % laf.length;
}
});
uiChanger.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ButtonTest().createAndShowGUI();
}
});
}
private static class SoftJButton extends JButton {
private static final JButton lafDeterminer = new JButton();
private static final long serialVersionUID = 1L;
private boolean rectangularLAF;
private float alpha = 1f;
SoftJButton() {
this(null, null);
}
SoftJButton(String text) {
this(text, null);
}
SoftJButton(String text, Icon icon) {
super(text, icon);
setOpaque(false);
setFocusPainted(false);
}
public float getAlpha() {
return alpha;
}
public void setAlpha(float alpha) {
this.alpha = alpha;
repaint();
}
#Override
public void paintComponent(java.awt.Graphics g) {
java.awt.Graphics2D g2 = (java.awt.Graphics2D) g;
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
if (rectangularLAF && isBackgroundSet()) {
Color c = getBackground();
g2.setColor(c);
g.fillRect(0, 0, getWidth(), getHeight());
}
super.paintComponent(g2);
}
#Override
public void updateUI() {
super.updateUI();
lafDeterminer.updateUI();
rectangularLAF = lafDeterminer.isOpaque();
}
}
}
but for JComponent it is possible to change its Color, Font, etc by calling UIManager.getLookAndFeel().uninitialize();, but this does not work for containers
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;
public class NimbusTestButtonsBackground extends JFrame {
private static final long serialVersionUID = 1L;
private javax.swing.JButton button;
public NimbusTestButtonsBackground() {
button = new javax.swing.JButton();
button.setText("Text");
add(button);
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
this.pack();
Timer t = new Timer(1000, new ActionListener() {
private Random r = new Random();
#Override
public void actionPerformed(ActionEvent e) {
Color c = new Color(r.nextInt(256), r.nextInt(256), r.nextInt(256));
try {
LookAndFeel lnf = UIManager.getLookAndFeel().getClass().newInstance();
UIDefaults uiDefaults = lnf.getDefaults();
uiDefaults.put("nimbusBase", c);
UIManager.getLookAndFeel().uninitialize();
UIManager.setLookAndFeel(lnf);
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
UIDefaults defaults = UIManager.getDefaults();
defaults.put("Button.background", c);
SwingUtilities.updateComponentTreeUI(button);
}
});
t.start();
}
public static void main(String args[]) {
try {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
return;
}
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new NimbusTestButtonsBackground().setVisible(true);
}
});
}
}
then I see (this way as most comfortable, easiest, etc..) overriding paintComponent for JPanel as best the best way
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
public class ChangePanelBackgroundNimbus {
private JFrame frame = new JFrame();
public ChangePanelBackgroundNimbus() {
GradientPane pane = new GradientPane();
pane.setLayout(new GridLayout(6, 4, 10, 10));
for (int i = 1; i <= 24; i++) {
pane.add(createButton(i));
}
pane.setOpaque(false);
frame.add(pane);
RepaintManager.setCurrentManager(new RepaintManager() {
#Override
public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
Container con = c.getParent();
while (con instanceof JComponent) {
if (!con.isVisible()) {
return;
}
if (con instanceof GradientPane) {
c = (JComponent) con;
x = 0;
y = 0;
w = con.getWidth();
h = con.getHeight();
}
con = con.getParent();
}
super.addDirtyRegion(c, x, y, w, h);
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
//frame.setSize(400, 300);
frame.pack();
frame.setVisible(true);
}
private JButton createButton(final int text) {
JButton button = new JButton(Integer.toString(text));
return button;
}
class GradientPane extends JPanel {
private static final long serialVersionUID = 1L;
private final int h = 150;
private BufferedImage img = null;
private BufferedImage shadow = new BufferedImage(1, h, BufferedImage.TYPE_INT_ARGB);
public GradientPane() {
paintBackGround(new Color(150, 250, 150));
Timer t = new Timer(500, new ActionListener() {
private Random r = new Random();
#Override
public void actionPerformed(ActionEvent e) {
paintBackGround(new Color(r.nextInt(256), r.nextInt(256), r.nextInt(256)));
}
});
t.start();
}
public void paintBackGround(Color g) {
Graphics2D g2 = shadow.createGraphics();
g2.setPaint(g);
g2.fillRect(0, 0, 1, h);
g2.setComposite(AlphaComposite.DstIn);
g2.setPaint(new GradientPaint(0, 0, new Color(0, 0, 0, 0f), 0, h, new Color(0.4f, 0.8f, 0.8f, 0.5f)));
g2.fillRect(0, 0, 1, h);
g2.dispose();
}
#Override
public void paintComponent(Graphics g) {
if (img == null || img.getWidth() != getWidth() || img.getHeight() != getHeight()) {
img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
}
Graphics2D g2 = img.createGraphics();
super.paintComponent(g2);
Rectangle bounds = this.getVisibleRect();
g2.scale(bounds.getWidth(), -1);
g2.drawImage(shadow, bounds.x, -bounds.y - h, null);
g2.scale(1, -1);
g2.drawImage(shadow, bounds.x, bounds.y + bounds.height - h, null);
g2.dispose();
g.drawImage(img, 0, 0, null);
repaint();
}
}
public static void main(String[] args) {
try {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ChangePanelBackgroundNimbus ml = new ChangePanelBackgroundNimbus();
}
});
}
}

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

GUI doesn't respond to mouse click and drag while running from another class

I have 2 classes: TestingPanel and SnipIt.
SnipIt is used for selecting an area on the screen.
TestingPanel is the main frame, containing a button to run method Snip() and receiving the return values.
If I test the SnipIt class separately, it works. But if I create a SnipIt object and run method Snip() from TestingPanel class, it doesn't work. The GUI just freezes and doesn't respond to mouse click or drag event. I guess something block the thread handle the mouse events but I'm not sure.
I have been stuck for couple hours and still don't know what caused the issue. Please help.
TestingPanel
package Testing;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class TestingPanel {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TestingPanel window = new TestingPanel();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public TestingPanel() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 200, 160);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
JButton btnSnip = new JButton("Snip");
btnSnip.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
getSelectionSize();
}
});
btnSnip.setBounds(47, 87, 89, 23);
frame.getContentPane().add(btnSnip);
}
private void getSelectionSize() {
int[] size = new int[4];
Thread worker = new Thread(new Runnable() {
public void run() {
SnipIt sn = new SnipIt();
sn.snip();
while(!sn.complete) {
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size[0] = sn.returnSize()[0];
size[1] = sn.returnSize()[1];
size[2] = sn.returnSize()[2];
size[3] = sn.returnSize()[3];
}
});
worker.start();
try {
worker.join();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(size[0] + " " + size[1] + " " + size[2] + " " + size[3]);
}
}
SnipIt
package Testing;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class SnipIt {
private int recX = 0;
private int recY = 0;
private int recWidth = 0;
private int recHeight = 0;
public boolean complete = false;
/*
public static void main(String [] args)
{
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
SnipIt s = new SnipIt();
s.snip();
}
});
}
*/
public void snip() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 0));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new SnipItPane());
frame.setBounds(getVirtualBounds());
frame.setVisible(true);
}
#SuppressWarnings("serial")
public class SnipItPane extends JPanel {
private Point mouseAnchor;
private Point dragPoint;
private SelectionPane selectionPane;
private ControlPane controlPane;
public SnipItPane() {
setOpaque(false);
setLayout(null);
selectionPane = new SelectionPane();
controlPane = new ControlPane();
add(selectionPane);
add(controlPane);
MouseAdapter adapter = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
mouseAnchor = e.getPoint();
dragPoint = null;
selectionPane.setLocation(mouseAnchor);
selectionPane.setSize(0, 0);
controlPane.setLocation(mouseAnchor);
controlPane.setSize(0, 0);
}
#Override
public void mouseDragged(MouseEvent e) {
dragPoint = e.getPoint();
int width = dragPoint.x - mouseAnchor.x;
int height = dragPoint.y - mouseAnchor.y;
int x = mouseAnchor.x;
int y = mouseAnchor.y;
if (width < 0) {
x = dragPoint.x;
width *= -1;
}
if (height < 0) {
y = dragPoint.y;
height *= -1;
}
selectionPane.setBounds(x, y, width, height);
selectionPane.revalidate();
int controlY = y + height + 5;
controlPane.setBounds(x, controlY, width, 25);
controlPane.revalidate();
repaint();
}
};
addMouseListener(adapter);
addMouseMotionListener(adapter);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
Area area = new Area(bounds);
area.subtract(new Area(selectionPane.getBounds()));
g2d.setColor(new Color(102, 102, 102, 80));
g2d.fill(area);
}
}
#SuppressWarnings("serial")
public class ControlPane extends JPanel {
private JButton btnClose;
public ControlPane() {
setOpaque(false);
btnClose = new JButton("Save");
setLayout(new BorderLayout());
this.add(btnClose, BorderLayout.NORTH);
btnClose.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
complete = true;
SwingUtilities.getWindowAncestor(ControlPane.this).dispose();
}
});
}
}
#SuppressWarnings("serial")
public class SelectionPane extends JPanel {
public SelectionPane() {
setOpaque(false);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
recX = getX();
recY = getY();
recWidth = getWidth();
recHeight = getHeight();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
float strokeWidth = 1.0f;
float dash1[] = {10.0f};
BasicStroke dashed =
new BasicStroke(strokeWidth,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, dash1, 0.0f);
g2d.setColor(Color.BLACK);
g2d.setStroke(dashed);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.dispose();
}
}
public static Rectangle getVirtualBounds() {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice lstGDs[] = ge.getScreenDevices();
for (GraphicsDevice gd : lstGDs) {
bounds.add(gd.getDefaultConfiguration().getBounds());
}
return bounds;
}
public int[] returnSize() {
int[] size = new int[4];
size[0] = recX;
size[1] = recY;
size[2] = recWidth;
size[3] = recHeight;
return size;
}
}
You're running the Snipit application in a background thread and then freezing that thread with Thread.sleep and a while true block, something guaranteed to freeze the GUI. Read Lesson: Concurrency in Swing and then be sure to always run Swing applications on the single Swing event thread, and do any long running or sleeping code in a background thread.
Possible solutions to your issue:
Make the Snipit window an undecorated modal dialog. This way program flow from the calling code stops when the dialog is visible and resumes when no longer visible.
Or Make the Snipit window JFrame an instance field of the class and allow outside classes to add listeners to it so that they will be notified when it closes.
e.g.,
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dialog.ModalityType;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class TestSnipit {
private static void createAndShowGui() {
boolean runTest = true;
if (runTest) {
TestingPanel.main(null);
} else {
SnipIt.main(null);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class TestingPanel {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TestingPanel window = new TestingPanel();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public TestingPanel() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 200, 160);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
JButton btnSnip = new JButton("Snip");
btnSnip.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
getSelectionSize();
}
});
btnSnip.setBounds(47, 87, 89, 23);
frame.getContentPane().add(btnSnip);
}
private void getSelectionSize() {
int[] size = new int[4];
// !!
SnipIt sn = new SnipIt();
sn.snip(frame);
// Thread worker = new Thread(new Runnable() {
// public void run() {
// SnipIt sn = new SnipIt();
// sn.snip();
//
// while (!sn.complete) {
// try {
// Thread.sleep(800);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
//
size[0] = sn.returnSize()[0];
size[1] = sn.returnSize()[1];
size[2] = sn.returnSize()[2];
size[3] = sn.returnSize()[3];
// }
// });
//
// worker.start();
//
// try {
// worker.join();
// } catch (InterruptedException e1) {
// e1.printStackTrace();
// }
System.out.println(size[0] + " " + size[1] + " " + size[2] + " " + size[3]);
}
}
class SnipIt {
private int recX = 0;
private int recY = 0;
private int recWidth = 0;
private int recHeight = 0;
public boolean complete = false;
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
SnipIt s = new SnipIt();
s.snip(null); // !!
}
});
}
public void snip(Window owner) { // !!
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
// JFrame frame = new JFrame();
JDialog frame = new JDialog(owner, null, ModalityType.APPLICATION_MODAL); // !!
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 0));
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); // !!
frame.setLayout(new BorderLayout());
frame.add(new SnipItPane());
frame.setBounds(getVirtualBounds());
frame.setVisible(true);
}
#SuppressWarnings("serial")
public class SnipItPane extends JPanel {
private Point mouseAnchor;
private Point dragPoint;
private SelectionPane selectionPane;
private ControlPane controlPane;
public SnipItPane() {
setOpaque(false);
setLayout(null);
selectionPane = new SelectionPane();
controlPane = new ControlPane();
add(selectionPane);
add(controlPane);
MouseAdapter adapter = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
mouseAnchor = e.getPoint();
dragPoint = null;
selectionPane.setLocation(mouseAnchor);
selectionPane.setSize(0, 0);
controlPane.setLocation(mouseAnchor);
controlPane.setSize(0, 0);
}
#Override
public void mouseDragged(MouseEvent e) {
dragPoint = e.getPoint();
int width = dragPoint.x - mouseAnchor.x;
int height = dragPoint.y - mouseAnchor.y;
int x = mouseAnchor.x;
int y = mouseAnchor.y;
if (width < 0) {
x = dragPoint.x;
width *= -1;
}
if (height < 0) {
y = dragPoint.y;
height *= -1;
}
selectionPane.setBounds(x, y, width, height);
selectionPane.revalidate();
int controlY = y + height + 5;
controlPane.setBounds(x, controlY, width, 25);
controlPane.revalidate();
repaint();
}
};
addMouseListener(adapter);
addMouseMotionListener(adapter);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
Area area = new Area(bounds);
area.subtract(new Area(selectionPane.getBounds()));
g2d.setColor(new Color(102, 102, 102, 80));
g2d.fill(area);
}
}
#SuppressWarnings("serial")
public class ControlPane extends JPanel {
private JButton btnClose;
public ControlPane() {
setOpaque(false);
btnClose = new JButton("Save");
setLayout(new BorderLayout());
this.add(btnClose, BorderLayout.NORTH);
btnClose.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
complete = true;
SwingUtilities.getWindowAncestor(ControlPane.this).dispose();
}
});
}
}
#SuppressWarnings("serial")
public class SelectionPane extends JPanel {
public SelectionPane() {
setOpaque(false);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
recX = getX();
recY = getY();
recWidth = getWidth();
recHeight = getHeight();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
float strokeWidth = 1.0f;
float dash1[] = { 10.0f };
BasicStroke dashed = new BasicStroke(strokeWidth, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f);
g2d.setColor(Color.BLACK);
g2d.setStroke(dashed);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.dispose();
}
}
public static Rectangle getVirtualBounds() {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice lstGDs[] = ge.getScreenDevices();
for (GraphicsDevice gd : lstGDs) {
bounds.add(gd.getDefaultConfiguration().getBounds());
}
return bounds;
}
public int[] returnSize() {
int[] size = new int[4];
size[0] = recX;
size[1] = recY;
size[2] = recWidth;
size[3] = recHeight;
return size;
}
}
A side issue unrelated to your initial problem is your use of null layougs. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.

Why JLabels disappeare?

Task: There are 2 labels which connect with each other by line, user must be able to move each of the labels with mouse and the connection between lables must move accordingly.
Here is my code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
class BackgroundJPanel extends JPanel {
BackgroundJPanel() {
this.setBounds(0, 0, 200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.orange);
g.fillRect(0, 0, 198, 198);
g.setColor(Color.black);
g.drawRect(0, 0, 198, 198);
}
}
class LinkJPanel extends JPanel {
JLabel[] labels = null;
LinkJPanel(JLabel[] labelsIn) {
labels = labelsIn;
this.setBounds(0, 0, 200, 200);
this.setOpaque(false);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(labels[0].getX(), labels[0].getY(),
labels[1].getX(), labels[1].getY());
}
}
class LabelJPanel extends JPanel {
AllPanel allPanel = null;
LabelJPanel(AllPanel allPanelIn) {
allPanel = allPanelIn;
this.setOpaque(false);
JLabel[] labels = allPanel.labels;
labels[0] = new JLabel("11");
this.add(labels[0]);
labels[1] = new JLabel("22");
this.add(labels[1]);
MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
this.addMouseMotionListener(myMouseAdapter);
this.addMouseListener(myMouseAdapter);
}
class MyMouseAdapter extends MouseAdapter {
JLabel labelToMove = null;
int startDeltaX = -1;
int startDeltaY = -1;
JLabel whichLabel(MouseEvent e) {
for (JLabel label : allPanel.labels) {
int deltaX = e.getX() - label.getX();
int deltaY = e.getY() - label.getY();
if ((deltaX >= 0)
&& (deltaX < label.getPreferredSize().width)
&& (deltaY >= 0)
&& (deltaY < label.getPreferredSize().height)) {
return label;
}
}
return null;
}
#Override
public void mousePressed(MouseEvent e) {
labelToMove = whichLabel(e);
if (labelToMove != null) {
startDeltaX = labelToMove.getX() - e.getX();
startDeltaY = labelToMove.getY() - e.getY();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (labelToMove != null) {
updateLocation(e);
}
}
#Override
public void mouseReleased(MouseEvent e) {
labelToMove = null;
}
public void updateLocation(MouseEvent e) {
int newXCoord = e.getX() + startDeltaX;
int newYCoord = e.getY() + startDeltaY;
labelToMove.setBounds(newXCoord, newYCoord,
labelToMove.getPreferredSize().width,
labelToMove.getPreferredSize().height);
//allPanel.backgroundJPanel.repaint();
allPanel.linkJPanel.repaint();
}
}
}
class AllPanel extends JPanel {
BackgroundJPanel backgroundJPanel = null;
LinkJPanel linkJPanel = null;
LabelJPanel labelJPanel = null;
JLabel[] labels = null;
AllPanel() {
this.setLayout(new BorderLayout());
this.setPreferredSize(new Dimension(200, 200));
labels = new JLabel[2];
backgroundJPanel = new BackgroundJPanel();
this.add(backgroundJPanel);
linkJPanel = new LinkJPanel(labels);
this.add(linkJPanel);
labelJPanel = new LabelJPanel(this);
this.add(labelJPanel);
this.setComponentZOrder(backgroundJPanel, 2);
this.setComponentZOrder(linkJPanel, 1);
this.setComponentZOrder(labelJPanel, 0);
}
}
class MyFrame extends JFrame {
MyFrame() {
this.setTitle("Test");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new AllPanel());
this.pack();
this.setVisible(true);
this.setResizable(false);
}
}
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new MyFrame();
}
});
}
}
I use 3 JPanel - one for background, one for draw connection line, one for drawing 2 labels. JPanel for drawing labels has MouseMotion and Mouse listeners. When some mouse action appears code check which one of the label must be move and then change that label coordinates and repaint JPanel with connection line.
All work fine until I try also repaint JPanel with background. If I uncomment the line
//allPanel.backgroundJPanel.repaint();
after action JLabels and line start disappeare.
Can someone explane why this happen?

Java image zooming japplet

Im trying to make an applet that will allow me to open, zoom in and out of an image. Ive got a working applet going, but am having trouble with the zoom. Any ideas on where to head from here?
This is what I have so far
ImageZoom
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
public class ImageZoom extends JApplet implements ActionListener
{
JPanel jpanel, pane2;
JLabel image;
JButton open_file, zoom_in, zoom_out;
Image img;
JFileChooser fc;
Zoom zP;
#Override
public void init()
{
setSize(450,450);
buttonLayout();
filechooser();
imgLayout();
zP = new Zoom();
}
public void imgLayout()
{
pane2 = new JPanel();
pane2.setLayout(new BorderLayout());
image = new JLabel("");
pane2.add(image,BorderLayout.CENTER);
}
public void buttonLayout()
{
jpanel = new JPanel(new FlowLayout());
open_file = new JButton("Open file");
open_file.addActionListener(this);
jpanel.add(open_file);
zoom_in = new JButton("Zoom In");
zoom_in.addActionListener(this);
jpanel.add(zoom_in);
zoom_out = new JButton("Zoom Out");
zoom_out.addActionListener(this);
jpanel.add(zoom_out);
add( jpanel, BorderLayout.NORTH );
}
public void filechooser()
{
fc = new JFileChooser();
fc.setCurrentDirectory(new File(
System.getProperty("user.home")));
}
#Override
public void actionPerformed(ActionEvent ae)
{
if(ae.getSource() == open_file)
{
int result = fc.showOpenDialog(null );
if(result == JFileChooser.APPROVE_OPTION)
{
File file = fc.getSelectedFile();
String sname = file.getAbsolutePath();
image = new JLabel("", new ImageIcon(sname), JLabel.CENTER);
jpanel.add(image, BorderLayout.CENTER);
jpanel.revalidate();
jpanel.repaint();
}
}
else if(ae.getSource() == zoom_in)
{
zP.increaseSize();
}
else if(ae.getSource() == zoom_out)
{
zP.decreaseSize();
}
}
}
Zoom
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class Zoom extends Panel
{
MediaTracker tracker;
Image img;
Dimension imgSize,iniSize;
public Zoom()
{
setSize(400,275);
img =Toolkit.getDefaultToolkit().getImage("image"); // here u give ur own image name
tracker=new MediaTracker(this);
tracker.addImage(img,1);
try{
tracker.waitForAll();
}
catch(InterruptedException ie){
System.out.println("Error in loading image");
}
imgSize=iniSize=new Dimension(img.getWidth(this),img.getHeight(this));
}
#Override
public Dimension getPreferredSize(){
return new Dimension(imgSize);
}
public void decreaseSize()
{
int x=10*imgSize.width/100;
int y=10*imgSize.height/100;
imgSize=new Dimension(imgSize.width-x,imgSize.height-y);
if(getWidth()>iniSize.width)
{
setSize(imgSize);
getParent().doLayout();
}
repaint();
}
public void increaseSize()
{
int x=10*imgSize.width/100;
int y=10*imgSize.height/100;
imgSize = new Dimension(imgSize.width+x,imgSize.height+y);
if(imgSize.width>iniSize.width)
{
setSize(imgSize);
getParent().doLayout();
}
repaint();
}
} // class Zoom
You could continue to use a JLabel and scale the image and reset the JLabels icon property, or you could create a custom component dedicated to the job...
To start with, we need to display the image...
public class ZoomPane extends JPanel {
private BufferedImage master;
private Image scaled;
private float scale = 1f;
public ZoomPane() {
try {
master = ImageIO.read(new File("..."));
scaled = master;
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return scaled != null ? new Dimension(scaled.getWidth(this), scaled.getHeight(this)) : new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (scaled != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - scaled.getWidth(this)) / 2;
int y = (getHeight() - scaled.getHeight(this)) / 2;
g2d.drawImage(scaled, x, y, this);
g2d.dispose();
}
}
}
Now, you could use a MouseWheelListener to scale the image...
addMouseWheelListener(new MouseWheelListener() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getWheelRotation() < 0) {
scale -= 0.1;
} else {
scale += 0.1;
}
scaleBy(scale);
}
});
And because you'll probably want to allow the users a few different methods to scale the image, a nice convenience method....
public void scaleBy(float amount) {
scale += amount;
scale = Math.min(Math.max(scale, 0.1f), 200);
scaled = master.getScaledInstance(
Math.round(master.getWidth() * scale),
Math.round(master.getHeight() * scale),
Image.SCALE_SMOOTH);
revalidate();
repaint();
}
Now, it'd probably be nice to allow the user to use the + and - keys, for this, we'll want some kind of Action...
public class ZoomAction extends AbstractAction {
private ZoomPane zoomPane;
private float zoomAmount;
public ZoomAction(ZoomPane zoomPane, String name, float zoomAmount) {
this.zoomAmount = zoomAmount;
this.zoomPane = zoomPane;
putValue(NAME, name);
}
#Override
public void actionPerformed(ActionEvent e) {
zoomPane.scaleBy(zoomAmount);
System.out.println("...");
}
}
This allows us to bind keyboard actions, buttons and menus and re-use the same basic code to do it...
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoom-in");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoom-out");
am.put("zoom-in", new ZoomAction(this, "Zoom in", 0.1f));
am.put("zoom-out", new ZoomAction(this, "Zoom out", -0.1f));
This example works with the numpad + and - keys.
All this functionality should be added directly within the ZoomPane
You can re-use the ZoomAction in your menus...
JMenuBar mb = new JMenuBar();
JMenu pictureMenu = new JMenu("Picture");
pictureMenu.add(new ZoomAction(zoomPane, "Zoom In", 0.1f));
pictureMenu.add(new ZoomAction(zoomPane, "Zoom Out", -0.1f));
mb.add(pictureMenu);
And even in you buttons...
JPanel buttons = new JPanel();
buttons.add(new JButton(new ZoomAction(zoomPane, "Zoom In", 0.1f)));
buttons.add(new JButton(new ZoomAction(zoomPane, "Zoom Out", -0.1f)));
frame.add(buttons, BorderLayout.SOUTH);
Warning
The scaling is pretty rough and ready, you should consider taking a look at:
The Perils of Image.getScaledInstance()
Java: maintaining aspect ratio of JPanel background image
Quality of Image after resize very low -- Java
... for more details
And finally, hacking it all together, as a runnable example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
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();
}
ZoomPane zoomPane = new ZoomPane();
JMenuBar mb = new JMenuBar();
JMenu pictureMenu = new JMenu("Picture");
pictureMenu.add(new ZoomAction(zoomPane, "Zoom In", 0.1f));
pictureMenu.add(new ZoomAction(zoomPane, "Zoom Out", -0.1f));
mb.add(pictureMenu);
JFrame frame = new JFrame("Testing");
frame.setJMenuBar(mb);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(zoomPane);
JPanel buttons = new JPanel();
buttons.add(new JButton(new ZoomAction(zoomPane, "Zoom In", 0.1f)));
buttons.add(new JButton(new ZoomAction(zoomPane, "Zoom Out", -0.1f)));
frame.add(buttons, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ZoomPane extends JPanel {
private BufferedImage master;
private Image scaled;
private float scale = 1f;
public ZoomPane() {
try {
master = ImageIO.read(new File("C:\\Users\\shane\\Dropbox\\MegaTokyo\\megatokyo_omnibus_1_3_cover_by_fredrin-d4oupef.jpg"));
scaled = master;
addMouseWheelListener(new MouseWheelListener() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getWheelRotation() < 0) {
scale -= 0.1;
} else {
scale += 0.1;
}
scaleBy(scale);
}
});
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoom-in");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoom-out");
am.put("zoom-in", new ZoomAction(this, "Zoom in", 0.1f));
am.put("zoom-out", new ZoomAction(this, "Zoom out", -0.1f));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void scaleBy(float amount) {
scale += amount;
scale = Math.min(Math.max(scale, 0.1f), 200);
scaled = master.getScaledInstance(
Math.round(master.getWidth() * scale),
Math.round(master.getHeight() * scale),
Image.SCALE_SMOOTH);
revalidate();
repaint();
}
#Override
public Dimension getPreferredSize() {
return scaled != null ? new Dimension(scaled.getWidth(this), scaled.getHeight(this)) : new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (scaled != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - scaled.getWidth(this)) / 2;
int y = (getHeight() - scaled.getHeight(this)) / 2;
g2d.drawImage(scaled, x, y, this);
g2d.dispose();
}
}
}
public class ZoomAction extends AbstractAction {
private ZoomPane zoomPane;
private float zoomAmount;
public ZoomAction(ZoomPane zoomPane, String name, float zoomAmount) {
this.zoomAmount = zoomAmount;
this.zoomPane = zoomPane;
putValue(NAME, name);
}
#Override
public void actionPerformed(ActionEvent e) {
zoomPane.scaleBy(zoomAmount);
System.out.println("...");
}
}
}

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