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("...");
}
}
}
Related
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);
}
}
}
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.
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);
}
}
}
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();
}
});
}
}
So, I have a JFrame with a menu, a toolbar and panel. I load images inside the panel but for some strange reason (to me at least) they are not correctly displayed in the panel. Sometimes they start under the toolbar, sometimes above. Furthermore the image is cut on bottom. The code is a cleaned example from the full one that can be compiled and tested. Thanks in advance.
Class of containing frame:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipInputStream;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
public class SSCE extends JFrame {
private JComicPanel panel;
private JToolBar toolbar;
private JButton buttonZoom;
private JButton buttonPrev;
private JButton buttonNext;
private JMenuBar menuBar;
private Dimension dim;
private BufferedImage img;
private int currentPage;
private JFrame parentFrame;
public SSCE(){
super("JComic");
BorderLayout layout = new BorderLayout();
setLayout(layout);
dim = Toolkit.getDefaultToolkit().getScreenSize();
setSize((int)(dim.width /2.5),dim.height);
this.setPreferredSize(new Dimension((int) (dim.width /2.5),dim.height));
createToolbar();
createPanel();
add(toolbar,BorderLayout.NORTH);
add(panel,BorderLayout.CENTER);
createMenu();
add(menuBar);
setJMenuBar(menuBar);
panel.setVisible(true);
img = null;
pack();
parentFrame = this;
}
private void createPanel(){
Border raisedbevel, loweredbevel;
raisedbevel = BorderFactory.createRaisedBevelBorder();
loweredbevel = BorderFactory.createLoweredBevelBorder();
panel = new JComicPanel(img);
panel.setBorder(BorderFactory.createCompoundBorder(raisedbevel,loweredbevel));
}
private void createToolbar(){
toolbar = new JToolBar();
toolbar.setPreferredSize(new Dimension(dim.width,45));
toolbar.setFloatable(false);
buttonZoom = new JButton("+");
toolbar.add(buttonZoom);
buttonPrev = new JButton("<-");
toolbar.add(buttonPrev);
buttonNext = new JButton("->");
toolbar.add(buttonNext);
toolbar.setBackground(Color.RED);
}
private void createMenu(){
JMenu menuFile,menuJComic;
JMenuItem fileOpen;
JMenuItem quitJComic,aboutJComic;
menuBar = new JMenuBar();
menuJComic = new JMenu("JComic");
aboutJComic = new JMenuItem("About JComic...");
menuJComic.add(aboutJComic);
quitJComic = new JMenuItem("Quit");
quitJComic.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
);
menuJComic.add(quitJComic);
menuBar.add(menuJComic);
menuFile = new JMenu("File");
fileOpen = new JMenuItem("Open...");
fileOpen.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e) {
try {
img = ImageIO.read(new File("superman.jpg"));
currentPage = 1;
int offset = menuBar.getHeight() + toolbar.getHeight();
panel.setImage(img,parentFrame,offset);
}
catch (IOException e1) {
System.out.println(e1.getMessage());
}
}
}
);
menuFile.add(fileOpen);
menuBar.add(menuFile);
}
/**
* #param args
*/
#SuppressWarnings("deprecation")
public static void main(String[] args) {
// TODO Auto-generated method stub
SSCE f = new SSCE();
f.show();
}
}
Class of panel
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JComicPanel extends JPanel{
private BufferedImage img;
private int offset;
private final float scaling = 0.5f;
public JComicPanel(BufferedImage img){
super();
this.img = img;
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e)
{
repaint();
}
});
}
public void setImage(BufferedImage img,JFrame parentFrame,int offset){
try{
int w = img.getWidth();
int h = img.getHeight();
int newW = (int)(w * scaling);
int newH = (int)(h * scaling);
BufferedImage dimg = new BufferedImage(newW, newH, img.getType());
Graphics2D g = dimg.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(img, 0, 0, newW, newH, 0, 0, w, h, null);
this.img = img;
System.out.printf("dim %d x %d",newW,newH);
this.setSize(new Dimension(newW,newH+offset));
parentFrame.pack();
}
catch(Exception e){
}
}
public Dimension getPreferredSize(){
return new Dimension(img.getWidth(),img.getHeight());
}
public void paintComponent(Graphics g){
// Draw our Image object.
super.paintComponent(g);
g.drawImage(img,0,0,getSize().width,getSize().height, this); // at location 50,10
System.out.println("painting 2");
}
}
Image superman.jpg can be found here http://i50.tinypic.com/2yxnc3n.jpg. As you can see the image is below the toolbar, but in my full code it goes also above.
Let's start here...
public void setImage(BufferedImage img,JFrame parentFrame,int offset){
try{
// You're scaling the incoming image, which means you no longer
// have a reference to the original image should you want to
// change that scale...
// You're previous code...
// Don't do this. The parent container's layout manager will simple
// override it, so it's useless...
this.setSize(newW,newH);
repaint();
// This is a bad idea (personally)...
parentFrame.setSize(newW,newH+offset);
} catch(Exception e){
}
}
Calling setSize in this way will temporarily allow the component to assume the size you have set. If you were to call invalidate() after it, the component would actually be re-sized back to meet the requirements of the layout manager.
The larger problem is the fact that you are setting the parent frame's size, but you have no idea of the frame's layout requirements with regards to it's other components, such as the tool bar, menu bar and frame (for example)
This is drawing the image without consideration to the ratio of the original image
g.drawImage(img,0,0,getSize().width,getSize().height, this);
As it has being suggested, after scaling the image, it might be easier to simply set the image as the icon of a JLabel which has been added to the image pane using a BorderLayout
You're assuming that the size of the panel is correct, but it won't be as the panel's layout manager will take over.
This is redundant as Swing will automatically repaint the component when it's resized.
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e)
{
repaint();
}
});
You're best bet is to either drop the image pane into a scroll pane and or rescale the image to fit the size of the pane dynamically.
For example...
Scalable Page
public class ComicPage {
public static void main(String[] args) {
new ComicPage();
}
public ComicPage() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
try {
BufferedImage page = ImageIO.read(getClass().getResource("/1346.gif"));
ComicPagePane comicPagePane = new ComicPagePane();
comicPagePane.setComicPage(page);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(comicPagePane);
frame.setSize(200, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception exp) {
exp.printStackTrace();
}
}
});
}
public class ComicPagePane extends JPanel {
private BufferedImage comicPage;
private Image scaledInstance;
public void setComicPage(BufferedImage page) {
if (page != comicPage) {
comicPage = page;
scaledInstance = null;
repaint();
}
}
#Override
public void invalidate() {
scaledInstance = null;
super.invalidate();
}
public BufferedImage getComicPage() {
return comicPage;
}
public double getScaleFactor(int iMasterSize, int iTargetSize) {
return (double) iTargetSize / (double) iMasterSize;
}
public double getScaleFactorToFit(BufferedImage img) {
double dScale = 1d;
double dScaleWidth = getScaleFactor(img.getWidth(), getWidth());
double dScaleHeight = getScaleFactor(img.getHeight(), getHeight());
dScale = Math.min(dScaleHeight, dScaleWidth);
return dScale;
}
protected Image getScaledInstance(BufferedImage master) {
if (scaledInstance == null) {
double scaleFactor = getScaleFactorToFit(master);
System.out.println("scaleFactor = " + NumberFormat.getNumberInstance().format(scaleFactor));
// This is not the best scaling alorithm
scaledInstance = master.getScaledInstance(
(int) Math.round(master.getWidth() * scaleFactor),
(int) Math.round(master.getHeight() * scaleFactor), Image.SCALE_SMOOTH);
}
return scaledInstance;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
BufferedImage comicPage = getComicPage();
if (comicPage != null) {
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth();
int height = getHeight();
// Normally, I would put this into a background worker as this
// operation can be expensive....
Image scaledInstance = getScaledInstance(comicPage);
int x = (width - scaledInstance.getWidth(this)) / 2;
int y = (height - scaledInstance.getHeight(this)) / 2;
g2d.drawImage(scaledInstance, x, y, this);
g2d.dispose();
}
}
}
}
Scrollable Page
public class ScrollableComicPage {
public static void main(String[] args) {
new ScrollableComicPage();
}
public ScrollableComicPage() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
try {
BufferedImage page = ImageIO.read(getClass().getResource("/1346.gif"));
ComicPagePage comicPagePane = new ComicPagePage();
comicPagePane.setComicPage(page);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(comicPagePane));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception exp) {
exp.printStackTrace();
}
}
});
}
public class ComicPagePage extends JPanel {
private BufferedImage comicPage;
#Override
public Dimension getPreferredSize() {
return comicPage != null ? new Dimension(comicPage.getWidth(), comicPage.getHeight()) : new Dimension(0, 0);
}
public void setComicPage(BufferedImage page) {
if (page != comicPage) {
comicPage = page;
invalidate();
repaint();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (comicPage != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(comicPage, 0, 0, this);
g2d.dispose();
}
}
}
}
You can have a read through Java: maintaining aspect ratio of JPanel background image for more information on scaling techniques.
The technical reason is that you are temporarily adding the menuBar to the center of the contentPane, that is the same logical location as the previously added panel:
add(panel, BorderLayout.CENTER);
// following line is wrong - must be removed!!!
add(menuBar); // no constraint == BorderLayout.CENTER
setJMenuBar(menuBar);
Doing so pushes the panel out off the control of the LayoutManager, but not out of the panel - the net effect is that it has no constraint.
That's not special to being a menuBar (which needs to be added to the layeredPane of the rootPane, not the contentPane - that's why it has its own specialized method setJMenuBar), the same mess would happen with any other arbitrary component at the center.
Apart from that, I would recommend you cleaning up your code:
remove all setXXSize (some reasons)
decide whether the frame should come up at its preferred (using pack) or an arbitrary fixed size (using setSize). The former is the correct thingy to do in most cases, the latter has its use-cases - but doing both doesn't make sense
components are visible by default