Java Swing: Making a growing circle by mouse click on JPanel - java

I'm a beginner in Java and this time I'm trying to learn more by finding code examples and editing them, for example from this website. I have a JFrame and each time it (or more precise the JPanel in it) is clicked on, a circle is drawn which will grow/expand outwards like a water ripple. Each circle starts with a certain radius and will be removed or redrawn when reaching a bigger radius (I chose radius "r" from 10 to 200). I have two programs for this which almost work but are missing something. If I'm correct, I might also know what their problems are but I can't figure out how to solve them:
One generates growing circles but all of them have the same size, no matter when they're generated, since I can't find out how to assign the radius to a single circle. The code is adapted from here:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Ripples extends JPanel implements ActionListener{
public int r = 10;
private ArrayList<Point> p;
public Ripples() {
p = new ArrayList<>();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
p.add(new Point(e.getX(), e.getY()));
}
});
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.CYAN);
for (Point pt : p) {
g2.drawOval(pt.x-r, pt.y-r, 2*r, 2*r);
}
}
#Override
public void actionPerformed(ActionEvent evt) {
if(r<200){
r++;
} else {
r = 10;
}
repaint();
}
public static void Gui() {
JFrame f = new JFrame();
Ripples p = new Ripples();
p.setBackground(Color.WHITE);
f.setContentPane(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(500,300);
f.setVisible(true);
Timer t = new Timer(20,p);
t.start();
}
public static void main(String[] args) {
Gui();
}
}
The other program(I've forgotten where I got it from) has circles with different radii depending on when they were generated, however the circles "flicker", because -as far as I understand- they are all processed at the same time, because the code doesn't include an Array to store and update each circle individually like the one above does:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Ripples extends JPanel {
int x,y;
int r = 10;
public Ripples(int x, int y) {
this.x = x;
this.y = y;
Timer t = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (r<200) {
r++;
} else {
r=10;
}
revalidate();
repaint();
}
});
t.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.CYAN);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.drawOval(x-r,y-r,2*r,2*r);
}
public static void Gui() {
JFrame f = new JFrame("Water Ripples");
JPanel p0 = new JPanel();
p0.setBackground(Color.WHITE);
f.add(p0);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setBounds(100,100,600,500);
f.setVisible(true);
f.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
Ripples rG = new Ripples(e.getX(), e.getY());
rG.setBackground(Color.WHITE);
f.add(rG);
}
});
}
public static void main(String[] args) {
Gui();
}
}
So how can I solve this so that I get the circles growing independent from each other? I'd prefer a solution/improvement/hint for the upper code because I think its structured better than the second one. Also, I apologize for not splitting the code into more classes and for possibly not sticking to naming conventions. I appreciate your help, thank you very much!

