jframe won't repaint at constant rate - java

I am trying to learn Java, coming from a C/assembly embedded systems background. After a few weeks of learning, I thought it would be fun to try and make a game, but I am having some problems with a JPanel being repainted at an inconsistent rate.
My "game" GUI consists of a single JFrame which contains a JPanel. As you can see, the main thread for the JFrame sleeps for 30 milliseconds and then updates "game" logic and redraws the JFrame and JPanel. Each time the JPanel is redrawn, I check that it took about 30 milliseconds. As you would expect, it never takes more than about 32 milliseconds between frame redraws.
In spite of the fact that the JPanel is definitely repainted every 30 milliseconds or so, The animation in it can be very jerky. On Ubuntu 14.10, it is extremely obvious, even though the time between calls to my JPanel's repaint() are still never more than 32ms apart.
The most vexing thing is that if I uncomment the lines
//this.resize(300 + a, 300);
//a = (a == 1)?(0):1;
in SimFrame.java, everything is perfectly smooth, which implies that my computer has the capability to update the JPanel at the rate I want.
Is there some way I can force the JFrame to update at the rate I want it to without doing the absurd .resize call?
Main.java
package jdemo;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Main
{
public static void main(String[] args)
{
SimFrame s = new SimFrame();
Thread t = new Thread(s);
t.start();
}
}
Sim.java
package jdemo;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.Timer;
import javax.swing.JPanel;
import javax.swing.JFrame;
public class Sim extends JPanel implements KeyListener
{
/**
*
*/
private static final long serialVersionUID = 1L;
private int keys[] = new int[1024];
double x = 100;
double y = 100;
double dx = 0;
double dy = 0;
double theta = 0.0;
private int size = 0;
private int dsize = 1;
public Sim()
{
this.addKeyListener(this);
this.setFocusable(true);
}
long prevmillis;
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D drawing = (Graphics2D)g;
//clear the background.
drawing.setColor(Color.WHITE);
drawing.fillRect(0, 0, this.getWidth() - 1, this.getHeight() - 1);
//System.out.printf("dt = %d\n", System.currentTimeMillis() - prevmillis);
prevmillis = System.currentTimeMillis();
drawing.setColor(Color.BLACK);
drawing.drawRect(0, 0, size, size);
drawing.setColor(Color.BLACK);
int[] xpoints = {(int)(x + 10 * Math.cos(Math.toRadians(theta))),
(int)(x + 5 * Math.cos(Math.toRadians(theta - 150))),
(int)(x + 5 * Math.cos(Math.toRadians(theta + 150)))};
int[] ypoints = {(int)(y + 10 * Math.sin(Math.toRadians(theta))),
(int)(y + 5 * Math.sin(Math.toRadians(theta - 150))),
(int)(y + 5 * Math.sin(Math.toRadians(theta + 150)))};
drawing.drawPolygon(xpoints, ypoints, 3);
}
public void updateLogic()
{
if(keys[KeyEvent.VK_UP] == 1)
{
size++;
}
else if(keys[KeyEvent.VK_DOWN] == 1)
{
size--;
}
//update theta.
if(keys[KeyEvent.VK_LEFT] == 1)
{
theta += 5;
}
if(keys[KeyEvent.VK_RIGHT] == 1)
{
theta -= 5;
}
if(theta > 360.1)
{
theta -= 360;
}
if(theta < -0.1)
{
theta += 360;
}
//update acceleration
if(keys[KeyEvent.VK_SPACE] == 1)
{
dx += 0.08* Math.cos(Math.toRadians(theta));
dy += 0.08 * Math.sin(Math.toRadians(theta));
}
dx *= 0.99;
dy *= 0.99;
//update position
x = x + dx;
y = y + dy;
System.out.printf("%f, %f\n", dx, dy);
//update size
if(size > 150)
{
dsize = -1;
}
if(size < 10)
{
dsize = 1;
}
size += dsize;
}
#Override
public void keyPressed(KeyEvent arg0)
{
// TODO Auto-generated method stub
keys[arg0.getKeyCode()] = 1;
System.out.printf("%d\n", arg0.getKeyCode());
}
#Override
public void keyReleased(KeyEvent arg0)
{
// TODO Auto-generated method stub
keys[arg0.getKeyCode()] = 0;
}
#Override
public void keyTyped(KeyEvent arg0)
{
// TODO Auto-generated method stub
}
}
SimFrame.java
package jdemo;
import jdemo.Sim;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class SimFrame extends JFrame implements Runnable
{
private Sim s;
public SimFrame()
{
this.s = new Sim();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setContentPane(this.s);
this.pack();
this.setLocationRelativeTo(null);
this.setSize(200, 200);
}
#Override
public void run()
{
int a = 0;
this.setVisible(true);
while(true)
{
//repaint
s.updateLogic();
this.getContentPane().revalidate();
this.repaint();
//this.resize(300 + a, 300);
//a = (a == 1)?(0):1;
try
{
Thread.sleep(30);
}
catch(InterruptedException e)
{
System.out.printf("failed");
break;
}
}
}
}
Thank you very much.

