How to get the (x,y) coordinates of JButton - java

Is there a method to get coordinates in the (x,y) format for a Jbutton similar to the pt.distance() method. The jbutton utilizes setLayout(null) and setBounds(x,y,0,0).
How would I compare the results of a pt.distance() and Jbutton(x,y)?
Lastly, how is (x,y) calculated?
Point pt = evt.getPoint();
double u = pt.distance((double)x,(double)y);
double k = st1.getAlignmentX();
double p = st1.getAlignmentY();
if(u > ){ // u has to be measured to (x,y) value of jbutton
tim.setDelay(500);
}
if(u < ){
tim.setDelay(100);
}

If by pt.distance(), you are referring to the Point2D.distance() method then you could proceed like this:
Point location = button.getLocation(); // where button is your JButton object
double distance = pt.distance(location); // where pt is your Point2D object
Or:
double distance = pt.distance(button.getX(), button.getY());
The Point will then contain your button's x and y coordinate. If you are not using layouts, these values will be what you set them to. But if you are using layouts, then the LayoutManager of the parent is responsible for calculating the values.
In response to your edit:
I don't understand what you are trying to do. Calling setLayout(null) on the JButton will not let you set the coordinates of the button, only it's children. I think this is what you are trying to achieve:
Point pt = evt.getPoint();
double distance = pt.distance(button);
int someLength = 100; // the distance away from the button the point has to be to decide the length of the delay
if (distance < someLength) {
tim.setDelay(500);
} else {
tim.setDelay(100);
}

