I am trying to create JPanel with draggable crosses which appear after mouse clicking. Everything works fine but when I resize the JPanel the crosses disappear. I tried to override the paintComponent method in my JPanel but then all crosses are at coordinates (0,0). How can I fix it?
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class CrossPanel extends JPanel implements MouseListener {
private int orderOfCross = 0;
private ArrayList<Cross> crosses;
private int defaultSizeOfCrosses = 10;
CrossPanel() {
setOpaque(false);
addMouseListener(this);
crosses = new ArrayList<Cross>();
}
#Override
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Cross cross = new Cross(orderOfCross++, defaultSizeOfCrosses);
crosses.add(cross);
cross.setLocation(x - defaultSizeOfCrosses, y - defaultSizeOfCrosses);
add(cross);
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// for (int i = 0; i < crosses.size(); i++) {
// crosses.get(i).paint(g);
// }
}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CrossPanel crossPane = new CrossPanel();
f.getContentPane().add(crossPane);
f.setSize(600, 500);
f.setLocation(200, 200);
f.setVisible(true);
}
}
class Cross extends JComponent {
private int order;
protected Cursor draggingCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
private volatile int draggedAtX, draggedAtY;
int size;
public Cross(int order, int size) {
this.order = order;
this.size = size;
this.setBounds(0, 0, 4 * size, 3 * size + 10);
addDragListeners();
setCursor(draggingCursor);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.setStroke(new BasicStroke(3));
g2.drawLine(0, size, size + size, size);
g2.drawLine(size, 0, size, size + size);
Font f = new Font("Monospaced", Font.BOLD, size + 10);
g2.setFont(f);
g2.drawString(String.valueOf(order), size - size / 2, 3 * size + 10);
}
private void addDragListeners() {
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
draggedAtX = e.getX();
draggedAtY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
Point newLocation = new Point(e.getX() - draggedAtX + getLocation().x, e.getY() - draggedAtY + getLocation().y);
setLocation(newLocation);
}
});
}
}
I very rarely see a use for a null layout, for all the perceived benefits, there are simply to many draw backs.
The entire Swing API has been designed around the use of layout managers so you'd be crazy (IMHO) to simply throw all that work away.
If you find yourself in a position where the available layout managers don't seem to do what you want, it might be more worth while to write you own.
Here, I've presented a PropertionalLayoutManager whose intention is to provide layout capabilities that will place components on a container based an percentage of the width/height of the parent component. This means, as the parent component is resized, the child components will reposition themselves at a percentage of the parent size.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class CrossPanel extends JPanel implements MouseListener {
private int orderOfCross = 0;
private ArrayList<Cross> crosses;
private int defaultSizeOfCrosses = 10;
CrossPanel() {
setOpaque(false);
addMouseListener(this);
crosses = new ArrayList<Cross>();
setLayout(new PropertionalLayoutManager());
}
#Override
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Cross cross = new Cross(orderOfCross++, defaultSizeOfCrosses);
float xPos = (float)x / (float)getWidth();
float yPos = (float)y / (float)getHeight();
crosses.add(cross);
add(cross, new PropertionalConstraints(xPos, yPos));
revalidate();
}
public static String format(float value) {
return NumberFormat.getNumberInstance().format(value);
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CrossPanel crossPane = new CrossPanel();
f.getContentPane().add(crossPane);
f.setSize(600, 500);
f.setLocation(200, 200);
f.setVisible(true);
}
public class Cross extends JComponent {
private int order;
protected Cursor draggingCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
private volatile int draggedAtX, draggedAtY;
int size;
public Cross(int order, int size) {
this.order = order;
this.size = size;
// this.setBounds(0, 0, 4 * size, 3 * size + 10);
addDragListeners();
setCursor(draggingCursor);
Font f = new Font("Monospaced", Font.BOLD, size + 10);
setFont(f);
}
#Override
public Dimension getPreferredSize() {
// This is dangrous, you are making assumptions about platforms
// that you have no eviednce to support.
FontMetrics fm = getFontMetrics(getFont());
return new Dimension(Math.max(fm.stringWidth(String.valueOf(order)), size), size + fm.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.setStroke(new BasicStroke(3));
FontMetrics fm = g2.getFontMetrics();
int width = getWidth() - 1;
int height = getHeight() - 1;
int x = (width - fm.stringWidth(String.valueOf(order))) / 2;
int y = fm.getAscent();
g2.drawString(String.valueOf(order), x, y);
int crossSize = Math.min(width, height - fm.getHeight());
x = (width - crossSize) / 2;
y = fm.getHeight();
g2.drawLine(x, y, x + crossSize, y + crossSize);
g2.drawLine(x + crossSize, y, x, y + crossSize);
}
private void addDragListeners() {
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
draggedAtX = e.getX();
draggedAtY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
Point newLocation = new Point(e.getX() - draggedAtX + getLocation().x, e.getY() - draggedAtY + getLocation().y);
setLocation(newLocation);
}
});
}
}
public class PropertionalConstraints {
private float x;
private float y;
public PropertionalConstraints(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public void setX(float x) {
if (x > 1f) {
x = 1f;
} else if (x < -0f) {
x = 0f;
}
this.x = x;
}
public void setY(float y) {
if (y > 1f) {
y = 1f;
} else if (y < -0f) {
y = 0f;
}
this.y = y;
}
}
public class PropertionalLayoutManager implements LayoutManager2 {
private Map<Component, PropertionalConstraints> mapConstraints;
public PropertionalLayoutManager() {
mapConstraints = new HashMap<>(25);
}
public PropertionalConstraints getConstraintsFor(Component comp) {
return mapConstraints.get(comp);
}
public void setConstraintsFor(Component comp, PropertionalConstraints pc) {
mapConstraints.put(comp, pc);
}
#Override
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints instanceof PropertionalConstraints) {
mapConstraints.put(comp, (PropertionalConstraints) constraints);
} else {
throw new IllegalArgumentException("Constraints must be PropertionalConstraints");
}
}
#Override
public Dimension maximumLayoutSize(Container target) {
return preferredLayoutSize(target);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
mapConstraints.remove(comp);
}
#Override
public Dimension preferredLayoutSize(Container parent) {
return parent.getSize();
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
int width = parent.getWidth();
int height = parent.getHeight();
for (Component comp : parent.getComponents()) {
PropertionalConstraints con = mapConstraints.get(comp);
if (con != null) {
int x = (int)(width * con.getX());
int y = (int)(height * con.getY());
comp.setSize(comp.getPreferredSize());
comp.setLocation(x, y);
} else {
comp.setBounds(0, 0, 0, 0);
}
}
}
}
}
On some side notes, you are using "magic" numbers to determine the size and rendering position of certain elements. This is a very bad idea. You should, especially when painting or printing, base all these values on empirical values.
In this example, I've reverted to using FontMertrics to provide the required information to more accurately calculate the size and positions of various elements. This will allow for better cross platform support, as not all fonts are rendered the same across all platforms ;)
Related
I have to drag multiple JLabels simultaneously after selecting them. I figured out how to drag a single JLabel and how to select multiple ones but I don't how move more than one at a time.
Code of JFrame class
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
public class HallViewer extends JFrame {
private String nomeSala;
private JPanel draggableSeatsPanel;
private List<MyDraggableSeat> draggableSeats = new ArrayList<>();
public HallViewer(String nomeSala) {
this.nomeSala = nomeSala;
initDraggableSeatsList();
initDraggableSeatsPanel();
initFrame();
addMouseListener(mouseHandler);
addMouseMotionListener(mouseMotionHandler);
}
private void initDraggableSeatsList() {
MyDraggableSeat mds1 = new MyDraggableSeat(253, 210);
mds1.setText("A1");
MyDraggableSeat mds2 = new MyDraggableSeat(408, 341);
mds2.setText("A2");
MyDraggableSeat mds3 = new MyDraggableSeat(157, 213);
mds3.setText("A3");
draggableSeats = new ArrayList<>();
draggableSeats.add(mds1);
draggableSeats.add(mds2);
draggableSeats.add(mds3);
}
private void initDraggableSeatsPanel() {
draggableSeatsPanel = new JPanel();
draggableSeatsPanel.setMinimumSize(new Dimension(300, 150));
draggableSeatsPanel.setLayout(null);
for(MyDraggableSeat mds : draggableSeats) {
draggableSeatsPanel.add(mds);
initMouseListenerForMDS(mds);
repaint();
}
}
private void initFrame() {
setTitle("Piantina " + nomeSala);
addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent windowEvent) {
setVisible(false);
}
});
setLayout(new GridLayout(1,1));
add(draggableSeatsPanel);
setSize(700,500);
setLocationRelativeTo(null);
setVisible(true);
}
private int screenX = 0;
private int screenY = 0;
private int myX = 0;
private int myY = 0;
private void initMouseListenerForMDS(MyDraggableSeat mds) {
mds.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
screenX = e.getXOnScreen();
screenY = e.getYOnScreen();
myX = mds.getX();
myY = mds.getY();
}
});
mds.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
int deltaX = e.getXOnScreen() - screenX;
int deltaY = e.getYOnScreen() - screenY;
mds.setLocation(myX + deltaX, myY + deltaY);
}
});
}
private int xbegin;
private int ybegin;
private int xend;
private int yend;
private int width;
private int height;
private int x;
private int y;
private boolean isNewRect = false;
private List<MyDraggableSeat> selectedMDSList = new ArrayList<>();
private MouseListener mouseHandler = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
for(MyDraggableSeat mds : selectedMDSList) {
mds.setBorder(new LineBorder(Color.BLUE, 3));
}
selectedMDSList.clear();
xbegin = e.getX();
ybegin = e.getY();
isNewRect = true;
}
#Override
public void mouseReleased(MouseEvent e) {
xend = e.getX();
yend = e.getY();
isNewRect = true;
repaint();
Rectangle rectangle = new Rectangle(x,y,width,height);
for(MyDraggableSeat mds : draggableSeats) {
if(rectangle.intersects(new Rectangle(mds.getX(), mds.getY(), mds.getWidth(), mds.getHeight()))) {
selectedMDSList.add(mds);
}
}
for(MyDraggableSeat mds : selectedMDSList) {
mds.setBorder(new LineBorder(Color.CYAN, 3));
}
}
};
private MouseMotionListener mouseMotionHandler = new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
xend = e.getX();
yend = e.getY();
isNewRect = false;
repaint();
}
};
#Override
public void paint(Graphics g) {
super.paint(g);
if((xbegin-xend)<0) {
width = Math.abs(xbegin-xend);
x = xbegin;
} else {
width = Math.abs(xend-xbegin);
x = xend;
}
if((ybegin-yend)<0) {
height = Math.abs(ybegin-yend);
y = ybegin;
} else {
height = Math.abs(yend-ybegin);
y = yend;
}
Graphics2D g2D = (Graphics2D) g;
AlphaComposite transparency = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
g2D.setComposite(transparency);
g2D.setColor(new Color(0x0073b5e9));
if(!isNewRect) {
g2D.fill(new Rectangle2D.Float(x,y,width,height));
}
}
public static class Main {
public static void main(String[] argv){ new HallViewer("Example"); }
}
}
Code of MyDraggableSeat JLabel
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
public class MyDraggableSeat extends JLabel {
public MyDraggableSeat(int x, int y) {
setBorder(new LineBorder(Color.BLUE, 3));
setBackground(Color.WHITE);
setBounds(x, y, 30, 25);
setOpaque(true);
setHorizontalAlignment(JLabel.CENTER);
setVerticalAlignment(JLabel.CENTER);
}
}
The plan is to be able to select multiple JLabels like you select multiple files on your desktop (blue selection box) and to drag them around in the JFrame. I still want to be able to drag a single one if I choose to
I am trying to draw two circle on a panel with a line joining them, all after a button is pressed. So far (apart from tweaking locations of the line) this is ok. However, I would like to animate it using a timer. The first circle should appear, then gradually the line will be revealed, and finally the second circle.
I have looked at many examples of timers, but I can't seem to get it to work for me. I must be misunderstanding something.
here is the ball class (for each circle):
package twoBalls;
import java.awt.Color;
import java.awt.Point;
public class Ball {
private int x;
private int y;
private int r;
private Color color;
private Point location;
private Ball parent;
public Ball(int x, int y, int r) {
this.x = x;
this.y = y;
this.r = r;
Point p = new Point(x, y);
setLocation(p);
}
public void setParent(Ball b) {
parent = b;
}
public Ball getParent() {
return parent;
}
public void setx(int x) {
this.x = x;
}
public void sety(int y) {
this.y = y;
}
public int getx() {
return x;
}
public int gety() {
return y;
}
public int getr() {
return r;
}
public void setPreferedSize() {
}
public void setLocation(Point p) {
setx(p.x);
sety(p.y);
location = p;
}
public Point getLocation() {
return location;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
then the class that will store balls in an arrayList. And I think that this is where the actual drawing should take place, along with the timer.
I am trying to set the start and end point of the line to be the same, and increment the end point until it is where it should be, using the timer. I'm probably way of track, but that was the intention!
I have change this class, the if statements in the while loop can now be entered, as I am now comparing different point. But the line doesn't get drawn at all still.
package twoBalls;
import java.awt.Color;
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.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import javax.swing.JPanel;
import javax.swing.Timer;
public class BallsArray extends JPanel implements ActionListener {
private ArrayList<Ball> balls;
private Timer timer;
private final int DELAY = 25;
private int xDest;
private int yDest;
private Point dest;
private Point starts;
private int xStart;
private int yStart;
public BallsArray() {
balls = new ArrayList<Ball>();
timer = new Timer(DELAY, this);
yDest = 0;
xDest = 0;
dest = new Point(xDest, yDest);
starts = new Point(xStart, yStart);
}
public void setDestXY(int x, int y) {
xDest = x;
yDest = y;
dest = new Point(xDest, yDest);
setDest(dest);
}
public void setDest(Point p) {
dest = p;
}
public Point getDest() {
return dest;
}
public void setStartsXY(int x, int y) {
xStart = x;
yStart = y;
starts = new Point(xStart, yStart);
setStarts(starts);
}
public void setStarts(Point p) {
starts = p;
}
public Point getStarts() {
return starts;
}
public void addBall(Ball b) {
balls.add(b);
}
public void addBall(int x, int y, int r) {
balls.add(new Ball(x, y, r));
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
for (int i = 0; i < balls.size(); i++) {
if (i == 0) {
paintBall(balls.get(0), g2);
}
if (i != 0) {
int j = i - 1;
Ball bp = balls.get(j);
Ball bc = balls.get(i);
bc.setParent(bp);
paintLine(bc, g2);
paintBall(bc, g2);
}
}
}
public void paintBall(Ball b, Graphics2D g2d) {
Ellipse2D circ = new Ellipse2D.Float(b.getx(), b.gety(), b.getr(),
b.getr());
g2d.draw(circ);
}
public void paintLine(Ball b, Graphics2D g2d) {
timer.start();
if (b != null && b.getLocation() != null) {
Ball parent = b.getParent();
if (parent != null) {
g2d.setColor(Color.GRAY);
if (parent.getLocation() != null && b.getLocation() != null) {
setDest(parent.getLocation());
setStarts(parent.getLocation());
g2d.draw(new Line2D.Float(starts, dest));
}
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
// Not sure what I need to do here
// increment second location somehow
// Point s = getStarts();
Point p = getDest();
Point t = this.getLocation();
while (p != t) {
if (p.x != t.x && p.y != t.y) {
System.out.println("hello");
int x = dest.x;
int y = dest.y;
x++;
y++;
setDestXY(x, y);
p = getDest();
repaint();
} else if (p.x == t.x && p.y != t.y) {
System.out.println("part 2");
int y = dest.y;
y++;
setDestXY(dest.x, y);
p = getDest();
repaint();
} else if (p.x != t.x && p.y == t.y) {
System.out.println("part 3");
int x = dest.x;
x++;
setDestXY(x, dest.y);
p = getDest();
repaint();
}
repaint();
}
}
}
I have had a lot of help online getting this far, I worry I am just beyond my depth now!. I am unsure about the EventQueue/run part below. Here is the class to set it all up:
package twoBalls;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Display implements ActionListener {
private JFrame frame;
private JButton button;
private BallsArray b;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Display ex = new Display();
}
});
}
public Display() {
b = new BallsArray();
frame = new JFrame();
frame.setSize(800, 500);
frame.setTitle("Show balls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
button = new JButton("New Ball");
frame.add(button, BorderLayout.SOUTH);
frame.setVisible(true);
button.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent e) {
Ball ball1 = new Ball(100, 100, 50);
b.addBall(ball1);
b.addBall(200, 200, 50);
frame.add(b, BorderLayout.CENTER);
frame.revalidate();
frame.repaint();
}
}
At the moment it draws the two circles, but not the line at all.
When you make an animation, it helps to use the model / view / controller pattern.
Here's the GUI I created from your code.
I simplified your Ball class. This is all you need to define a ball.
package twoBalls;
import java.awt.Color;
import java.awt.Point;
public class Ball {
private final int radius;
private final Color color;
private final Point center;
public Ball(int x, int y, int radius, Color color) {
this(new Point(x, y), radius, color);
}
public Ball(Point center, int radius, Color color) {
this.center = center;
this.radius = radius;
this.color = color;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
I created the GUIModel class to hold all of the information your GUI needs. This separates the model from the view.
package twoBalls;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
public class GUIModel {
private double direction;
private double distance;
private List<Ball> balls;
private Point lineStartPoint;
private Point lineEndPoint;
public GUIModel() {
this.balls = new ArrayList<>();
}
public void addBall(Ball ball) {
this.balls.add(ball);
}
public List<Ball> getBalls() {
return balls;
}
public void calculatePoints() {
this.lineStartPoint = balls.get(0).getCenter();
this.lineEndPoint = balls.get(1).getCenter();
this.distance = Point.distance(lineStartPoint.x, lineStartPoint.y,
lineEndPoint.x, lineEndPoint.y);
this.direction = Math.atan2(lineEndPoint.y - lineStartPoint.y,
lineEndPoint.x - lineStartPoint.x);
}
public Point getCurrentPoint(int pos, int total) {
double increment = distance / total;
double length = increment * pos;
double x = lineStartPoint.x + Math.cos(direction) * length;
double y = lineStartPoint.y - Math.sin(direction) * length;
x = Math.round(x);
y = Math.round(y);
return new Point((int) x, (int) y);
}
public Point getLineStartPoint() {
return lineStartPoint;
}
}
This class holds the two Ball instances, and calculates the length and direction of the line, divided into total increments.
Now that we've defined the model classes, let's look at the view classes. The first is your Display class.
package twoBalls;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Display implements Runnable {
private GUIModel guiModel;
private JFrame frame;
public static void main(String[] args) {
EventQueue.invokeLater(new Display());
}
public Display() {
this.guiModel = new GUIModel();
Ball ball1 = new Ball(150, 200, 50, Color.BLUE);
Ball ball2 = new Ball(450, 200, 50, Color.GREEN);
guiModel.addBall(ball1);
guiModel.addBall(ball2);
guiModel.calculatePoints();
}
#Override
public void run() {
frame = new JFrame();
frame.setTitle("Show Balls Animation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
DrawingPanel drawingPanel = new DrawingPanel(guiModel);
panel.add(drawingPanel, BorderLayout.CENTER);
panel.add(createButtonPanel(drawingPanel), BorderLayout.SOUTH);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
private JPanel createButtonPanel(DrawingPanel drawingPanel) {
JPanel panel = new JPanel();
JButton startButton = new JButton("Start Animation");
startButton.addActionListener(new StartAnimation(drawingPanel));
panel.add(startButton);
return panel;
}
public class StartAnimation implements ActionListener {
private DrawingPanel drawingPanel;
public StartAnimation(DrawingPanel drawingPanel) {
this.drawingPanel = drawingPanel;
}
#Override
public void actionPerformed(ActionEvent event) {
LineRunnable runnable = new LineRunnable(drawingPanel);
new Thread(runnable).start();
}
}
}
The constructor of the Display class sets up the GUI model.
The run method of the Display class constructs the GUI, and starts the animation.
See how I've separated the model and view.
The StartAnimation class is your controller. It starts the animation when you left click on the JButton. I'll discuss the LineRunnable class later.
Next, let's take a look at the DrawingPanel class.
package twoBalls;
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 javax.swing.JPanel;
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = -3709678584255542338L;
private boolean drawLine;
private int pos;
private int total;
private GUIModel guiModel;
public DrawingPanel(GUIModel guiModel) {
this.guiModel = guiModel;
this.drawLine = false;
this.setPreferredSize(new Dimension(600, 400));
}
public boolean isDrawLine() {
return drawLine;
}
public void setDrawLine(boolean drawLine) {
this.drawLine = drawLine;
}
public void setPos(int pos) {
this.pos = pos;
repaint();
}
public void setTotal(int total) {
this.total = total;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Ball ball : guiModel.getBalls()) {
g2d.setColor(ball.getColor());
Point center = ball.getCenter();
int radius = ball.getRadius();
g2d.fillOval(center.x - radius, center.y - radius, radius + radius,
radius + radius);
}
if (isDrawLine()) {
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(5.0F));
Point a = guiModel.getLineStartPoint();
Point b = guiModel.getCurrentPoint(pos, total);
g2d.drawLine(a.x, a.y, b.x, b.y);
}
}
}
The only thing this view class does is draw the balls and the line. The responsibility for calculating the length of the line belongs in the model.
I set the preferred size here, and use the pack method in the Display class to get the size of the JFrame. You usually want to know the dimensions of the drawing area, rather than the entire window.
Finally, let's look at the LineRunnable class. This is the class that controls the animation.
package twoBalls;
import java.awt.EventQueue;
public class LineRunnable implements Runnable {
private int total;
private DrawingPanel drawingPanel;
public LineRunnable(DrawingPanel drawingPanel) {
this.drawingPanel = drawingPanel;
this.total = 240;
}
#Override
public void run() {
setDrawLine();
for (int pos = 0; pos <= total; pos++) {
setPos(pos);
sleep(50L);
}
}
private void setDrawLine() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.setDrawLine(true);
drawingPanel.setTotal(total);
}
});
}
private void setPos(final int pos) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.setPos(pos);
}
});
}
private void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
}
}
}
In the run method, we divide the line into 240 segments, and draw a segment every 50 milliseconds. It takes the GUI 12 seconds to draw the line. You can play with these numbers if you wish.
The for loop is a classic animation loop. First you update the model, which I'm doing through the drawing panel. Then you sleep.
This animation loop is running on a different thread from the GUI thread. This keeps the GUI responsive. Since the loop is running on a different thread, we have to use the invokeLater method to draw on the Event Dispatch thread.
I hope this was helpful to you. Divide and conquer. Don't let a class do more than one thing.
There is one Paraview user interface as follow attracted me.
I think this interface can be used to assign value into array. It works like this :
I want to implement this into a Java program but I found no Java API can support my idea. The closest design from me would be adding multiple JSlider like this :
But what if it is a 100 size array, I wouldn't want to add 100 JSliders. Do you have better solution for this ?
Okay, so this is a pretty basic example. It needs a lot more work and optimisation, but should get you moving in the right direction
Have a look at Painting in AWT and Swing, Performing Custom Painting, 2D Graphics and How to Write a Mouse Listener for more details
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestGraph {
public static void main(String[] args) {
new TestGraph();
}
public TestGraph() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new GraphPane(0, 100, new int[100]));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class GraphPane extends JPanel {
protected static final int COLUMN_WIDTH = 10;
protected static final int VERTICAL_INSETS = 10;
private int[] data;
private int minValue, maxValue;
private Path2D.Double graph;
private List<Shape> buttons;
private Point mousePoint;
public GraphPane(int minValue, int maxValue, int[] data) {
this.data = data;
this.minValue = minValue;
this.maxValue = maxValue;
buttons = new ArrayList<>(data == null ? 25 : data.length);
updateView();
MouseAdapter ma = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
updateData(e);
}
#Override
public void mouseDragged(MouseEvent e) {
updateData(e);
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
}
protected void updateData(MouseEvent e) {
// Which "column" was clicked on
int column = (int) Math.round(((double) e.getX() / (double) COLUMN_WIDTH)) - 1;
// Get the "height" of the clickable area
int clickRange = getHeight() - (VERTICAL_INSETS * 2);
// Adjust the y click point for the margins...
int yPos = e.getY() - VERTICAL_INSETS;
// Calculate the vertical position that was clicked
// this ensures that the range is between 0 and clickRange
// You could choose to ignore values out side of this range
int row = Math.min(Math.max(clickRange - yPos, 0), clickRange);
// Normalise the value between 0-1
double clickNormalised = row / (double) clickRange;
// Calculate the actual row value...
row = minValue + (int) (Math.round(clickNormalised * maxValue));
// Update the data
data[column] = row;
mousePoint = new Point(
COLUMN_WIDTH + (column * COLUMN_WIDTH),
getHeight() - (VERTICAL_INSETS + (int) Math.round((data[column] / 100d) * clickRange)));
updateView();
repaint();
}
#Override
public void invalidate() {
super.invalidate();
updateView();
}
protected Shape createButton(int xPos, int yPos) {
return new Ellipse2D.Double(xPos - COLUMN_WIDTH / 2, yPos - COLUMN_WIDTH / 2, COLUMN_WIDTH, COLUMN_WIDTH);
}
protected void updateView() {
graph = new Path2D.Double();
buttons.clear();
if (data != null && data.length > 0) {
int verticalRange = getHeight() - (VERTICAL_INSETS * 2);
int xPos = COLUMN_WIDTH;
int yPos = getHeight() - (VERTICAL_INSETS + (int) Math.round((data[0] / 100d) * verticalRange));
graph.moveTo(xPos, yPos);
if (data[0] > 0) {
buttons.add(createButton(xPos, yPos));
}
for (int index = 1; index < data.length; index++) {
xPos = (index * COLUMN_WIDTH) + COLUMN_WIDTH;
yPos = getHeight() - (VERTICAL_INSETS + (int) Math.round((data[index] / 100d) * verticalRange));
graph.lineTo(xPos, yPos);
if (data[index] > 0) {
buttons.add(createButton(xPos, yPos));
}
}
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(data == null ? 0 : (data.length + 1) * COLUMN_WIDTH, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (data != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(64, 64, 64, 32));
for (int index = 0; index < data.length; index++) {
int xPos = (index * COLUMN_WIDTH) + COLUMN_WIDTH;
g2d.drawLine(xPos, VERTICAL_INSETS, xPos, getHeight() - VERTICAL_INSETS);
}
g2d.setColor(Color.BLACK);
g2d.draw(graph);
for (Shape button : buttons) {
g2d.fill(button);
}
if (mousePoint != null) {
g2d.setColor(new Color(255, 192, 203));
Ellipse2D dot = new Ellipse2D.Double((mousePoint.x - COLUMN_WIDTH / 2) - 2, (mousePoint.y - COLUMN_WIDTH / 2) - 2, COLUMN_WIDTH + 4, COLUMN_WIDTH + 4);
g2d.draw(dot);
g2d.setColor(new Color(255, 192, 203, 128));
g2d.fill(dot);
}
g2d.dispose();
}
}
}
}
Before anyone says I didn't put the "fill" in, I deliberately used a Path2D to make it much simpler to achieve ;)
here is a small example how to create this using polygon class .i sorted x coordinate and use polygon class to make this.
GraphPane.class
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.Collections;
import javax.swing.JPanel;
public class GraphPane extends JPanel {
ArrayList<XYpoints> poinList = new ArrayList();
private int px;
private int py;
private XYpoints last;
private boolean drag;
private static Color graphColor=new Color(32, 178, 170);
public GraphPane() {
initComponents();
poinList.add(new XYpoints(50, 400));
poinList.add(new XYpoints(450, 50));
poinList.add(new XYpoints(600, 400));
}
private void initComponents() {
setBackground(new java.awt.Color(255, 255, 255));
addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
public void mouseDragged(java.awt.event.MouseEvent evt) {
System.out.println("drag");
if (drag) {
last.setY(evt.getY());
GraphPane.this.repaint();
}
}
});
addMouseListener(new java.awt.event.MouseAdapter() {
public void mousePressed(java.awt.event.MouseEvent evt) {
int x = evt.getX();
int y = evt.getY();
for (XYpoints poinList1 : poinList) {
px = poinList1.getpX();
py = poinList1.getpY();
if (x < px + 5 && x > px - 5 && y < py + 5 && y > py - 5) {
System.out.println("inter");
poinList1.setIntersect(true);
last = poinList1;
drag = true;
GraphPane.this.repaint();
return;
}
}
poinList.add(new XYpoints(x, y));
Collections.sort(poinList, new XComp());
GraphPane.this.repaint();
}
public void mouseReleased(java.awt.event.MouseEvent evt) {
if (drag) {
drag = false;
last.setIntersect(false);
GraphPane.this.repaint();
}
}
});
}
#Override
protected void paintComponent(Graphics gr) {
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr.create();
Polygon p = new Polygon();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (XYpoints poinList1 : poinList) {
px = poinList1.getpX();
py = poinList1.getpY();
p.addPoint(px, py);
}
g.setColor(graphColor);
g.fillPolygon(p);
for (XYpoints poinList1 : poinList) {
px = poinList1.getpX();
py = poinList1.getpY();
g.setColor(Color.red);
if (poinList1.isIntersect()) {
g.setColor(Color.blue);
}
g.fillOval(px - 5, py - 5, 10, 10);
}
g.dispose();
}
}
XYpoints.class
import java.awt.Polygon;
import java.util.Comparator;
public class XYpoints extends Polygon {
private int x;
private int y;
private boolean inter;
public XYpoints(int x, int y) {
this.x = x;
this.y = y;
}
public void setIntersect(boolean state) {
inter = state;
}
public void setY(int y){
this.y=y;
}
public boolean isIntersect() {
return inter;
}
public int getpX() {
//System.out.println("send " + this.x);
return this.x;
}
public int getpY() {
return this.y;
}
}
XComp .class
class XComp implements Comparator<XYpoints> {
#Override
public int compare(XYpoints t, XYpoints t1) {
if (t.getpX() < t1.getpX()) {
return -1;
} else {
return 1;
}
}
}
myframe.class
import javax.swing.JFrame;
public class myframe extends JFrame {
public myframe() {
GraphPane pane = new GraphPane();
setContentPane(pane);
setSize(650, 500);
setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new myframe();
}
});
}
}
A little rough with java but can someone tell me how I take a draw method from a list of objects, say there's more than 1 box, and put it in the actionPerform so I could modify the getter of X and Y to repaint() them and make them move?
From Box Class:
public void draw(Graphics g) {
g.setColor(Color.BLUE);
g.fillRect((int) (getX() - (width/2)), (int) (getY() - (height/2)), getWidth(), getHeight());
}
actionPerform method: (This is in another class)
public void actionPerformed(ActionEvent e){
timer.start();
}
The basic idea is...
When actionPerformed is called...
Iterate through the list of objects and update them in some meaningful way
Call repaint...
Then within your components paintComponent method, iterate the list and paint them
The following is a very basic concept...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FlyingBoxes {
public static void main(String[] args) {
new FlyingBoxes();
}
public interface Drawable {
public void update(JComponent comp);
public void draw(Graphics g);
}
public class Box implements Drawable {
private int x;
private int y;
private int width = 10;
private int height = 10;
private int xDelta;
private int yDelta;
public Box(int x, int y) {
this.x = x;
this.y = y;
xDelta = random();
yDelta = random();
}
#Override
public void update(JComponent comp) {
x += xDelta;
y += yDelta;
if (x < 0) {
x = 0;
xDelta *= -1;
} else if (x + width > comp.getWidth()) {
x = comp.getWidth() - width;
xDelta *= -1;
}
if (y < 0) {
y = 0;
yDelta *= -1;
} else if (y + height > comp.getHeight()) {
y = comp.getHeight() - height;
yDelta *= -1;
}
}
#Override
public void draw(Graphics g) {
g.setColor(Color.BLUE);
g.fillRect(x, y, width, height);
}
protected int random() {
int value = 0;
do {
value = -2 + (int)(Math.random() * 4);
} while (value == 0);
return value;
}
}
public FlyingBoxes() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<Drawable> drawables;
public TestPane() {
drawables = new ArrayList<>(25);
for (int index = 0; index < 25; index++) {
int x = (int) (Math.random() * 190);
int y = (int) (Math.random() * 190);
drawables.add(new Box(x, y));
}
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Drawable d : drawables) {
d.update(TestPane.this);
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable d : drawables) {
d.draw(g);
}
}
}
}
newbie programmer here.
I'm making a program that renders user-inputted equations in a Cartesian coordinate system. At the moment I'm having some issues with letting the user move the view around freely in the coordinate. Currently with mouseDragged the user can drag the view around a bit, but once the user releases the mouse and tries to move the view again the origin snaps back to the current position of the mouse cursor. What is the best way to let the user move around freely? Thanks in advance!
Here's the code for the drawing area.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JPanel;
public class DrawingArea extends JPanel implements MouseMotionListener {
private final int x_panel = 350; // width of the panel
private final int y_panel = 400; // height of the panel
private int div_x; // width of one square
private int div_y; // height of one square
private int real_y;
private int real_x;
private Point origin; // the origin of the coordinate
private Point temp; // temporary point
private static int y = 0;
private static int x = 0;
DrawingArea() {
setBackground(Color.WHITE);
real_x = x_panel;
real_y = y_panel;
setDivisionDefault();
setOrigin(new Point((real_x / 2), (real_y / 2)));
setSize(x_panel, y_panel);
addMouseMotionListener(this);
}
DrawingArea(Point origin, Point destination) {
this.origin = origin;
this.destination = destination;
panel = new JPanel();
panel.setSize(destination.x, destination.y);
panel.setLocation(origin);
this.panel.setBackground(Color.red);
panel.setLayout(null);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D line = (Graphics2D) g;
temp = new Point(origin.x, origin.y);
line.setColor(Color.red);
drawHelpLines(line);
line.setColor(Color.blue);
drawOrigin(line);
line.setColor(Color.green);
for (int i = 0; i < 100; i++) { // This is a test line
//temp = this.suora();
temp.x++;
temp.y++;
line.drawLine(temp.x, temp.y, temp.x, temp.y);
}
}
public void setOrigin(Point p) {
origin = p;
}
public void drawOrigin(Graphics2D line) {
line.drawLine(origin.x, 0, origin.x, y_panel);
line.drawLine(0, origin.y, x_panel, origin.y);
}
public void drawHelpLines(Graphics2D line) {
int xhelp= origin.x;
int yhelp= origin.y;
for (int i = 0; i < 20; i++) {
xhelp+= div_x;
line.drawLine(xhelp, 0, xhelp, y_panel);
}
xhelp= origin.x;
for (int i = 0; i < 20; i++) {
xhelp-= div_x;
line.drawLine(xhelp, 0, xhelp, y_panel);
}
for (int i = 0; i < 20; i++) {
yhelp-= div_y;
line.drawLine(0, yhelp,x_panel, yhelp);
}
yhelp= origin.y;
for (int i = 0; i < 20; i++) {
yhelp+= div_y;
line.drawLine(0, yhelp, x_panel, yhelp);
}
}
public void setDivisionDefault() {
div_x = 20;
div_y = 20;
}
#Override
public void mouseDragged(MouseEvent e) {
//Point temp_point = new Point(mouse_x,mouse_y);
Point coords = new Point(e.getX(), e.getY());
setOrigin(coords);
repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
}
}
Based on this example, the following program allows the user to drag the axes' intersection to an arbitrary point, origin, which starts at the center of the panel.
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/15576413/230513
* #see https://stackoverflow.com/a/5312702/230513
*/
public class MouseDragTest extends JPanel {
private static final String TITLE = "Drag me!";
private static final int W = 640;
private static final int H = 480;
private Point origin = new Point(W / 2, H / 2);
private Point mousePt;
public MouseDragTest() {
this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
mousePt = e.getPoint();
repaint();
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
int dx = e.getX() - mousePt.x;
int dy = e.getY() - mousePt.y;
origin.setLocation(origin.x + dx, origin.y + dy);
mousePt = e.getPoint();
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(0, origin.y, getWidth(), origin.y);
g.drawLine(origin.x, 0, origin.x, getHeight());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame(TITLE);
f.add(new MouseDragTest());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}