Try this and if it works I'll transform it into a true answer (I just can't know if it will work on your system better than your current code):
public class SimFrame extends JFrame {
public SimFrame() {
setContentPane(new Sim());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private class Sim extends JPanel {
double x = 100;
double y = 100;
double dx = 0;
double dy = 0;
double theta = 0.0;
private int size = 0;
private int dsize = 1;
public Sim() {
addKeyListener(new Controller());
setFocusable(true);
setBackground(Color.WHITE);
new Timer(30, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
dx *= 0.99;
dy *= 0.99;
// update position
x = x + dx;
y = y + dy;
// update size
if (size > 150)
dsize = -1;
if (size < 10)
dsize = 1;
size += dsize;
// update theta.
if (theta > 360.1) {
theta -= 360;
}
if (theta < -0.1) {
theta += 360;
}
repaint();
}
}).start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
};
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D drawing = (Graphics2D) g;
drawing.setColor(Color.BLACK);
drawing.drawRect(0, 0, size, size);
drawing.setColor(Color.BLACK);
int[] xpoints = {(int) (x + 10 * Math.cos(Math.toRadians(theta))), (int) (x + 5 * Math.cos(Math.toRadians(theta - 150))),
(int) (x + 5 * Math.cos(Math.toRadians(theta + 150)))};
int[] ypoints = {(int) (y + 10 * Math.sin(Math.toRadians(theta))), (int) (y + 5 * Math.sin(Math.toRadians(theta - 150))),
(int) (y + 5 * Math.sin(Math.toRadians(theta + 150)))};
drawing.drawPolygon(xpoints, ypoints, 3);
}
private class Controller extends KeyAdapter {
#Override
public void keyPressed(KeyEvent evt) {
switch (evt.getKeyCode()) {
case KeyEvent.VK_UP:
size++;
break;
case KeyEvent.VK_DOWN:
size--;
break;
case KeyEvent.VK_LEFT:
theta += 5;
break;
case KeyEvent.VK_RIGHT:
theta -= 5;
break;
case KeyEvent.VK_SPACE:
dx += 0.08 * Math.cos(Math.toRadians(theta));
dy += 0.08 * Math.sin(Math.toRadians(theta));
break;
}
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new SimFrame();
}
});
}
}
And yes, I know there's the OS's delay on holding a key, we'll get to it if it works.
Edit:
Simpler animation:
public class CopyOfSimFrame extends JFrame {
public CopyOfSimFrame() {
setContentPane(new Sim());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private class Sim extends JPanel {
private int size = 0;
private int dsize = 1;
public Sim() {
setBackground(Color.WHITE);
new Timer(30, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// update size
if (size >= 150)
dsize = -1;
if (size <= 0)
dsize = 1;
size += dsize;
repaint();
}
}).start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(150, 150);
};
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.drawRect(0, 0, size, size);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new CopyOfSimFrame();
}
});
}
}

Related

Graphics for bouncing ball in multi-threaded program in Java