Howabout getLocation, which returns the coordinate of a component on its parent component, or getLocationOnScreen, which returns the coordinate of the component on the display?
Your second question about how x and y are calculated, I'm not sure what you mean by 'calculated'. The coordinates will be relative to something. Usually either the parent component (like a JPanel on which the JButton is sitting) or a location on the screen (such as getLocation would return for a JFrame).
A method like Point.distance will subtract the two coordinates' x and y values and tell you the difference. This is just basic geometry.
For example, here is a method that will return the distance of a point from the center of a JButton:
public static double getDistance(Point point, JComponent comp) {
Point loc = comp.getLocation();
loc.x += comp.getWidth() / 2;
loc.y += comp.getHeight() / 2;
double xdif = Math.abs(loc.x - point.x);
double ydif = Math.abs(loc.y - point.y);
return Math.sqrt((xdif * xdif) + (ydif * ydif));
}
This returns the hypotenuse of a triangle as a measurement in pixels, which means if the point you give it (like cursor coordinates) is at a diagonal it will give you the useful distance.
Point.distance will do something like this.
I've noticed this old answer of mine has gotten quite a few views, so here is a better way to do the above (but doesn't really show the math):
public static double distance(Point p, JComponent comp) {
Point2D.Float center =
// note: use (0, 0) instead of (getX(), getY())
// if the Point 'p' is in the coordinates of 'comp'
// instead of the parent of 'comp'
new Point2D.Float(comp.getX(), comp.getY());
center.x += comp.getWidth() / 2f;
center.y += comp.getHeight() / 2f;
return center.distance(p);
}
Here's a simple example showing this kind of geometry in a Swing program:
This draws a line to wherever the mouse cursor is and displays the length of the line (which is the distance from the center of the JPanel to the cursor).
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
class DistanceExample implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new DistanceExample());
}
#Override
public void run() {
JLabel distanceLabel = new JLabel("--");
MousePanel clickPanel = new MousePanel();
Listener listener =
new Listener(distanceLabel, clickPanel);
clickPanel.addMouseListener(listener);
clickPanel.addMouseMotionListener(listener);
JPanel content = new JPanel(new BorderLayout());
content.setBackground(Color.white);
content.add(distanceLabel, BorderLayout.NORTH);
content.add(clickPanel, BorderLayout.CENTER);
JFrame frame = new JFrame();
frame.setContentPane(content);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
static class MousePanel extends JPanel {
Point2D.Float mousePos;
MousePanel() {
setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (mousePos != null) {
g.setColor(Color.red);
Point2D.Float center = centerOf(this);
g.drawLine(Math.round(center.x),
Math.round(center.y),
Math.round(mousePos.x),
Math.round(mousePos.y));
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
}
static class Listener extends MouseAdapter {
JLabel distanceLabel;
MousePanel mousePanel;
Listener(JLabel distanceLabel, MousePanel mousePanel) {
this.distanceLabel = distanceLabel;
this.mousePanel = mousePanel;
}
#Override
public void mouseMoved(MouseEvent e) {
Point2D.Float mousePos =
new Point2D.Float(e.getX(), e.getY());
mousePanel.mousePos = mousePos;
mousePanel.repaint();
double dist = distance(mousePos, mousePanel);
distanceLabel.setText(String.format("%.2f", dist));
}
#Override
public void mouseExited(MouseEvent e) {
mousePanel.mousePos = null;
mousePanel.repaint();
distanceLabel.setText("--");
}
}
static Point2D.Float centerOf(JComponent comp) {
Point2D.Float center =
new Point2D.Float((comp.getWidth() / 2f),
(comp.getHeight() / 2f));
return center;
}
static double distance(Point2D p, JComponent comp) {
return centerOf(comp).distance(p);
}
}

Something like this?:
JButton b = new JButton();
b.getAlignmentX();
b.getAlignmentY();
You can also use this:
Rectangle r = b.getBounds(); // The bounds specify this component's width, height,
// and location relative to its parent.

Related

Java swing how to layer custom paintings

BACKGROUND
I'm currently working on a project where I want to be able to draw a triangle, circle and a rectangle on a JPanel and move them around.
When a figure is moved, it should end up "at the top" so that it covers other figures that are in an overlapping position and when a figure is moved, the "top" is selected; if several figures cover the position of the mouse pointer, the top one is selected.
MY PROBLEM
I cannot figure out how to fix the issue of my shapes not ending up on top of each other if I drag them to the same position. They just stay in the same layers and if i stack them all on top of each other I still pick up the rectangle first.
They just stay in the same layers...
That's because of the order in which you are drawing them:
drawRec.paintComponent(g);
drawCirc.paintComponent(g);
drawTri.paintComponent(g);
Which will result in the drawTri to always be painted on top of the others (because you always paint it last). In a similar way, drawRec will be painted on the bottom of the others (since you are painting it always first). Then drawCirc will be painted in the middle. There is no dynamic order (let me put it) of the painting here. It doesn't matter what you drag or not, they are always going to be painted in that sequence.
A solution might be to have them in a list or array of some sort where when you drag one shape, you put it last in the list, and move the others before it. If you combine that with painting all shapes sequentialy from the list, then you will have your desired result.
...if i stack them all on top of each other I still pick up the rectangle first.
That's because of how your mousePressed in the ClickListener class works: it first of all checks if the rectangle is clicked and then the others. That means that if the rectangle overlaps another shape, then the rectangle is always going to be prioritized.
One solution might again be putting all your shapes in a data structure where you can modify their selection order. For example a list or array where let's say that the closer to the top is a shape then the later it will appear in the list. Then, when the user clicks somewhere, you will check the shapes starting from the last one in the list going to first one. If you find something, you immediately break the loop and select what you found. If you don't find anything, then the user clicked the panel somewhere where there are no shapes currently.
I am almost certain there must be more efficient data structures than a list or array for this problem (because you have to iterate in linear time all shapes to find the one clicked), but I am not an expert on collision detection, so, in order to keep things simple, I am going to stick with it. But there is also one other operation we want to do: change the painting and the selection order of the clicked shape. Long story short, we can use a LinkedHashSet for the job, because:
It maintains an order of the shapes.
We can quickly and efficiently change the order by first removing the clicked shape from its current postion (constant time) and then adding it back to the LinkedHashSet (also constant time), essentialy placing it last in the insertion order. So that automatically means that we have to use the last element in the set as the top most one. And that's good, because when painting we can iterate over all shapes in the set in the order in which they are found in it, so the last shape will be painted last (which means on top of all others). The same stands for the selection of a shape when clicking: we iterate over all elements, and select the last one which we found containing the user's click point. If LinkedHashSet had a descending iterator (by insertion order) then we could also optimize a little the searching of shapes at a given click point, but it doesn't (at least for Java 8 in which the following demonstration/example code applies), so I will stick with iterating from the start, checking all shapes every time and keeping the last one found containing the click point.
Finally, I would advise you to use the API class java.awt.Shape for the shapes since this enables you to create arbitrary shapes and get the contains method to check if a point lies inside them, the drawing/filling capability, the bounds, the path iterator, and so on...
Summarizing all the above, with an example code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class NonComponentMain {
public static Shape createRectangle(final double width,
final double height) {
return new Rectangle2D.Double(0, 0, width, height);
}
public static Shape createEllipse(final double width,
final double height) {
return new Ellipse2D.Double(0, 0, width, height);
}
public static Shape createCircle(final double radius) {
return createEllipse(radius + radius, radius + radius);
}
public static class MoveableShapeHolder {
private final Shape shape;
private final Paint paint;
private final Rectangle2D originalBounds;
private double offsetX, offsetY;
public MoveableShapeHolder(final Shape shape,
final Paint paint) {
this.shape = Objects.requireNonNull(shape);
this.paint = paint;
offsetX = offsetY = 0;
originalBounds = shape.getBounds2D();
}
public void paint(final Graphics2D g2d) {
final AffineTransform originalAffineTransform = g2d.getTransform();
final Paint originalPaint = g2d.getPaint();
g2d.translate(offsetX, offsetY);
if (paint != null)
g2d.setPaint(paint);
g2d.fill(shape);
g2d.setPaint(originalPaint);
g2d.setTransform(originalAffineTransform);
}
public void moveTo(final double newBoundsCenterX,
final double newBoundsCenterY) {
offsetX = newBoundsCenterX - originalBounds.getCenterX();
offsetY = newBoundsCenterY - originalBounds.getCenterY();
}
public void moveBy(final double dx,
final double dy) {
offsetX += dx;
offsetY += dy;
}
public boolean contains(final Point2D pt) {
return shape.contains(pt.getX() - offsetX, pt.getY() - offsetY);
}
public Point2D getTopLeft() {
return new Point2D.Double(offsetX + originalBounds.getX(), offsetY + originalBounds.getY());
}
public Point2D getCenter() {
return new Point2D.Double(offsetX + originalBounds.getCenterX(), offsetY + originalBounds.getCenterY()); //Like 'getTopLeft' but with adding half the size.
}
public Point2D getBottomRight() {
return new Point2D.Double(offsetX + originalBounds.getMaxX(), offsetY + originalBounds.getMaxY()); //Like 'getTopLeft' but with adding the size of the bounds.
}
}
public static class DrawPanel extends JPanel {
private class MouseDrag extends MouseAdapter {
private MoveableShapeHolder current;
private Point origin;
private Point2D center;
#Override
public void mousePressed(final MouseEvent e) {
current = null;
center = null;
final Point evtLoc = e.getPoint();
for (final MoveableShapeHolder moveable: moveables)
if (moveable.contains(evtLoc))
current = moveable; //Keep the last moveable found to contain the click point! It's important to be the last one, because the later the moveable appears in the collection, the closer to top its layer.
if (current != null) { //If a shape was clicked...
//Initialize MouseDrag's state:
origin = e.getPoint();
center = current.getCenter();
//Move to topmost layer:
moveables.remove(current); //Remove from its current position.
moveables.add(current); //Move to last (topmost layer).
//Rapaint panel:
repaint();
}
}
#Override
public void mouseDragged(final MouseEvent e) {
if (current != null) { //If we are dragging something (and not empty space), then:
current.moveTo(center.getX() + e.getX() - origin.x, center.getY() + e.getY() - origin.y);
repaint();
}
}
#Override
public void mouseReleased(final MouseEvent e) {
current = null;
origin = null;
center = null;
}
}
private final LinkedHashSet<MoveableShapeHolder> moveables;
public DrawPanel() {
moveables = new LinkedHashSet<>();
final MouseAdapter ma = new MouseDrag();
super.addMouseMotionListener(ma);
super.addMouseListener(ma);
}
/**
* Warning: all operations on the returned value must be made on the EDT.
* #return
*/
public Collection<MoveableShapeHolder> getMoveables() {
return moveables;
}
#Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
moveables.forEach(moveable -> moveable.paint((Graphics2D) g)); //Topmost moveable is painted last.
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet())
return super.getPreferredSize();
final Dimension preferredSize = new Dimension();
moveables.forEach(moveable -> {
final Point2D max = moveable.getBottomRight();
preferredSize.width = Math.max(preferredSize.width, (int) Math.ceil(max.getX()));
preferredSize.height = Math.max(preferredSize.height, (int) Math.ceil(max.getY()));
});
return preferredSize;
}
}
private static void createAndShowGUI() {
final DrawPanel drawPanel = new DrawPanel();
final Collection<MoveableShapeHolder> moveables = drawPanel.getMoveables();
MoveableShapeHolder moveable = new MoveableShapeHolder(createRectangle(100, 50), Color.RED);
moveable.moveTo(100, 75);
moveables.add(moveable);
moveable = new MoveableShapeHolder(createCircle(40), Color.GREEN);
moveable.moveTo(125, 100);
moveables.add(moveable);
moveable = new MoveableShapeHolder(createRectangle(25, 75), Color.BLUE);
moveable.moveTo(125, 75);
moveables.add(moveable);
final JFrame frame = new JFrame("Click to drag");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(drawPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(NonComponentMain::createAndShowGUI);
}
}
All the above apply in the case you want to work with a single custom component painting all shapes. If instead you want, you can create a custom component for each shape and use a JLayeredPane for positioning them on top of each other. But then you would probably need a custom LayoutManager too (in order to handle the location of each component).
I created the following GUI.
I created a Shape class. I used a java.awt.Point to hold the center point and a java.awt.Polygon to hold the actual shape. I was able to use the Polygon contains method to see if I was mouse clicking inside the polygon.
I created a ShapeModel class to hold a java.util.List of Shape instances. Creating the proper application model is so important when creating a Swing GUI.
I created a JFrame and a drawing JPanel. The drawing JPanel draws the List of Shape instances. Period. The MouseAdapter will take care of recalculating the polygon and repainting the JPanel.
The trick is in the MouseAdapter mousePressed method. I delete the selected Shape instance and add the selected Shape instance back to the List. This moves the selected shape to the top of the Z-order.
Here's the complete runnable code. I made all the classes inner classes so I could post this 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.Polygon;
import java.awt.RenderingHints;
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;
public class MoveShapes implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new MoveShapes());
}
private final DrawingPanel drawingPanel;
private final ShapeModel shapeModel;
public MoveShapes() {
this.shapeModel = new ShapeModel();
this.drawingPanel = new DrawingPanel();
}
#Override
public void run() {
JFrame frame = new JFrame("Move Shapes");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void repaint() {
drawingPanel.repaint();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel() {
this.setBackground(Color.WHITE);
this.setPreferredSize(new Dimension(600, 500));
MoveListener listener = new MoveListener();
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 (Shape shape : shapeModel.getShapes()) {
g2d.setColor(shape.getColor());
g2d.fillPolygon(shape.getShape());
}
}
}
public class MoveListener extends MouseAdapter {
private Point pressedPoint;
private Shape selectedShape;
#Override
public void mousePressed(MouseEvent event) {
this.pressedPoint = event.getPoint();
this.selectedShape = null;
List<Shape> shapes = shapeModel.getShapes();
for (int i = shapes.size() - 1; i >= 0; i--) {
Shape shape = shapes.get(i);
if (shape.getShape().contains(pressedPoint)) {
selectedShape = shape;
break;
}
}
if (selectedShape != null) {
shapes.remove(selectedShape);
shapes.add(selectedShape);
}
}
#Override
public void mouseReleased(MouseEvent event) {
moveShape(event.getPoint());
}
#Override
public void mouseDragged(MouseEvent event) {
moveShape(event.getPoint());
}
private void moveShape(Point point) {
if (selectedShape != null) {
int x = point.x - pressedPoint.x;
int y = point.y - pressedPoint.y;
selectedShape.incrementCenterPoint(x, y);
drawingPanel.repaint();
pressedPoint = point;
}
}
}
public class ShapeModel {
private final List<Shape> shapes;
public ShapeModel() {
this.shapes = new ArrayList<>();
this.shapes.add(new Shape(100, 250, Color.BLUE, ShapeType.TRIANGLE));
this.shapes.add(new Shape(300, 250, Color.RED, ShapeType.RECTANGLE));
this.shapes.add(new Shape(500, 250, Color.BLACK, ShapeType.CIRCLE));
}
public List<Shape> getShapes() {
return shapes;
}
}
public class Shape {
private final Color color;
private Point centerPoint;
private Polygon shape;
private final ShapeType shapeType;
public Shape(int x, int y, Color color, ShapeType shapeType) {
this.centerPoint = new Point(x, y);
this.color = color;
this.shapeType = shapeType;
createPolygon(shapeType);
}
public void incrementCenterPoint(int x, int y) {
centerPoint.x += x;
centerPoint.y += y;
createPolygon(shapeType);
}
private void createPolygon(ShapeType shapeType) {
this.shape = new Polygon();
switch (shapeType) {
case TRIANGLE:
int angle = 30;
int radius = 100;
for (int i = 0; i < 3; i++) {
Point point = toCartesianCoordinates(angle, radius);
shape.addPoint(point.x, point.y);
angle += 120;
}
break;
case RECTANGLE:
angle = 45;
radius = 100;
for (int i = 0; i < 4; i++) {
Point point = toCartesianCoordinates(angle, radius);
shape.addPoint(point.x, point.y);
angle += 90;
}
break;
case CIRCLE:
radius = 75;
for (angle = 0; angle < 360; angle++) {
Point point = toCartesianCoordinates(angle, radius);
shape.addPoint(point.x, point.y);
}
break;
}
}
private Point toCartesianCoordinates(int angle, int radius) {
double theta = Math.toRadians(angle);
int x = (int) Math.round(Math.cos(theta) * radius) + centerPoint.x;
int y = (int) Math.round(Math.sin(theta) * radius) + centerPoint.y;
return new Point(x, y);
}
public Color getColor() {
return color;
}
public Point getCenterPoint() {
return centerPoint;
}
public Polygon getShape() {
return shape;
}
}
public enum ShapeType {
TRIANGLE, RECTANGLE, CIRCLE
}
}
I cannot figure out how to fix the issue of my shapes not ending up on top of each other if I drag them to the same position
Components on a panel are painted based on the components ZOrder. The component with the lowest ZOrder is painted last.
So in the mousePressed metthod of your MouseListener when you select a component to drag you can change its ZOrder:
Component child = e.getComponent();
child.getParent().setComponentZOrder(child, 0);
Edit:
drawRec.paintComponent(g);
drawCirc.paintComponent(g);
drawTri.paintComponent(g);
I didn't notice that code before.
Never invoke paintComponent() directly.
Swing has a parent/child relationship. You just need to add each of the 3 shape panels to the parent panel. The parent panel will then paint the child components based on the ZOrder I described above. Get rid of those 3 lines of code.