I added a Circle class to your Ripples code. This allows the ActionListener to treat each circle independently.
I started the GUI with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
Here's the code.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
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.SwingUtilities;
import javax.swing.Timer;
public class Ripples extends JPanel implements ActionListener {
private List<Circle> circles;
public Ripples() {
circles = new ArrayList<>();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent event) {
circles.add(new Circle(event.getPoint()));
}
});
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.CYAN);
g2.setStroke(new BasicStroke(3f));
for (Circle circle : circles) {
Point p = circle.getCenter();
int radius = circle.getRadius();
g2.drawOval(p.x - radius, p.y - radius,
2 * radius, 2 * radius);
}
}
#Override
public void actionPerformed(ActionEvent evt) {
for (Circle circle : circles) {
circle.incrementRadius();
}
repaint();
}
public static void createGUI() {
JFrame f = new JFrame("Ripples");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Ripples p = new Ripples();
p.setBackground(Color.WHITE);
p.setPreferredSize(new Dimension(500, 500));
f.setContentPane(p);
f.pack();
f.setLocationByPlatform(true);
f.setVisible(true);
Timer t = new Timer(20, p);
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createGUI();
}
});
}
public class Circle {
private int radius;
private final Point center;
public Circle(Point center) {
this.center = center;
this.radius = 10;
}
public void incrementRadius() {
radius += 1;
radius = (radius > 200) ? 10 : radius;
}
public int getRadius() {
return radius;
}
public Point getCenter() {
return center;
}
}
}
Edited to add:
I reworked the Ripples class code to separate the concerns. I created a DrawingPanel class to hold the drawing panel, a RipplesListener class to hold the MouseAdapter code, an Animation class to hold the Runnable that runs the animation of the circles, a RipplesModel class to hold the List of Circle instances, and finally, the Circle class.
I could have used a Swing Timer for the animation, but I'm more familiar with creating and running my own animation thread.
Yes, this code is more complicated than the original example. The coding style used here can be carried into larger, more complex Swing GUI development.
Here's the revised code. I hope it's a better example.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Ripples implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Ripples());
}
private Animation animation;
private DrawingPanel drawingPanel;
private RipplesModel model;
public Ripples() {
model = new RipplesModel();
}
#Override
public void run() {
JFrame frame = new JFrame("Ripples");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
stopAnimation();
frame.dispose();
System.exit(0);
}
});
drawingPanel = new DrawingPanel(model);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(this, model);
new Thread(animation).start();
}
public void repaint() {
drawingPanel.repaint();
}
private void stopAnimation() {
if (animation != null) {
animation.setRunning(false);
}
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private RipplesModel model;
public DrawingPanel(RipplesModel model) {
this.model = model;
setBackground(Color.WHITE);
setPreferredSize(new Dimension(500, 500));
addMouseListener(new RipplesListener(model));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(3f));
List<Circle> circles = model.getCircles();
for (Circle circle : circles) {
Point p = circle.getCenter();
int radius = circle.getRadius();
g2.setColor(circle.getColor());
g2.drawOval(p.x - radius, p.y - radius,
2 * radius, 2 * radius);
}
}
}
public class RipplesListener extends MouseAdapter {
private RipplesModel model;
public RipplesListener(RipplesModel model) {
this.model = model;
}
#Override
public void mousePressed(MouseEvent event) {
model.addCircle(new Circle(event.getPoint(),
createColor()));
}
private Color createColor() {
Random random = new Random();
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
return new Color(r, g, b);
}
}
public class Animation implements Runnable {
private volatile boolean running;
private Ripples frame;
private RipplesModel model;
public Animation(Ripples frame, RipplesModel model) {
this.frame = frame;
this.model = model;
this.running = true;
}
#Override
public void run() {
while (running) {
sleep(20L);
incrementRadius();
}
}
private void incrementRadius() {
List<Circle> circles = model.getCircles();
for (Circle circle : circles) {
circle.incrementRadius();
}
repaint();
}
private void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.repaint();
}
});
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
public class RipplesModel {
private List<Circle> circles;
public RipplesModel() {
this.circles = new ArrayList<>();
}
public void addCircle(Circle circle) {
this.circles.add(circle);
}
public List<Circle> getCircles() {
return circles;
}
}
public class Circle {
private int radius;
private final Color color;
private final Point center;
public Circle(Point center, Color color) {
this.center = center;
this.color = color;
this.radius = 10;
}
public void incrementRadius() {
radius = (++radius > 200) ? 10 : radius;
}
public Color getColor() {
return color;
}
public int getRadius() {
return radius;
}
public Point getCenter() {
return center;
}
}
}

I'd prefer a solution/improvement/hint for the upper code
The second code is better because it uses:
a custom class to contain information about the object to be painted
an ArrayList to contain the objects to be painted
a Timer for the animation.
because I think its structured better than the second one.
Not a good reason. Use the code that provides the functionality that you require.
Restructure the code yourself. That is part of the learning experience.
Issues with the second code:
It doesn't compile. Why post code that doesn't compile? This implies you haven't even tested it.
the initial radius is assigned when the Position object is created.
When the Timer fires you need to iterate through the ArrayList to update the radius of each Position object.
The radius of the Position object is used in the painting code.
As an added change, maybe call the Position class Ripple. Then you can add another custom property for the Color of the ripple. Then when you add the Ripple to the ArrayList you randomly generate a Color. Then in the painting method you use the Color property of the Ripple class. This is how you make objects and painting more flexible and dynamic.

Related

How to change the color of a panel when I pass over it with another one?