I am trying to adapt a program I have created for bouncing a ball in Java. I am very new so apologies if the solution is obvious. I have used the following code to create an array of bouncing balls, and then subsequently create two threads with the balls bouncing.
I am trying to achieve this without the array. So, that each thread has only 1 ball.
I feel like the answer is staring me in the face but I just cannot solve the issue. Any help would be greatly appreciated.
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Scanner;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class AClass implements Runnable {
private JFrame mainFrame;
private DrawPanel drawPanel;
// private java.util.List<Ball> balls;
private int windowWidth = 640;
private int windowHeight = 480;
private String windowLabel = "Multi-threaded ball application";
public void run() {
//balls = new ArrayList<>();
//Scanner sc = new Scanner(System.in);
//System.out.print("Enter the number of balls you would like to create:");
//int n = sc.nextInt();
//sc.close();
/* Generate balls */
//for (int i = 0; i < n; i++) {
Ball ball = new Ball(
/* Random positions from 0 to windowWidth or windowHeight */
(int) Math.floor(Math.random() * windowWidth),
(int) Math.floor(Math.random() * windowHeight),
/* Random size from 10 to 30 */
(int) Math.floor(Math.random() * 20) + 10,
/* Random RGB colors*/
new Color(
(int) Math.floor((Math.random() * 256)),
(int) Math.floor((Math.random() * 256)),
(int) Math.floor((Math.random() * 256))
),
/* Random velocities from -5 to 5 */
(int) Math.floor((Math.random() * 10) - 5),
(int) Math.floor((Math.random() * 10) - 5)
);
// balls.add(ball);
// }
/* Initialize program */
mainFrame = new JFrame();
drawPanel = new DrawPanel();
mainFrame.getContentPane().add(drawPanel);
mainFrame.setTitle(windowLabel);
mainFrame.setSize(windowWidth, windowHeight);
mainFrame.setVisible(true);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
while (true) {
//for (Ball b: balls) {
ball.update();
// }
/* Give Swing 10 milliseconds to see the update! */
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
mainFrame.repaint();
}
}
public class DrawPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
// for (Ball b: balls) {
ball.draw(graphics);
// }
}
}
class Ball {//ball class
private int posX, posY, size;
private Color color;
private int vx = 5;
private int vy = 5;
public Ball(int posX, int posY, int size, Color color, int vx, int vy) {
this.posX = posX;
this.posY = posY;
this.size = size;
this.color = color;
this.vx = vx;
this.vy = vy;
}
void update() {
if (posX > mainFrame.getWidth() || posX < 0) {
vx *= -1;
}
if (posY > mainFrame.getHeight() || posY < 0) {
vy *= -1;
}
if (posX > mainFrame.getWidth()) {
posX = mainFrame.getWidth();
}
if (posX < 0) {
posX = 0;
}
if (posY > mainFrame.getHeight()) {
posY = mainFrame.getHeight();
}
if (posY < 0) {
posY = 0;
}
this.posX += vx;
this.posY += vy;
}
void draw(Graphics g) {
g.setColor(color);
g.fillOval(posX, posY, size, size);
}
}
public static void main(String[] args) {
AClass ex = new AClass();
Thread t1= new Thread(ex);
Thread t2 = new Thread(ex);
t1.start();
t2.start();
//System.out.println("Hi");
}
}
First, Swing is not thread safe. You should not be updating the UI (or any state the UI relies on) from outside the context of the Event Dispatching Thread.
See Concurrency in Swing for more details.
I don't think your intention is the correct approach (trying to have each ball be it's own Thread), you're going to quickly end up with all sorts of issues trying to do collision detection, as the state of each ball is always changing independent of each other and it won't scale well. The array and a Swing Timer would be a more suitable solution.
This is the probably the closes I can get to what you want, the problem is, in order to paint it, you'd need a reference to the Ball, so I extended Ball from JPanel instead.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new SurfacePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface Surface {
public Dimension getSize();
public void repaint();
}
public class SurfacePane extends JPanel implements Surface {
public SurfacePane() {
setLayout(null);
for (int index = 0; index < 10; index++) {
Ball ball = new Ball(
/* Random positions from 0 to windowWidth or windowHeight */
(int) Math.floor(Math.random() * 400),
(int) Math.floor(Math.random() * 400),
/* Random size from 10 to 30 */
(int) Math.floor(Math.random() * 20) + 10,
/* Random RGB colors*/
new Color(
(int) Math.floor((Math.random() * 256)),
(int) Math.floor((Math.random() * 256)),
(int) Math.floor((Math.random() * 256))
),
/* Random velocities from -5 to 5 */
(int) Math.floor((Math.random() * 10) - 5),
(int) Math.floor((Math.random() * 10) - 5),
this
);
add(ball);
ball.start();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
public class Ball extends JPanel {
private int posX, posY, size;
private Color color;
private int vx = 5;
private int vy = 5;
private Surface surface;
private Timer timer;
public Ball(int posX, int posY, int size, Color color, int vx, int vy, Surface surface) {
this.posX = posX;
this.posY = posY;
this.size = size;
this.color = color;
this.vx = vx;
this.vy = vy;
this.surface = surface;
setBackground(color);
setSize(size, size);
setOpaque(false);
}
public void start() {
if (timer != null) {
timer.stop();
}
timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
update();
surface.repaint();
}
});
timer.start();
}
public void stop() {
if (timer == null) {
return;
}
timer.stop();
}
protected void update() {
int width = surface.getSize().width;
int height = surface.getSize().height;
if (posX > width || posX < 0) {
vx *= -1;
}
if (posY > height || posY < 0) {
vy *= -1;
}
if (posX > width) {
posX = width;
}
if (posX < 0) {
posX = 0;
}
if (posY > height) {
posY = height;
}
if (posY < 0) {
posY = 0;
}
this.posX += vx;
this.posY += vy;
setLocation(posX, posY);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
g.setColor(color);
g.fillOval(0, 0, size, size);
}
}
}
The problem with this approach (and even the Threaded approach) is, it's not going to scale well. For example, during my experimentation, I only got to about 5, 000 balls before I started have responsiveness issues (resizing the window lagged, alot), compared to roughly 20, 000 balls using an ArrayList (and a single Timer) - I'll be honest, the frame rate was terrible, but the UI remained relatively responsive - I could resize the window without lag
Single Timer, array based example...
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.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new SurfacePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface Surface {
public Dimension getSize();
public void repaint();
}
public class SurfacePane extends JPanel implements Surface {
private List<Ball> balls = new ArrayList<>(32);
public SurfacePane() {
for (int index = 0; index < 20_000; index++) {
Ball ball = new Ball(
/* Random positions from 0 to windowWidth or windowHeight */
(int) Math.floor(Math.random() * 400),
(int) Math.floor(Math.random() * 400),
/* Random size from 10 to 30 */
(int) Math.floor(Math.random() * 20) + 10,
/* Random RGB colors*/
new Color(
(int) Math.floor((Math.random() * 256)),
(int) Math.floor((Math.random() * 256)),
(int) Math.floor((Math.random() * 256))
),
/* Random velocities from -5 to 5 */
(int) Math.floor((Math.random() * 10) - 5),
(int) Math.floor((Math.random() * 10) - 5),
this
);
balls.add(ball);
}
Timer timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Ball ball : balls) {
ball.update();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Ball ball : balls) {
ball.paint(g2d);
}
g2d.dispose();
}
}
public class Ball {
private int posX, posY, size;
private Color color;
private int vx = 5;
private int vy = 5;
private Surface surface;
private Timer timer;
public Ball(int posX, int posY, int size, Color color, int vx, int vy, Surface surface) {
this.posX = posX;
this.posY = posY;
this.size = size;
this.color = color;
this.vx = vx;
this.vy = vy;
this.surface = surface;
}
protected void update() {
int width = surface.getSize().width;
int height = surface.getSize().height;
if (posX > width || posX < 0) {
vx *= -1;
}
if (posY > height || posY < 0) {
vy *= -1;
}
if (posX > width) {
posX = width;
}
if (posX < 0) {
posX = 0;
}
if (posY > height) {
posY = height;
}
if (posY < 0) {
posY = 0;
}
this.posX += vx;
this.posY += vy;
}
public void paint(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillOval(posX, posY, size, size);
}
}
}