How to multithread with paintComponent()?

I created an app that contains a square that bounces every time it touches an edge of the frame.I don't have issues lunching the app,the problem is that i don't know how to create various threads in order to have multiples squares inside the frame.
I tried multiple things but i can't figure out where i should create the threads.
I also noticed that the square is visible only when i add it directly inside the frame and not when i put it inside a JPanel.
Square.java
public class Square extends JComponent implements ActionListener {
int width = 20;
int height = 20;
double y = Math.random() * 360;
double x = Math.random() * 360;
boolean xMax = false;
boolean yMax = false;
boolean xMin = true;
boolean yMin = true;
Rectangle2D.Double square = new Rectangle2D.Double(x, y, width, height);
public Square() {
Timer t = new Timer(2, this);
t.start();
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g);
g2.setColor(Color.BLUE);
g2.fill(square);
x_y_rules();
}
public void x_y_rules() {
if (xMax == true) {
x = x - 0.5;
if (x <= 0) {
xMax = false;
}
} else {
x = x + 0.5;
if (x >= this.getWidth()) {
xMax = true;
}
}
if (yMax == true) {
y = y - 0.5;
if (y <= 0) {
yMax = false;
}
} else {
y = y + 0.5;
if (y >= this.getHeight()) {
yMax = true;
}
}
square.setFrame(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent arg0) {
repaint();
}
}
App.java
public class App extends JFrame {
public static void main(String[] args) {
JFrame jf = new JFrame();
Square sqr = new Square();
jf.setSize(400, 400);
jf.setVisible(true);
jf.add(sqr);
jf.setDefaultCloseOperation(EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
}
}
Is it normal that despite i put a time of 2 inside the Timer,the square moves very slowly?
Issues:
you've got program logic, the x_y_rules() method call, inside of the paintComponent method. Get it out as it does not belong there, and instead put it into the Timer's ActionListener code where it belongs.
you can give each Square its own Swing Timer if you want. This isn't really a threading issue since each Timer's ActionListener will run on the EDT.
Two milliseconds is an unrealistic time slice to expect to use in a Swing Timer and no timer will run that fast. 11 to 13 is about the fastest to expect or hope for.
if you want your sprite to move faster, give it a greater value for delta-x and delta-y in your movement code.
Your JComponent has no preferred size defined which is likely why it's not showing up in the JPanel, since the default FlowLayout will size it then to [0, 0]. Override its getPreferredSize() and have it return a reasonable Dimension value.
you're calling setVisible(true) on your JFrame before adding all components, a no-no.
Ok,i put a getPrefferedSize() inside the square class but i've encountered a problem: the squares are not "together",it's like they're bouncing on separate panels
Then your program structure is broken. You really don't want create separate Swing components, and in fact your Square class shouldn't extend JComponent or JPanel. Rather
Square should be a logical class, one that extends from nothing (other than default Object).
Give it a drawing method, say public void draw(Graphics g) {....}
Create one class that extends JPanel, say called DrawingPanel, and override its paintComponent method.
Give the DrawingPanel class an ArrayList<Square> so that it can hold multiple Square objects.
Give the DrawingPanel class a Swing Timer
In the DrawingPanel class's Timer, have it update the position of all the Squares in the ArrayList, and then call repaint()
In the paintComponent method, iterate through all the Squares in the list, using a for loop, and call each one's draw method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 20;
private static final Color[] SQUARE_COLOR = { Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.BLACK, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.RED, Color.YELLOW };
List<Square> squareList = new ArrayList<>();
public DrawingPanel() {
// create a bunch of squares
for (int i = 0; i < SQUARE_COLOR.length; i++) {
squareList.add(new Square(SQUARE_COLOR[i], PREF_W, PREF_H));
}
setBackground(Color.WHITE);
// create and start the timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// simply draw all the squares in the list
for (Square square : squareList) {
square.draw(g);
}
}
// set size of JPanel
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// simply iterate through list and move all squares
for (Square square : squareList) {
square.move();
}
repaint(); // then repaint the GUI
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// this class does *not* extend JPanel or JComponent
class Square {
public static final int WIDTH = 20;
// location of Square
private double sqrX;
private double sqrY;
// X and Y speed
private double deltaX;
private double deltaY;
// width and height of DrawingPanel JPanel
private int dpWidth;
private int dpHeight;
// image to draw
private Image image;
public Square(Color color, int dpWidth, int dpHeight) {
this.dpWidth = dpWidth;
this.dpHeight = dpHeight;
// create square at random location with random speed
sqrX = Math.random() * (dpWidth - WIDTH);
sqrY = Math.random() * (dpHeight - WIDTH);
deltaX = Math.random() * 10 - 5;
deltaY = Math.random() * 10 - 5;
// one way to draw it is to create an image and draw it
image = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.setColor(color);
g.fillRect(0, 0, WIDTH, WIDTH);
g.dispose();
}
public void move() {
// check that we're not hitting boundaries
if (sqrX + deltaX < 0) {
deltaX = Math.abs(deltaX);
}
if (sqrX + deltaX + WIDTH >= dpWidth) {
deltaX = -Math.abs(deltaX);
}
sqrX += deltaX;
// check that we're not hitting boundaries
if (sqrY + deltaY < 0) {
deltaY = Math.abs(deltaY);
}
if (sqrY + deltaY + WIDTH >= dpHeight) {
deltaY = -Math.abs(deltaY);
}
sqrY += deltaY;
}
public void draw(Graphics g) {
int x = (int) sqrX;
int y = (int) sqrY;
g.drawImage(image, x, y, null);
}
}

Drawing circle at centre of JFrame doesn't update after `repaint()`

I am writing a program which involves creating a JFrame and drawing a circle inside it using drawOval() from the Graphics class. I have reached a problem where I am trying to create a point at the centre of the JFrame, and then draw my circle with this pont being the x and y coordinates of the circle. Here is my code so far:
import java.awt.Graphics;
import javax.swing.JFrame;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.Point;
class MouseJFrameMotion extends JFrame implements MouseMotionListener{
int circleXcenter;
int circleYcenter;
int circleRadius = 50;
boolean show = false;
public MouseJFrameMotion(){
addMouseMotionListener(this);
}
public void paint(Graphics g){
super.paint(g);
if(show){
g.drawOval(circleXcenter,circleYcenter, circleRadius*2,circleRadius*2);
}
}
public void mouseDragged(MouseEvent e){
}
Point frameCenter = new Point((this.getWidth()/2), (this.getHeight()/2));
public void mouseMoved(MouseEvent e){
int xLocation = e.getX();
int yLocation = e.getY();
show = true;
circleXcenter = (int) frameCenter.getX();
circleYcenter = (int) frameCenter.getY();
repaint();
}
}
public class GrowingCircle {
public static void main(String[] args) {
MouseJFrameMotion myMouseJFrame = new MouseJFrameMotion();
myMouseJFrame.setSize(500, 500);
myMouseJFrame.setVisible(true);
}
}
As you can see in the main() function, I set the size of the JFrame to 500x500. However, when the circle is drawn, it's x and y coordinates are (0,0) when I expect them to be (250, 250) based on Point frameCenter after repaint() is called. Where am I going wrong?
So two things...
Don't override paint of JFrame, there a JRootPane, contentPane and other components between the user and the frames surface which can interfere with the painting. Instead, use a JPanel and override its paintComponent method
At the time Point frameCenter = new Point((this.getWidth()/2), (this.getHeight()/2)); is evaluated, the frame's size is 0x0, you need to reevaluate the frameCenter before you paint the circle. When you do this will depend on how dynamic you want the change to be
I think you need both repaint() and revalidate() method
When you are constructing the class MouseJFrameMotion, the variable frameCenter is defined and set width and height 0 and it will never change. So what you can do is to calculate the frame center when you are drawing.
public void mouseMoved(MouseEvent e) {
int xLocation = e.getX();
int yLocation = e.getY();
show = true;
Point frameCenter = new Point((this.getWidth() / 2), (this.getHeight() / 2));
circleXcenter = (int) frameCenter.getX();
circleYcenter = (int) frameCenter.getY();
repaint();
}

Java hit-test behaving strangely

I was trying to extend the java ScrollDemo2 to report if a shape on the canvas had been clicked. I started with a simple rectangle believing that it should be no problem to simply loop over rectangle in the canvas checking if the click point was contained within them. But then something peculiar happened, the contains methods only seems to care if the point is in a rectangle anchored at (0,0), and doesn't seem to care that my component is at y=20. Therefore i get a hit if I click withing the Jpanel from x:[0,20] y:[0,20] when I should only get a hit if I click on x:[0,20] y[20,40]. Is this a bug, or am I doing something incorrectly?
public class CachedDrawableComponent extends JComponent
{
//this will do more later
CachedDrawableComponent(Rectangle bounds)
{
this.setBounds(bounds);
}
protected void paintComponent(Graphics g)
{
g.setColor(Color.magenta);
Rectangle r = this.getBounds();
g.fillRect(r.x, r.y, r.width, r.height);
}
}
public class ScrollDemo2 extends JPanel
implements MouseListener {
private Dimension area; //indicates area taken up by graphics
private Vector<Rectangle> circles; //coordinates used to draw graphics
private Vector<CachedDrawableComponent> otherDrawables;
private JPanel drawingPane;
private final Color colors[] = {
Color.red, Color.blue, Color.green, Color.orange,
Color.cyan, Color.magenta, Color.darkGray, Color.yellow};
private final int color_n = colors.length;
public ScrollDemo2() {
super(new BorderLayout());
area = new Dimension(0,0);
circles = new Vector<Rectangle>();
this.otherDrawables = new Vector<CachedDrawableComponent>();
//Set up the instructions.
JLabel instructionsLeft = new JLabel(
"Click left mouse button to place a circle.");
JLabel instructionsRight = new JLabel(
"Click right mouse button to clear drawing area.");
JPanel instructionPanel = new JPanel(new GridLayout(0,1));
instructionPanel.setFocusable(true);
instructionPanel.add(instructionsLeft);
instructionPanel.add(instructionsRight);
//Set up the drawing area.
drawingPane = new DrawingPane();
drawingPane.setBackground(Color.white);
drawingPane.addMouseListener(this);
TestRect t = new TestRect(new Rectangle(0,20,20,20));
this.otherDrawables.add(t);
//Put the drawing area in a scroll pane.
JScrollPane scroller = new JScrollPane(drawingPane);
scroller.setPreferredSize(new Dimension(200,200));
//Lay out this demo.
add(instructionPanel, BorderLayout.PAGE_START);
add(scroller, BorderLayout.CENTER);
}
/** The component inside the scroll pane. */
public class DrawingPane extends JPanel {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle rect;
for (int i = 0; i < circles.size(); i++) {
rect = circles.elementAt(i);
g.setColor(colors[(i % color_n)]);
g.fillOval(rect.x, rect.y, rect.width, rect.height);
}
for (int i = 0; i < otherDrawables.size(); i++) {
CachedDrawableComponent drawMe = otherDrawables.elementAt(i);;
g.setColor(colors[(i % color_n)]);
drawMe.paint(g);
}
}
}
//Handle mouse events.
public void mouseReleased(MouseEvent e) {
final int W = 100;
final int H = 100;
boolean changed = false;
if (SwingUtilities.isRightMouseButton(e)) {
//This will clear the graphic objects.
circles.removeAllElements();
area.width=0;
area.height=0;
changed = true;
} else {
int x = e.getX() - W/2;
int y = e.getY() - H/2;
if (x < 0) x = 0;
if (y < 0) y = 0;
Rectangle rect = new Rectangle(x, y, W, H);
circles.addElement(rect);
drawingPane.scrollRectToVisible(rect);
int this_width = (x + W + 2);
if (this_width > area.width) {
area.width = this_width; changed=true;
}
int this_height = (y + H + 2);
if (this_height > area.height) {
area.height = this_height; changed=true;
}
}
if (changed) {
//Update client's preferred size because
//the area taken up by the graphics has
//gotten larger or smaller (if cleared).
drawingPane.setPreferredSize(area);
//Let the scroll pane know to update itself
//and its scrollbars.
drawingPane.revalidate();
}
drawingPane.repaint();
}
public void mouseClicked(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
public void mousePressed(MouseEvent e)
{
System.out.println("Did press:"+e.getPoint());
System.out.println(otherDrawables.get(0).getBounds());
if(otherDrawables.get(0).contains(e.getPoint()))
{
System.out.println("Did Hit");
}
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("ScrollDemo2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
JComponent newContentPane = new ScrollDemo2();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
Output:
Did press:java.awt.Point[x=8,y=7]
java.awt.Rectangle[x=0,y=20,width=20,height=20]
Did Hit
Did press:java.awt.Point[x=14,y=89]
java.awt.Rectangle[x=0,y=20,width=20,height=20]
The face problem is that you are mixing coordinate systems:
the coordinates in the mouseListener are coordinates of the drawingPane
the coordinates expected in somecomponent.contains are coordinates of the component
From the api doc of contains:
Checks whether this component "contains" the specified point,
where x and y are defined to be
relative to the coordinate system of this component.
It's unusual (don't do without a reeeaal good reason :-) to have those components without actually adding them to the drawingPane.
Check out Playing With Shapes. It may give you some ideas for a slightly different approach.
For example by creating Shapes you don't need to keep track of Rectangle and others since all shapes will be treated the same.
You can also use the Shapes as components is you wish.

How to implement oval GradientPaint?

We know that there are a class named RadialGradientPaint in Java and we can use it to have a gradient painting for circle.
But I want to have an oval (ellipse) GradientPaint. How to implement oval GradientPaint?
Use an AffineTransform when drawing the RadialGradientPaint. This would require a scale instance of the transform. It might end up looking something like this:
import java.awt.*;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class OvalGradientPaint {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
JPanel gui = new JPanel(new BorderLayout());
gui.setBorder(new EmptyBorder(2, 3, 2, 3));
gui.add(new OvalGradientPaintSurface());
gui.setBackground(Color.WHITE);
JFrame f = new JFrame("Oval Gradient Paint");
f.add(gui);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
class OvalGradientPaintSurface extends JPanel {
public int yScale = 150;
public int increment = 1;
RadialGradientPaint paint;
AffineTransform moveToOrigin;
OvalGradientPaintSurface() {
Point2D center = new Point2D.Float(100, 100);
float radius = 90;
float[] dist = {0.05f, .95f};
Color[] colors = {Color.RED, Color.MAGENTA.darker()};
paint = new RadialGradientPaint(center, radius, dist, colors,CycleMethod.REFLECT);
moveToOrigin = AffineTransform.
getTranslateInstance(-100d, -100d);
ActionListener listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
if (increment < 0) {
increment = (yScale < 50 ? -increment : increment);
} else {
increment = (yScale > 150 ? -increment : increment);
}
yScale += increment;
repaint();
}
};
Timer t = new Timer(15, listener);
t.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
AffineTransform moveToCenter = AffineTransform.
getTranslateInstance(getWidth()/2d, getHeight()/2d);
g2.setPaint(paint);
double y = yScale/100d;
double x = 1/y;
AffineTransform at = AffineTransform.getScaleInstance(x, y);
// We need to move it to the origin, scale, and move back.
// Counterintutitively perhaps, we concatentate 'in reverse'.
moveToCenter.concatenate(at);
moveToCenter.concatenate(moveToOrigin);
g2.setTransform(moveToCenter);
// fudge factor of 3 here, to ensure the scaling of the transform
// does not leave edges unpainted.
g2.fillRect(-getWidth(), -getHeight(), getWidth()*3, getHeight()*3);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 200);
}
}
Original image: The original static (boring) 'screen shot' of the app.
RadialGradientPaint provides two ways to paint itself as an ellipse instead of a circle:
Upon construction, you can specify a transform for the gradient. For example, if you provide the following transform: AffineTransform.getScaleInstance(0.5, 1), your gradient will be an upright oval (the x dimension will be half that of the y dimension).
Or, you can use the constructor that requires a Rectangle2D be provided. An appropriate transform will be created to make the gradient ellipse bounds match that of the provided rectangle. I found the class documentation helpful: RadialGradientPaint API. In particular, see the documentation for this constructor.

Categories