I'm creating a code where I need to change a Panel color with the one I'm still pressed on when I pass over it. For instance if let's say I pressed on a green panel and drag it over another one, this one should get the green color. However it doesn't work everytime, like sometimes it does change the color but sometimes it doesn't.
import java.awt.Color;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.util.Random;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;
import javax.swing.*;
import javax.swing.event.MouseInputListener;
public class Etabli extends JFrame {
JPanel paneprinci;
CarrePanel selected;
public Etabli() {
this.setVisible(true);
setTitle("Cadre");
setDefaultCloseOperation(EXIT_ON_CLOSE);
paneprinci=new JPanel(null);
setSize(600,600);
selected=new CarrePanel();
for (int i=0;i<10;i++) {
paneprinci.add(new CarrePanel(new Carre()));
}
this.getContentPane().add(paneprinci);
}
public static void main (String[]args) {
javax.swing.SwingUtilities.invokeLater(
new Runnable() {
public void run() {
Etabli e=new Etabli();
}
});
}
public class CarrePanel extends JPanel implements MouseInputListener{
private Carre carre;
private boolean etat;
private int xprev;
private int yprev;
public CarrePanel(Carre c) {
setBounds(new Random().nextInt(500),new Random().nextInt(500), 50, 50);
addMouseListener(this);
addMouseMotionListener(this);
this.setBackground(c.getColor());
this.carre=c;
}
public CarrePanel() {
setBounds(new Random().nextInt(500),new Random().nextInt(500), 50, 50);
addMouseListener(this);
addMouseMotionListener(this);
}
public void setCarre(Carre c) {
carre=c;
}
public Carre getCarre() {
return carre;
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println(this.carre.getColor());
}
#Override
public void mouseEntered(MouseEvent e) {
if (selected.getCarre()!=null) {
this.carre.setColor(selected.getCarre().getColor());
this.setBackground(selected.getCarre().getColor());
}
}
#Override
public void mousePressed(MouseEvent e) {
etat=true;
selected.setCarre(this.carre);
xprev=e.getXOnScreen();
yprev=e.getYOnScreen();
}
#Override
public void mouseReleased(MouseEvent e) {
etat=false;
if(selected.getCarre()==this.carre) {
selected.setCarre(null);;
}
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void mouseDragged(MouseEvent e) {
if(etat) {
int x=this.getX()+e.getXOnScreen()-xprev;
int y=this.getY()+e.getYOnScreen()-yprev;
this.setLocation(x,y);
xprev=e.getXOnScreen();
yprev=e.getYOnScreen();
}
}
#Override
public void mouseMoved(MouseEvent e) {
}
}
}
Here's the code for Carre ( which is square in French )
import java.awt.Color;
import java.util.Arrays;
import java.util.Random;
import java.awt.Color;
public class Carre {
private Color color;
public Carre() {
color=new Color(new Random().nextInt(255),new Random().nextInt(255),new Random().nextInt(255));
}
public Color getColor() {
return color;
}
public void setColor(Color c) {
color=c;
}
}
What I don't understand is why does it works sometimes, I don't know if the problem comes from how I did my drag event or if there's something wrong elsewhere.
Thank you for your awnser.
What I don't understand is why does it works sometimes,
When you drag a square to another square you will notice that sometimes the dragged square is painted:
below the other square
above the other square
When the square is painted above the other square a mouseEntered event is not generated because the default logic will only generate the event for the top component.
When the square is painted below the other square, then your mouseEntered event is generated and your logic works as expected.
The order of painting of a component is determined by its ZOrder. So you can adjust the ZOrder of the dragged component to be the last component painted.
In the mousePressed logic you can add:
JPanel parent = (JPanel)getParent();
parent.setComponentZOrder(this, parent.getComponentCount() - 1);
This will make sure that the dragged component is always painted below the other components.
Also note that your etat variable is not needed. The mouseDragged event is only generated while the mouse is pressed.
I created the following GUI.
I added a java.awt.Rectangle to the Carre class to holds the bounds of each rectangle. The Carre class now holds all of the information to draw a Carre instance.
I created a CarreModel class to hold a java.util.List of Carre instances. I create all of the Carre instances in the constructor of the CarreModel class.
I created one JFrame and one drawing JPanel. I left your JFrame extend, but generally, you should use Swing components. You only extend a Swing component, like JPanel in CarrePanel, when you want to override one of the class methods.
I separated the MouseListener and MouseMotionListener from the CarrePanel JPanel class. I find it helps to keep separate functions in separate methods and classes. It makes it much easier to focus on one part of the application at a time.
The MouseListener mousePressed method has a function at the bottom to move the selected carre to the top of the Z-order. This makes the carre move more visually appealing.
The MouseListener updateCarreColor method checks for carre intersections and changes the color of any carre that intersects the selected carre.
Here's the complete runnable code. I made all the classes inner classes so I can post the code as one block.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
public class Etabli extends JFrame {
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Etabli();
}
});
}
private CarreModel model;
private CarrePanel paneprinci;
public Etabli() {
this.model = new CarreModel();
this.setTitle("Cadre");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
paneprinci = new CarrePanel();
this.add(paneprinci, BorderLayout.CENTER);
this.pack();
this.setLocationByPlatform(true);
this.setVisible(true);
}
public class CarrePanel extends JPanel {
private static final long serialVersionUID = 1L;
public CarrePanel() {
this.setPreferredSize(new Dimension(550, 550));
ColorListener listener = new ColorListener();
this.addMouseListener(listener);
this.addMouseMotionListener(listener);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Carre carre : model.getCarres()) {
g2d.setColor(carre.getColor());
Rectangle r = carre.getBounds();
g2d.fillRect(r.x, r.y, r.width, r.height);
}
}
}
public class ColorListener implements MouseInputListener {
private Carre selectedCarre;
private Point pressedPoint;
#Override
public void mouseClicked(MouseEvent event) {
}
#Override
public void mouseEntered(MouseEvent event) {
}
#Override
public void mousePressed(MouseEvent event) {
pressedPoint = event.getPoint();
selectedCarre = null;
for (Carre carre : model.getCarres()) {
if (carre.getBounds().contains(pressedPoint)) {
selectedCarre = carre;
break;
}
}
if (selectedCarre != null) {
List<Carre> carres = model.getCarres();
carres.remove(selectedCarre);
carres.add(selectedCarre);
}
}
#Override
public void mouseReleased(MouseEvent event) {
updateCarrePosition(event.getPoint());
updateCarreColor();
}
#Override
public void mouseExited(MouseEvent event) {
}
#Override
public void mouseDragged(MouseEvent event) {
updateCarrePosition(event.getPoint());
updateCarreColor();
}
#Override
public void mouseMoved(MouseEvent event) {
}
private void updateCarrePosition(Point point) {
int x = point.x - pressedPoint.x;
int y = point.y - pressedPoint.y;
if (selectedCarre != null) {
selectedCarre.incrementBounds(x, y);
paneprinci.repaint();
pressedPoint.x = point.x;
pressedPoint.y = point.y;
}
}
private void updateCarreColor() {
if (selectedCarre != null) {
for (Carre carre : model.getCarres()) {
if (!carre.equals(selectedCarre) &&
carre.getBounds().intersects(selectedCarre.getBounds())) {
carre.setColor(selectedCarre.getColor());
paneprinci.repaint();
}
}
}
}
}
public class CarreModel {
private final List<Carre> carres;
public CarreModel() {
this.carres = new ArrayList<>();
for (int i = 0; i < 10; i++) {
this.carres.add(new Carre());
}
}
public List<Carre> getCarres() {
return carres;
}
}
public class Carre {
private Color color;
private final Random random;
private Rectangle bounds;
public Carre() {
random = new Random();
int red = random.nextInt(128);
int green = random.nextInt(128);
int blue = random.nextInt(128);
color = new Color(red, green, blue);
int x = random.nextInt(500);
int y = random.nextInt(500);
bounds = new Rectangle(x, y, 50, 50);
}
public void incrementBounds(int x, int y) {
bounds.x += x;
bounds.y += y;
}
public Color getColor() {
return color;
}
public void setColor(Color c) {
color = c;
}
public Rectangle getBounds() {
return bounds;
}
}
}