Zooming using scrollwheel in java

I am trying to make a Mandelbrot program that allows zooming, but the zoom doesn't seem to be working, and i don't see what is wrong with the way i have implemented the zoom.I am using eclipse and the program doesn't return any errors. Here is my code:
import java.awt.Graphics;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class Mandelbrot extends JFrame {
private final int MAX_ITER = 570;
private static double ZOOM = 200;
private BufferedImage I;
private double zx, zy, cX, cY, tmp;
public static boolean zooming = false;
public Mandelbrot()
{
super("MandelbrotSet");
setBounds(100, 100, 800, 600);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
I = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
zx = zy = 0;
cX = (x - 400) / ZOOM;
cY = (y - 300) / ZOOM;
int iter = MAX_ITER;
while (zx * zx + zy * zy < 4 && iter > 0) {
tmp = zx * zx - zy * zy + cX;
zy = 2.0 * zx * zy + cY;
zx = tmp;
iter--;
}
I.setRGB(x, y, iter | (iter << 8));
}
}
setVisible(true);
while(1>0)
{
if(zooming)
{
revalidate();
repaint();
System.out.println("zooming");
zooming = false;
}
} }
#Override
public void paint(Graphics g) {
g.drawImage(I, 0, 0, this);
}
public static void main(String[] args) {
new Mandelbrot().addMouseWheelListener(new MouseWheelListener(){ public void mouseWheelMoved(MouseWheelEvent
e) {
if (e.getWheelRotation() < 0) {
ZOOM=ZOOM+100;
zooming = true;
} else
{
ZOOM=ZOOM-100;
zooming = true;
} } });
} }
Your constructor contains an endless loop. It therefore never returns and your MouseWheelListener is never added to the frame.
You calculate the BufferedImage exactly once (before the endless loop), so even if you would attach the MouseWheelListener before the loop it would have no effect.
I would move the calculation of the picture into its own method, call this method once from the constructor and once from your MouseWheelListener and remove the endless loop from the constructor.
public Mandelbrot()
{
super("MandelbrotSet");
//...
I = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
calculatePicture();
setVisible(true);
}
public void calculatePicture() {
for (int y = 0; y < getHeight(); y++) {
//...
}
repaint();
}
public static void main(String[] args) {
new Mandelbrot().addMouseWheelListener(new MouseWheelListener(){
public void mouseWheelMoved(MouseWheelEvent
e) {
//...
calculatePicture();
}
});
}