MouseDragging still firing even after releasing

Hello and thanks in advance,
i am working with Graphics2D for a casino game (Roulette), so i am trying to add motion to the chips of the casino (The money), so for that i am using MouseDragged events and as a test i am working with only 1 ellipse.
code below
package roulette;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.JPanel;
import javax.swing.Timer;
public class RouletteInterface extends JPanel{
private List<Shape> money = new ArrayList<>();
public RouletteInterface() {
createEllipseGrap();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g4d = (Graphics2D) g.create();
paintEllipseGrap(g4d, g);
g4d.dispose();
}
protected void createEllipseGrap() {
Ellipse2D elipse = new Ellipse2D.Double(100, 100, 30, 30);
money.add(elipse);
addMouseListener(new moneyMouseListener());
addMouseMotionListener(new moneyMouseListener());
}
protected void paintEllipseGrap(Graphics2D g3d, Graphics g) {
g3d.setColor(Color.BLUE);
g3d.fill(money.get(0));
}
private class moneyMouseListener extends MouseAdapter {
int dragging;
private int x;
private int y;
#Override
public void mousePressed(MouseEvent e) {
if(money.get(0).contains(e.getPoint())) {
x = e.getX();
y = e.getY();
dragging = 0;
} else {
return ;
}
}
#Override
public void mouseDragged(MouseEvent e) {
if(dragging == 0) {
x = e.getX();
y = e.getY();
Ellipse2D elipse = new Ellipse2D.Double(x, y, 30, 30);
money.set(0, elipse);
repaint();
} else {
}
}
#Override
public void mouseReleased(MouseEvent m) {
dragging = 1;
repaint();
}
}
}
public class principal{
public static void main(String[] args) {
new principal();
}
public principal() {
JFrame frame = new JFrame();
frame.add(new RouletteInterface());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
What's the problem?
The MouseDragged event is still firing even after i release the mouseclick so the circle is still moving with my cursor when i click and drag on another side of the window
Your problem is that you are adding two different instances of your moneyMouseListeners as MouseListener and as MouseMotionListener:
addMouseListener(new moneyMouseListener());
addMouseMotionListener(new moneyMouseListener());
You would have to do it like that:
moneyMouseListener mListener = new moneyMouseListener();
addMouseListener(mListener);
addMouseMotionListener(mListener);
PS.: When using a variable like your "dragging" variable that is only used to assign a "1" or a "0" you should use a boolean variable with "true" and "false" ;)
Also, you might want to consider drawing/undrawing the chip using XOR mode of the Graphics, rather than calling repaint() all the time.
Press: undraw the chip in normal mode, then xor draw it.
Drag: XOR draw the chip, reset the position, XOR draw again.
Release: XOR draw the chip, then draw in normal mode.
That way if you move the chip over something in the background (or over another chip) you don't damage the other chip or the background.

List of graphics2D

Howcome this code below wont work? I want to add new Ovals to the ArrayList every 200 ms and display them and run them one by one. It works fine when Im running one particle s.runner(); but it doesnt seem to run all my particles.
MAIN:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.Timer;
public class ExempelGraphics extends JFrame implements ActionListener {
Timer t;
private int inc = 0;
ArrayList<Surface> particle = new ArrayList<>();
Surface s;
public ExempelGraphics() {
t = new Timer(10, this);
t.start();
s = new Surface(10, 10);
initUI();
}
private void initUI() {
add(s);
setSize(350, 250);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
// s.runner();
// add
if (inc++ % 20 == 0) {
particle.add(new Surface(10, 10));
}
// display
for (int i = 0; i < particle.size(); i++) {
Surface p = particle.get(i);
p.runner();
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ExempelGraphics ex = new ExempelGraphics();
ex.setVisible(true);
}
});
}
}
GRAPHICS:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class Surface extends JPanel {
private int locX = 0;
private int locY = 0;
public Surface(int locX, int locY) {
this.locX = locX;
this.locY = locY;
}
public void runner() {
locX = locX + 1;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
g2d.fillOval(locX, locY, 10, 10);
}
}
I think that you're program structure is broken. You should have only one JPanel here that does the drawing, that has its paintComponent overridden, and your Surface class should be a logical class and not a component class -- in other words, don't have it extend JPanel, and give it a public void draw(Graphics g) method where you draw the oval. Then have the drawing JPanel hold an ArrayList of these surfaces, and in the main JPanel's paintComponent method, iterate through the surfaces, calling each one's draw method.
Also your Timer's delay is not realistic and is too small. 15 would be much more realistic.
Also, don't call repaint() from within surface, since that will generate too many repaint calls unnecessarily. Instead call it from within the Timer's ActionListener after calling the runner methods on all the Surface objects.
Also note that every time you add a component to a JFrame's contentPane in a default fashion, you cover up the previously added components. If you go by my recommendations above, this isn't an issue since you'd only be adding that single JPanel to it.
For example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class ExampleGraphics2 extends JPanel {
private static final int PREF_W = 650;
private static final int PREF_H = 500;
private static final int TIMER_DELAY = 20;
private List<Surface> surfaces = new ArrayList<>();
public ExampleGraphics2() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Surface surface : surfaces) {
surface.draw(g);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
private int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
index++;
index %= 20;
if (index == 0) {
surfaces.add(new Surface(10, 10));
}
for (Surface surface : surfaces) {
surface.runner();
}
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("Example Graphics 2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ExampleGraphics2());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
package foo1;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class Surface {
private int locX = 0;
private int locY = 0;
public Surface(int locX, int locY) {
this.locX = locX;
this.locY = locY;
}
public void runner() {
locX = locX + 1;
}
public void draw(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
g2d.fillOval(locX, locY, 10, 10);
}
}

Rotate Shape Doens't Work [duplicate]

I have an image I am rotating when the user clicks on a button. But it is not working.
I would like to see the image rotating gradually to 90 degrees till it stops but it doesn't. The image must rotate 90 degrees gradually when the button is clicked.
I have created an SSCCE to demonstrate the problem. Please replace the image in the CrossingPanelSSCE class with any image of your choice. Just put the image in your images folder and name it images/railCrossing.JPG.
RotateButtonSSCE
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JPanel;
public class RotateButtonSSCE extends JPanel implements ActionListener{
private JButton rotate = new JButton("Rotate");
private VisualizationPanelSSCE vis = new VisualizationPanelSSCE();
public RotateButtonSSCE() {
this.setBorder(BorderFactory.createTitledBorder("Rotate Button "));
this.rotate.addActionListener(this);
this.add(rotate);
}
public void actionPerformed(ActionEvent ev) {
vis.rotatetheCrossing();
}
}
CrossingPanelSSCE
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;
public class CrossingPanelSSCE extends JPanel{
private static final long serialVersionUID = 1L;
// private data members
private Image crossingImage;
private int currentRotationAngle;
private int imageWidth;
private int imageHeight;
private AffineTransform affineTransform;
private boolean clockwise;
private static int ROTATE_ANGLE_OFFSET = 2;
private int xCoordinate;
private int yCoordinate;
private static javax.swing.Timer timer;
private void initialize(){
this.crossingImage = Toolkit.getDefaultToolkit().getImage("images/railCrossing.JPG");
this.imageWidth = this.getCrossingImage().getWidth(this);
this.imageHeight = this.getCrossingImage().getHeight(this);
this.affineTransform = new AffineTransform();
currentRotationAngle = 90;
timer = new javax.swing.Timer(20, new MoveListener());
}
public CrossingPanelSSCE(int x, int y) {
this.setxCoordinate(x);
this.setyCoordinate(y);
this.setPreferredSize(new Dimension(50, 50));
this.setBackground(Color.red);
TitledBorder border = BorderFactory.createTitledBorder("image");
this.setLayout(new FlowLayout());
this.initialize();
}
public void paintComponent(Graphics grp){
Rectangle rect = this.getBounds();
Graphics2D g2d = (Graphics2D)grp;
g2d.setColor(Color.BLACK);
this.getAffineTransform().setToTranslation(this.getxCoordinate(), this.getyCoordinate());
//rotate with the rotation point as the mid of the image
this.getAffineTransform().rotate(Math.toRadians(this.getCurrentRotationAngle()), this.getCrossingImage().getWidth(this) /2,
this.getCrossingImage().getHeight(this)/2);
//draw the image using the AffineTransform
g2d.drawImage(this.getCrossingImage(), this.getAffineTransform(), this);
}
public void rotateCrossing(){
System.out.println("CurrentRotationAngle: " + currentRotationAngle);
this.currentRotationAngle += ROTATE_ANGLE_OFFSET;
//int test = currentRotationAngle % 90;
if(currentRotationAngle % 90 == 0){
setCurrentRotationAngle(currentRotationAngle);
timer.stop();
}
//repaint the image panel
repaint();
}
void start() {
if (timer != null) {
timer.start();
}
}
private class MoveListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
rotateCrossing();
}
}
public Image getCrossingImage() {
return crossingImage;
}
public void setCrossingImage(Image crossingImage) {
this.crossingImage = crossingImage;
}
public int getCurrentRotationAngle() {
return currentRotationAngle;
}
public void setCurrentRotationAngle(int currentRotationAngle) {
this.currentRotationAngle = currentRotationAngle;
}
public int getImageWidth() {
return imageWidth;
}
public void setImageWidth(int imageWidth) {
this.imageWidth = imageWidth;
}
public int getImageHeight() {
return imageHeight;
}
public void setImageHeight(int imageHeight) {
this.imageHeight = imageHeight;
}
public AffineTransform getAffineTransform() {
return affineTransform;
}
public void setAffineTransform(AffineTransform affineTransform) {
this.affineTransform = affineTransform;
}
public boolean isClockwise() {
return clockwise;
}
public void setClockwise(boolean clockwise) {
this.clockwise = clockwise;
}
public int getxCoordinate() {
return xCoordinate;
}
public void setxCoordinate(int xCoordinate) {
this.xCoordinate = xCoordinate;
}
public int getyCoordinate() {
return yCoordinate;
}
public void setyCoordinate(int yCoordinate) {
this.yCoordinate = yCoordinate;
}
public javax.swing.Timer getTimer() {
return timer;
}
public void setTimer(javax.swing.Timer timer) {
this.timer = timer;
}
}
VisualizationPanelSSCE
import gui.CrossingPanel;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.GeneralPath;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import application.Robot2;
public class VisualizationPanelSSCE extends JPanel{
//private data members
private GeneralPath path;
private Shape horizontalRail;
private Shape verticalRail;
private static int LENGTH = 350;
private CrossingPanelSSCE crossingP;
private void initializeComponents(){
this.path = new GeneralPath();
this.horizontalRail = this.createHorizontalRail();
this.verticalRail = this.createVerticalRail();
this.crossingP = new CrossingPanelSSCE(328,334);
}
public VisualizationPanelSSCE(){
this.initializeComponents();
this.setPreferredSize(new Dimension(400,400));
TitledBorder border = BorderFactory.createTitledBorder("Rotation");
this.setBorder(border);
}
public GeneralPath getPath() {
return path;
}
public void setPath(GeneralPath path) {
this.path = path;
}
private Shape createHorizontalRail(){
this.getPath().moveTo(5, LENGTH);
this.getPath().lineTo(330, 350);
this.getPath().closePath();
return this.getPath();
}
private Shape createVerticalRail(){
this.getPath().moveTo(350, 330);
this.getPath().lineTo(350,10);
this.getPath().closePath();
return this.getPath();
}
public void paintComponent(Graphics comp){
super.paintComponent(comp);
Graphics2D comp2D = (Graphics2D)comp;
BasicStroke pen = new BasicStroke(15.0F, BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND);
comp2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
comp2D.setPaint(Color.black);
comp2D.setBackground(Color.WHITE);
comp2D.draw(this.horizontalRail);
this.crossingP.paintComponent(comp2D);
}
public CrossingPanelSSCE getCrossingP() {
return crossingP;
}
public void setCrossingP(CrossingPanelSSCE crossingP) {
this.crossingP = crossingP;
}
public void rotatetheCrossing(){
Runnable rotateCrossing1 = new Runnable(){
public void run() {
crossingP.start();
}
};
SwingUtilities.invokeLater(rotateCrossing1);
}
}
TestGUISSCE it contains the main method.
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
import javax.swing.*;
public class TestGUISSCE{
private RotateButtonSSCE rotate = new RotateButtonSSCE();
private VisualizationPanelSSCE vision = new VisualizationPanelSSCE();
public void createGui(){
JFrame frame = new JFrame("Example");
frame.setSize(new Dimension(500, 500));
JPanel pane = new JPanel();
pane.add(this.vision);
pane.add(rotate);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.setVisible(true);
}
public static void main(String[] args) {
new TestGUISSCE().createGui();
}
}
In addition to #tulskiy's helpful observations, I would add two points:
Always construct your GUI on the event dispatch thread, as shown below.
An sscce should be a Short, Self Contained, Correct (Compilable), Example. As a convenience, don't require others to recreate multiple public classes; use top-level (package-private) or nested classes. As this is a graphics problem, use a public or synthetic image that reflects your problem.
In the example below, paintComponent() alters the graphics context's transform to effect the rotation. Note that the operations are performed in the (apparent) reverse of the declaration order: First, the image's center is translated to the origin; second, the image is rotated; third, the image's center is translated to the center of the panel. You can see the effect by resizing the panel.
Addendum: See also this alternative approach using AffineTransform.
package overflow;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;
/**
* #see https://stackoverflow.com/questions/3371227
* #see https://stackoverflow.com/questions/3405799
*/
public class RotateApp {
private static final int N = 3;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(N, N, N, N));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
for (int i = 0; i < N * N; i++) {
frame.add(new RotatePanel());
}
frame.pack();
frame.setVisible(true);
}
});
}
}
class RotatePanel extends JPanel implements ActionListener {
private static final int SIZE = 256;
private static double DELTA_THETA = Math.PI / 90;
private final Timer timer = new Timer(25, this);
private Image image = RotatableImage.getImage(SIZE);
private double dt = DELTA_THETA;
private double theta;
public RotatePanel() {
this.setBackground(Color.lightGray);
this.setPreferredSize(new Dimension(
image.getWidth(null), image.getHeight(null)));
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
image = RotatableImage.getImage(SIZE);
dt = -dt;
}
});
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
g2d.rotate(theta);
g2d.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
g2d.drawImage(image, 0, 0, null);
}
#Override
public void actionPerformed(ActionEvent e) {
theta += dt;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
}
class RotatableImage {
private static final Random r = new Random();
static public Image getImage(int size) {
BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(Color.getHSBColor(r.nextFloat(), 1, 1));
g2d.setStroke(new BasicStroke(size / 8));
g2d.drawLine(0, size / 2, size, size / 2);
g2d.drawLine(size / 2, 0, size / 2, size);
g2d.dispose();
return bi;
}
}
The code for Rotated Icon uses the AffineTransform to rotate about its center.
this.crossingP.paintComponent(comp2D);
Never do this! Your CrossingPane is not added to any component, so repaint() doesn't have any effect. You can check it by adding prints in the paintComponent() method. SO you need to add CrossingPane to the VisualizationPane:
setLayout(new BorderLayout());
add(crossingP, BorderLayout.CENTER);
There are some issues with centering the image, but this shouldn't be hard to fix.
PS. Read again about layouts and painting.

Issues Creating a Pen Tool with Java's Path2D

I've been attempting to create a pen tool for my Java drawing program using the Path2D class in conjunction with mouse listeners, but I've had baffling results. The tool will work for several seconds, but then the entire application will freeze and will have to be closed. (No exceptions occur here; the program just freezes). Here's an SSCCE that demonstrates the issue:
import java.awt.BasicStroke;
import java.awt.event.MouseAdapter;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PenDemoPanel extends JPanel {
private Point start;
private Point stop;
private Shape shape;
public PenDemoPanel() {
setBackground(Color.white);
setPreferredSize(new Dimension(600, 600));
PathListener listener = new PathListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
public void paintComponent(Graphics gc) {
super.paintComponent(gc);
Graphics2D g2 = (Graphics2D) gc;
if (start != null && stop != null) {
BasicStroke stroke = new BasicStroke(1);
shape = stroke.createStrokedShape(shape);
g2.draw(shape);
g2.fill(shape);
}
}
private class PathListener
extends MouseAdapter {
public void mousePressed(MouseEvent event) {
start = event.getPoint();
Path2D path = new Path2D.Double();
shape = path;
}
public void mouseDragged(MouseEvent event) {
stop = event.getPoint();
Path2D path = (Path2D) shape;
path.moveTo(start.x, start.y);
path.lineTo(stop.x, stop.y);
shape = path;
start = stop;
repaint();
}
public void mouseReleased(MouseEvent event) {
Path2D path = (Path2D) shape;
path.closePath();
shape = path;
repaint();
}
}
public static void main(String[] args) {
PenDemoPanel shapes = new PenDemoPanel();
JFrame frame = new JFrame("PenDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(shapes);
frame.pack();
frame.setVisible(true);
}
}
I had written my own Path class, which worked perfectly here, but I wanted to use some of the additional functionality in the Path2D class.
Am I doing something wrong here or is Path2D not capable of what I'm trying to do?
Any help would be greatly appreciated.
The problem seems to come from assigning the stroked shape back to the shape. If you avoid doing that, the app. remains responsive. Vis.
import java.awt.BasicStroke;
import java.awt.event.MouseAdapter;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import javax.swing.*;
public class PenDemoPanel extends JPanel {
private Point start;
private Point stop;
private Shape shape;
public PenDemoPanel() {
setBackground(Color.white);
setPreferredSize(new Dimension(600, 600));
PathListener listener = new PathListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
public void paintComponent(Graphics gc) {
super.paintComponent(gc);
Graphics2D g2 = (Graphics2D) gc;
if (start != null && stop != null) {
BasicStroke stroke = new BasicStroke(1);
Shape strokedShape = stroke.createStrokedShape(shape);
g2.draw(strokedShape);
g2.fill(strokedShape);
}
}
private class PathListener
extends MouseAdapter {
public void mousePressed(MouseEvent event) {
start = event.getPoint();
Path2D path = new Path2D.Double();
shape = path;
}
public void mouseDragged(MouseEvent event) {
stop = event.getPoint();
Path2D path = (Path2D) shape;
path.moveTo(start.x, start.y);
path.lineTo(stop.x, stop.y);
shape = path;
start = stop;
repaint();
}
public void mouseReleased(MouseEvent event) {
Path2D path = (Path2D) shape;
try {
path.closePath();
} catch(Exception ingore) {
}
shape = path;
repaint();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
PenDemoPanel shapes = new PenDemoPanel();
JFrame frame = new JFrame("PenDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(shapes);
frame.pack();
frame.setVisible(true);
}
});
}
}

Categories

Resources