java GUI to assign array value

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

How to move two circles together in a JFrame from two different classes

I am trying to display the two circles moving together on a single frame
through two different classes. But only one is shown moving at a time,even though value of "x" is changing continuously in class child1, paintComponent() is only taking the value of "x1" and showing the circle in moving from class child2.
If two separate frames are assigned to both the classes they works perfectly fine.
Here is my code
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test13
{
public static void main(String[] args)
{
child1 c1 = new child1();
child2 c2 = new child2();
JFrame f1 = new JFrame("Frame Test1");
f1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//passing a single JFrame to both methods as parameters
c1.cfunc1(f1);
c2.cfunc2(f1); // but this line always hides the upper one
f1.setSize(1000,700);
f1.setVisible(true);
}
}
class child1 extends JPanel implements ActionListener
{
int x,y;
int delay1;
Timer timer1; //timer for 1st class constructor
child1()
{
x=1;
y=100;
timer1 = new Timer(50,this);
}
#Override
public void actionPerformed(ActionEvent e)
{
if(x <= 500)
{
x += 1;
y = 100;
repaint();
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
void cfunc1(JFrame f1)//passing JFrame as parameter
{
child1 c1 = new child1();
f1.add(c1);
c1.timer1.start(); //timer started at the end of class1
}
}
class child2 extends JPanel implements ActionListener
{
int x1,y1;
int delay2;
Timer timer2;
child2()
{
x1 = 500;
y1 = 100;
timer2 = new Timer(50,this);//timer for 2nd class constructor
}
#Override
public void actionPerformed(ActionEvent e)
{
if(x1 <= 500)
{
x1 -= 1;
y1 = 100;
repaint();
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.fillOval(x1, y1, 10, 10);
}
void cfunc2(JFrame f1)//passing JFrame as parameter
{
child2 c2 = new child2();
f1.add(c2);
c2.timer2.start();//timer started for 2nd class
}
}
When two components are added to a single constraint of a BorderLayout (the default layout for a JFrame), only one is displayed.
The typical way to do this is not to design the custom painting in a JComponent (like JPanel) but instead to have them as simple classes which can paint(Graphics) or draw(Graphics) when requested to do so.
Then extend a single JComponent that iterates a list of the drawable components and paints each one.
Don't create individual classes (child1, child2) for each object. What if you want 50 objects? Instead you create a class that accepts parameters that allows you to customize the object.
This example shows how you might:
create a generic Ball class.
uses a Swing Timer for animation of the balls
create a panel that does custom painting by invoking the draw(...) method of each Ball.
Here is the code:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class BallAnimation4
{
private static void createAndShowUI()
{
BallPanel panel = new BallPanel();
JFrame frame = new JFrame("BallAnimation4");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setSize(800, 600);
frame.setLocationRelativeTo( null );
//frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setVisible( true );
panel.addBalls(5);
panel.startAnimation();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
class BallPanel extends JPanel implements ActionListener
{
private ArrayList<Ball> balls = new ArrayList<Ball>();
public BallPanel()
{
setLayout( null );
setBackground( Color.BLACK );
}
public void addBalls(int ballCount)
{
Random random = new Random();
for (int i = 0; i < ballCount; i++)
{
Ball ball = new Ball();
ball.setRandomColor(true);
ball.setLocation(random.nextInt(getWidth()), random.nextInt(getHeight()));
ball.setMoveRate(32, 32, 1, 1, true);
// ball.setMoveRate(16, 16, 1, 1, true);
ball.setSize(32, 32);
balls.add( ball );
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Ball ball: balls)
{
ball.draw(g);
}
}
public void startAnimation()
{
Timer timer = new Timer(75, this);
timer.start();
}
public void actionPerformed(ActionEvent e)
{
move();
repaint();
}
private void move()
{
for (Ball ball : balls)
{
ball.move(this);
}
}
class Ball
{
public Color color = Color.BLACK;
public int x = 0;
public int y = 0;
public int width = 1;
public int height = 1;
private int moveX = 1;
private int moveY = 1;
private int directionX = 1;
private int directionY = 1;
private int xScale = moveX;
private int yScale = moveY;
private boolean randomMove = false;
private boolean randomColor = false;
private Random myRand = null;
public Ball()
{
myRand = new Random();
setRandomColor(randomColor);
}
public void move(JPanel parent)
{
int iRight = parent.getSize().width;
int iBottom = parent.getSize().height;
x += 5 + (xScale * directionX);
y += 5 + (yScale * directionY);
if (x <= 0)
{
x = 0;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (x >= iRight - width)
{
x = iRight - width;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (y <= 0)
{
y = 0;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
if (y >= iBottom - height)
{
y = iBottom - height;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
}
public void draw(Graphics g)
{
g.setColor(color);
g.fillOval(x, y, width, height);
}
public void setColor(Color c)
{
color = c;
}
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
}
public void setMoveRate(int xMove, int yMove, int xDir, int yDir, boolean randMove)
{
this.moveX = xMove;
this.moveY = yMove;
directionX = xDir;
directionY = yDir;
randomMove = randMove;
}
public void setRandomColor(boolean randomColor)
{
this.randomColor = randomColor;
switch (myRand.nextInt(3))
{
case 0: color = Color.BLUE;
break;
case 1: color = Color.GREEN;
break;
case 2: color = Color.RED;
break;
default: color = Color.BLACK;
break;
}
}
public void setSize(int width, int height)
{
this.width = width;
this.height = height;
}
}
}
Normally you wouldn't make the classes static.

Java Swing: JPanel will not paint

Right now, I am trying to make a Hex Grid GUI that highlights whatever hex that the mouse is hovering over. However, the JPanel which is supposed to display the hexagons are not showing up.
I seriously have no idea what is happening between repaint() and paintComponents() in this case. I tried to look up solutions, which involve setting the JPanel into a visible hierachy, which I made sure I did, and it still is not working.
Code is slightly long
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
public class HexGridTester implements ActionListener, MouseListener, MouseMotionListener
{
private JFrame frame;
private TestPanel panel;
private double radius;
private double width;
private double side;
private double height;
private Location mouseover;
private class TestPanel extends JPanel
{
#Override
public void paintComponents(Graphics g)
{
System.out.println("CALLED");
super.paintComponents(g);
int[] xpoints = new int[6];
int[] ypoints = new int[6];
for(int c = 0; c < 15; c++)
{
for(int r = 0; r < 21; r++)
{
int dx = c * (int) width;
int dy = r * (int) height;
if(new Location(c, r).equals(mouseover))
{
g.setColor(Color.blue);
}
else
{
g.setColor(Color.black);
}
if(c % 2 != 0)
{
dy += height / 2;
}
for(int i = 0; i < 6; i++)
{
xpoints[i] = (int) ((int) 10 * Math.cos(i * 2 * Math.PI / 6) + dx);
ypoints[i] = (int) ((int) 10 * Math.sin(i * 2 * Math.PI / 6) + dy);
}
Polygon hex = new Polygon (xpoints, ypoints, 6);
g.fillPolygon(hex);
System.out.print("Drawing " + r + "," + c);
}
}
}
}
public HexGridTester(int r)
{
radius = r;
side = radius * 3/2;
width = radius * 2;
height = Math.sqrt(3) * radius;
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(0, 1));
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
panel = new TestPanel();
panel.setLayout(new GridLayout(0,1));
panel.addMouseListener(this);
panel.addMouseMotionListener(this);
frame.add(panel);
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
panel.repaint();
}
#Override
public void actionPerformed(ActionEvent ae)
{
}
#Override
public void mouseClicked(MouseEvent me)
{
}
#Override
public void mousePressed(MouseEvent me)
{
}
#Override
public void mouseReleased(MouseEvent me)
{
}
#Override
public void mouseEntered(MouseEvent me)
{
}
#Override
public void mouseExited(MouseEvent me)
{
}
#Override
public void mouseDragged(MouseEvent me)
{
}
#Override
public void mouseMoved(MouseEvent me)
{
//System.out.println(me.getPoint().x + " " + me.getPoint().y + " " + mouseover);
mouseover = convert(me.getPoint().x, me.getPoint().y);
panel.repaint();
}
private Location convert(double x, double y)
{
double xt;
double yt;
double r;
double c;
double rt;
double ct;
double dr;
ct = x / side;
if(ct % 2 == 0)
{
rt = y / height;
}
else
{
rt = (y - ( height) / 2) / (height);
}
xt = x - ct * side;
yt = y - rt * height;
if(yt > (height /2))
{
dr = 1;
}
else
{
dr = 0;
}
if(xt > (radius * Math.abs(.5 - yt/height)))
{
c = ct;
r = rt;
}
else
{
c = ct - 1;
r = rt - c%2 + dr;
}
return new Location((int) c, (int) r);
}
private static void runTestWindow()
{
HexGridTester tester = new HexGridTester(25);
}
public static void main(String[] args)
{
Runnable run = new Runnable()
{
#Override
public void run()
{
runTestWindow();
}
};
javax.swing.SwingUtilities.invokeLater(run);
}
}
#Override
public void paintComponents(Graphics g)
{
System.out.println("CALLED");
super.paintComponents(g);
Should be:
#Override
public void paintComponent(Graphics g)
{
System.out.println("CALLED");
super.paintComponent(g);
Respect the paint chain. Don't override any method but paintComponent(Graphics) for any JComponent.

Categories

Resources