Writing a JPanel subclass that will obey GridBagLayout? - java

I built a class that extends JPanel, and I have been setting a size on it every time. But now I need multiple instances of that JPanel sub-class to exist on another panel. I am using GridBagLayout, but my components disappear when I resize the Frame or take away the setSize(800,100) from the JPanel subclass. How do I built components that will obey these layout managers?
I want my custom component to be able to fit to the size that the layout manager asks for.
Here is my custom component
public class GridPanel extends JPanel implements MouseMotionListener, MouseListener{
private Rectangle offdutyRect, sbRect, driveRect, onRect;
private int delta = 60;
private int[][] gridArray;
int draggedStartX = -1;
int draggedStartY = -1;
private int dutyStatusSpacing = 60;
private int totalSpacing = 80;
public GridPanel(){
super();
this.setSize(new Dimension(800, 100));//without this it doesn't display
this.addMouseMotionListener((MouseMotionListener) this);
this.addMouseListener(this);
int gridArrayColumns = 24*60/delta;
gridArray = new int[4][gridArrayColumns];
int r = 0;
int rHeight = this.getHeight()/4;
offdutyRect = new Rectangle(this.getX() + dutyStatusSpacing, this.getY() + r*rHeight, this.getWidth() - totalSpacing, rHeight);
r++;
sbRect = new Rectangle(this.getX() + dutyStatusSpacing, this.getY() + r*rHeight, this.getWidth() - totalSpacing, rHeight);
r++;
driveRect = new Rectangle(this.getX() + dutyStatusSpacing, this.getY() + r*rHeight, this.getWidth() - totalSpacing, rHeight);
r++;
onRect = new Rectangle(this.getX() + dutyStatusSpacing, this.getY() + r*rHeight, this.getWidth() - totalSpacing, rHeight);
Rectangle rect = null;
for(r = 0; r < gridArray.length; r++){
if(r == 0){
rect = offdutyRect;
}else if(r == 1){
rect = sbRect;
}else if(r == 2){
rect = driveRect;
}else if(r == 3){
rect = onRect;
}
//I haven't actually derived any of these things, just my best guesses.
int len = gridArray[r].length;
int width = (int) (rect.getWidth()/len);
rect.setSize((int)(width*len), (int) rect.getHeight());
}
}
public void paintComponent(Graphics g){
g.clearRect(this.getX(), this.getY(), this.getWidth(), this.getHeight());
//the center black bar for duty status "placeholders"
g.setColor(Color.black);
g.drawRect(getX(), getY(), getWidth() - 1, getHeight() - 1);
g.drawRect((int)offdutyRect.getX(), (int)offdutyRect.getY() + (int)offdutyRect.getHeight()/2, (int)offdutyRect.getWidth(), 1);
g.drawRect((int)sbRect.getX(), (int)sbRect.getY() + (int)sbRect.getHeight()/2, (int)sbRect.getWidth(), 1);
g.drawRect((int)driveRect.getX(), (int)driveRect.getY() + (int)driveRect.getHeight()/2, (int)driveRect.getWidth(), 1);
g.drawRect((int)onRect.getX(), (int)onRect.getY() + (int)onRect.getHeight()/2, (int)onRect.getWidth(), 1);
g.setColor(Color.pink);
g.drawRect((int)offdutyRect.getX(), (int)offdutyRect.getY(), (int)offdutyRect.getWidth(), (int)offdutyRect.getHeight());
g.drawRect((int)sbRect.getX(), (int)sbRect.getY(), (int)sbRect.getWidth(), (int)sbRect.getHeight());
g.drawRect((int)driveRect.getX(), (int)driveRect.getY(), (int)driveRect.getWidth(), (int)driveRect.getHeight());
g.drawRect((int)onRect.getX(), (int)onRect.getY(), (int)onRect.getWidth(), (int)onRect.getHeight());
//draw the array
g.setColor(Color.green);
Rectangle rect = null;
for(int r = 0; r < gridArray.length; r++){
if(r == 0){
rect = offdutyRect;
}else if(r == 1){
rect = sbRect;
}else if(r == 2){
rect = driveRect;
}else if(r == 3){
rect = onRect;
}
//I haven't actually derived any of these things, just my best guesses.
int len = gridArray[r].length;
int width = (int) (rect.getWidth()/len);
int height = (int) rect.getHeight() - 2;
for(int c = 0; c < gridArray[r].length; c++){
if(gridArray[r][c] == 1){
int x = (int) (rect.getX() + width*c);
int y = (int) rect.getY() + 2;
g.fillRect(x, y, width, height);
}
}
}
}
And here is how I am adding them to the container using gridbag
public void actionPerformed(ActionEvent e) {
System.out.println("action performed" + panels.size());
removeAll();
repaint();
panels.add(new GridPanel());
int r = 0;
GridBagConstraints c = new GridBagConstraints();
for(int i = 0; i < panels.size(); i++){
JLabel label = new JLabel("day " + i);
c = new GridBagConstraints();
c.gridx = 0;
c.gridy = r;
this.add(label, c);
r++;
c = new GridBagConstraints();
c.gridx = 0;
c.gridy = r;
c.fill = GridBagConstraints.HORIZONTAL;
this.add(panels.get(i), c);
r++;
}
c = new GridBagConstraints();
c.gridx = 0;
c.gridy = r;
c.fill = GridBagConstraints.HORIZONTAL;
BufferedImage img = null;
try {
img = ImageIO.read(this.getClass().getResource("/res/add.png"));
} catch (IOException ex) { }
JButton addButton = new JButton();
addButton.setIcon(new ImageIcon(img));
addButton.addActionListener(this);
this.add(addButton, c);
}
When I do this "action performed" that is supposed to add it, my custom component shows up in the top left corner, but I have to resize the window to get all the components to show.

Many layout managers make use of the values returned by getPreferredSize, getMinimumSize and getMaximumSize.
GridBagLayout is one of those which will, generally, where possible, try and honor all three.
In your custom component, override (at least), the getPreferredSize method and return, to the best of you knowledge, the size of the component you need.
If the component is using a layout manager with other components in it, you should always let the layout manager make this determination (which is the default behavior)
Updated
Don't forget, when adding or removing components, you may be required to call revalidate to force the container hierarchy to update it's layout(s)

Related

Using Bresenham’s Circle Algorithm to generate an Ellipse in Java

I am trying to create a GUI based program which uses a grid and Bresenham’s Circle Algorithm to draw an ellipse. However, I am having two problems. One is that I am unable to get the panel_grid to refresh such that the new value of r is passed to the GridComponent and the ellipse is redrawn with the new radius. The second issue is that I managed to get the code to generate a circle, but I am unable to figure out how I would modify it to generate an ellipse instead. Can someone explain to me step by step how I would go about solving both these problems? Thanks in advance.
public class GUI extends JFrame {
private int r = 100;
public GUI(){
JFrame frame = new JFrame("Bresenham’s Ellipse Algorithm");
frame.setIconImage(Toolkit.getDefaultToolkit().getImage(GUI.class.getResource("/com/sun/java/swing/plaf/windows/icons/TreeLeaf.gif")));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(600, 600));
frame.setMinimumSize(new Dimension(440, 400));
JPanel panel = new JPanel();
JLabel label = new JLabel("Input the Dimensions of the Ellipse");
label.setFont(new Font("Sinhala Sangam MN", Font.PLAIN, 16));
JPanel panel_inst = new JPanel();
panel_inst.setPreferredSize(new Dimension(550, 30));
panel_inst.add(label);
panel.add(panel_inst);
JPanel panel_1 = new JPanel();
JLabel w = new JLabel("Radius");
JTextField width = new JTextField(10);
JPanel panel_grid = new JPanel();
JButton draw = new JButton("Draw");
draw.setPreferredSize(new Dimension(80, 25));
draw.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
r = Integer.parseInt(width.getText());
panel_grid.revalidate();
panel_grid.repaint();
}
});
panel_1.add(w);
panel_1.add(width);
panel_1.add(draw);
panel.add(panel_1);
panel_grid.setBackground(Color.white);
panel_grid.setPreferredSize(new Dimension(550, 450));
GridComponent grid = new GridComponent(r);
panel_grid.add(grid);
panel.add(panel_grid);
frame.pack();
frame.setVisible(true);
frame.setContentPane(panel);
}
public class GridComponent extends JComponent{
int r;
public GridComponent(int r){
this.r = r;
setPreferredSize(new Dimension(550, 450));
}
public void paintComponent(Graphics g){
//Draw Grid
Graphics2D g2 = (Graphics2D) g;
int width = 54;
int height = 44;
int size = 10;
for( int i = 0; i < width; i ++){
for( int j = 0; j < height; j++){
Rectangle grid = new Rectangle( 0 + i * size, 0 + j * size, size, size);
g2.draw(grid);
}
}
g.setColor(Color.black);
g2.fillOval((int)Math.floor((width*size)/2)-3,(int)Math.floor((height*size)/2-6)+3, 5, 5);
g2.drawLine(0, (int)Math.floor((height*size)/2)-1, (int)Math.floor((width*size)), (int)Math.floor((height*size)/2)-1);
g2.drawLine((int)Math.floor((width*size/2))-1, 0, (int)Math.floor((width*size)/2)-1, (int)Math.floor((height*size)));
//Draw Ellipse using algo
int a = (int)Math.floor((height*size/2));//(int)Math.floor((width*15/2));
int b = (int)Math.floor((width*size/2));// (int)Math.floor((height*15/2));
int x = r, y = 0, d = 3 - 2*r;
g2.setColor(Color.red);
// x is initially r, x will be same as y at 45(degree) angle
while(y <= x) {
// Eight way symmetry of circle
g2.drawString("+", x + b, y + a);
g2.drawString(".", y + b, x + a);
g2.drawString("+", (-1)*y + b, x + a);
g2.drawString(".", (-1)*x + b, y + a);
g2.drawString("+", (-1)*x + b, (-1)*y + a);
g2.drawString(".", (-1)*y + b, (-1)*x + a);
g2.drawString("+", y + b, (-1)*x + a);
g2.drawString(".", x + b, (-1)*y + a);
if(d < 0) // move Up = d + Up + 2
d = d + 4*y + 6;
else { // move Left = d + Left + 2
d = d - 4*(x - y) + 10;
//Since we've started at the right hand side of the circle
x = x - 1;
}
// Since we have started at top of the circle
y = y + 1;
}
}
private int side;
}
}
Start by...
Adding a setter for the radius to your GridComponent class
public void setR(int r) {
this.r = r;
repaint();
}
Then...
Change your ActionListener to interact with your GridComponent and call it's setter method
GridComponent grid = new GridComponent(r);
draw.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
r = Integer.parseInt(width.getText());
grid.setR(r);
}
});
Which will answer the first part of your question.
The second part is more difficult and I'm not going to attempt to generate code, other then to say, you need two values, you need a width and height radius
But, Bresenham's circle/ellipse drawing algorithm might provide an insight

Button To Reset The View Of JFrame

I have to write a program that will generate 20 random circles with random radius lengths. If any of these circles intersect with another, the circle must be blue, and if it does not intersect, the color is red. I must also place a button on the JFrame. If this button is pressed, it needs to clear out the JFrame, and generate a new set of 20 circles following the same color rules. I am extremely new to Java Swing and am really stuck. I have everything working except the button. I cannot get a new set of circles to generate. Any help would be greatly appreciated. Thank You.
import java.awt.Graphics;
import javax.swing.JPanel;
import java.util.Random;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class IntersectingCircles extends JPanel
{
private int[] xAxis = new int [20]; // array to hold x axis points
private int[] yAxis = new int [20]; // array to hold y axis points
private int[] radius = new int [20]; // array to hold radius length
public static void main (String[] args)
{
JFrame frame = new JFrame("Random Circles");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add (new IntersectingCircles());
frame.pack();
frame.setVisible(true);
}
public IntersectingCircles()
{
setPreferredSize(new Dimension(1000, 800)); // set window size
Random random = new Random();
// Create coordinates for circles
for (int i = 0; i < 20; i++)
{
xAxis[i] = random.nextInt(700) + 100;
yAxis[i] = random.nextInt(500) + 100;
radius[i] = random.nextInt(75) + 10;
}
}
public void paintComponent(Graphics g)
{
// Add button to run again
JButton btnAgain = new JButton("Run Again");
btnAgain.setBounds(850, 10, 100, 30);
add(btnAgain);
btnAgain.addActionListener(new ButtonClickListener());
// Determine if circles intersect, create circles, color circles
for (int i = 0; i < 20; i++)
{
int color = 0;
for (int h = 0; h < 20; h++)
{
if(i != h)
{
double x1 = 0, x2 = 0, y1 = 0, y2 = 0, d = 0;
x1 = (xAxis[i] + radius[i]);
y1 = (yAxis[i] + radius[i]);
x2 = (xAxis[h] + radius[h]);
y2 = (yAxis[h] + radius[h]);
d = (Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1)*(y2 - y1))));
if (d > radius[i] + radius[h] || d < (Math.abs(radius[i] - radius[h])))
{
color = 0;
}
else
{
color = 1;
break;
}
}
}
if (color == 0)
{
g.setColor(Color.RED);
g.drawOval(xAxis[i], yAxis[i], radius[i] * 2, radius[i] * 2);
}
else
{
g.setColor(Color.BLUE);
g.drawOval(xAxis[i], yAxis[i], radius[i] * 2, radius[i] * 2);
}
}
}
private class ButtonClickListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
String action = e.getActionCommand();
if(action.equals("Run Again"))
{
new IntersectingCircles();
}
}
}
}
Suggestions:
Give your class a method that creates the random circles and calls repaint()
This method should create the circles and add them into an ArrayList.
Consider using Ellipse2D to represent your circles, and so you'd have an ArrayList<Ellipse2D>.
Call this method in your class constructor.
Call it again in the button's ActionListener.
Never add button's or change the state of your class from within your paintComponent method. This method is for drawing the circles and drawing them only and nothing more. Your way you will be creating the button each time the paintComponent method is called, so you could be potentially needlessly creating many JButtons
and needlessly slowing down your time-critical painting method.
Instead add the button in the constructor.
Be sure to call super.paintComponent(g) in your paintComponent method as its first call. This will clear the old circles when need be.
Also in paintComponent, iterate through the ArrayList of circles, drawing each one.
After some searching on this website, i found what i needed. Everything seems to work now. Thanks.
import java.awt.Graphics;
import javax.swing.JPanel;
import java.util.Random;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class IntersectingCircles extends JPanel
{
private int[] xAxis = new int [20]; // array to hold x axis points
private int[] yAxis = new int [20]; // array to hold y axis points
private int[] radius = new int [20]; // array to hold radius length
public static void main (String[] args)
{
JFrame frame = new JFrame("Random Circles");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(1000, 800));
ActionListener runAgain = new ActionListener()
{
#Override
public void actionPerformed(ActionEvent c)
{
frame.getContentPane().add(new IntersectingCircles());
frame.pack();
}
};
JButton btnAgain = new JButton("Run Again");
btnAgain.setBounds(850, 10, 100, 30);
btnAgain.addActionListener(runAgain);
frame.add(btnAgain);
frame.getContentPane().add (new IntersectingCircles());
frame.pack();
frame.setVisible(true);
}
public IntersectingCircles()
{
Random random = new Random();
// Create coordinates for circles
for (int i = 0; i < 20; i++)
{
xAxis[i] = random.nextInt(700) + 100;
yAxis[i] = random.nextInt(500) + 100;
radius[i] = random.nextInt(75) + 10;
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Determine if circles intersect, create circles, color circles
for (int i = 0; i < 20; i++)
{
int color = 0;
for (int h = 0; h < 20; h++)
{
if(i != h)
{
double x1 = 0, x2 = 0, y1 = 0, y2 = 0, d = 0;
x1 = (xAxis[i] + radius[i]);
y1 = (yAxis[i] + radius[i]);
x2 = (xAxis[h] + radius[h]);
y2 = (yAxis[h] + radius[h]);
d = (Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1)*(y2 - y1))));
if (d > radius[i] + radius[h] || d < (Math.abs(radius[i] - radius[h])))
{
color = 0;
}
else
{
color = 1;
break;
}
}
}
if (color == 0)
{
g.setColor(Color.RED);
g.drawOval(xAxis[i], yAxis[i], radius[i] * 2, radius[i] * 2);
}
else
{
g.setColor(Color.BLUE);
g.drawOval(xAxis[i], yAxis[i], radius[i] * 2, radius[i] * 2);
}
}
}
}
private class ButtonClickListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
repaint();
}
}
Maybe you can have a try...

Swing timer for display grid

I'm getting the feeling that I have no idea how swing Timer works. I'm still new to the Java GUI API, and the program I'm writing is just to test myself and help me familiarize myself more with its inner workings.
What it's supposed to do is wait until the user presses the Start button, then iterate the display (a grid of white or black JPanels), which displays a simple cellular automata simulation at a 1 second interval, and pauses when the Pause button is pressed (same as the Start button, but changes name). Each cell in the grid is supposed to start with a random color (white/black). What it's instead doing is to pause for a half second or so, then "run" for another half second, then pause, then run, so on and so forth.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CA_Driver extends JFrame{
private JPanel gridPanel, buttonPanel;
private JButton start_pause, pause;
private static Timer timer;
private Color black = Color.black;
private Color white = Color.white;
static Color[][] currentGrid, newGrid;
static Cell[][] cellGrid;
static boolean run, stop;
static int height = 20, width = 30, state;
public CA_Driver(){
stop = false;
run = false;
currentGrid = new Color[height][width];
newGrid = new Color[height][width];
cellGrid = new Cell[height][width];
//Initialize grid values
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int z = (int) (Math.random() * 2);
if (z == 0)
currentGrid[x][y] = newGrid[x][y] = white;
else currentGrid[x][y] = newGrid[x][y] = black;
}
//Create grid panel
gridPanel = new JPanel();
gridPanel.setLayout(new GridLayout(height,width));
//Populate grid
for (int x = 0; x < newGrid.length; x++)
for (int y = 0; y < newGrid[x].length; y++){
cellGrid[x][y] = new Cell(x,y);
cellGrid[x][y].setBackground(newGrid[x][y]);
int z = (int) Math.random();
if (z == 0) cellGrid[x][y].setBackground(black);
else cellGrid[x][y].setBackground(currentGrid[x][y]);
gridPanel.add(cellGrid[x][y]);
}
//Create buttons
state = 0;
start_pause = new JButton();
start_pause.setText("Start");
start_pause.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
if (state == 0) {
start_pause.setText("Pause");
run = true;
timer.start();
state += 1;
}
else {
start_pause.setText("Start");
run = false;
timer.stop();
state -= 1;
}
}
});
buttonPanel = new JPanel(new BorderLayout());
buttonPanel.add(start_pause, BorderLayout.NORTH);
// buttonPanel.add(pause, BorderLayout.EAST);
//Initialize and display frame
this.add(gridPanel, BorderLayout.NORTH);
this.add(buttonPanel, BorderLayout.SOUTH);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
//this.setSize(500, 500);
pack();
this.setVisible(true);
//Initialize timer
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int x = 0; x < cellGrid.length; x++)
for (int y = 0; y < cellGrid[x].length; y++){
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
//Display processing for next frame
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int b = checkNeighbors(y,x);
if (b > 4 || b < 2)
newGrid[x][y] = black;
else newGrid[x][y] = white;
}
if(!run) timer.stop();
}
});
}
public static void main(String[] args) {
new CA_Driver();
}
private int checkNeighbors(int w, int h){
int b = 0;
//Top Left
if((w != 0) && (h != 0) && (currentGrid[h - 1][w - 1] == black))
b++;
//Top Middle
if((h != 0) && (currentGrid[h - 1][w] == black))
b++;
//Top Right
if((w != width - 1) && (h != 0) && (currentGrid[h - 1][w + 1] == black))
b++;
//Middle Left
if((w != 0) && (currentGrid[h][w - 1] == black))
b++;
//Middle Right
if((w != width - 1) && (currentGrid[h][w + 1] == black))
b++;
//Bottom left
if((w != 0) && (h != height - 1) && (currentGrid[h + 1][w - 1] == black))
b++;
//Bottom Middle
if((h != height - 1) && (currentGrid[h + 1][w] == black))
b++;
//Bottom Right
if((w != width - 1) && (h != height - 1) && (currentGrid[h + 1][w + 1] == black))
b++;
return b;
}
private class Cell extends JPanel{
private Color c;
private int posx, posy;
public Cell(int x, int y){
posx = x;
posy = y;
}
public Point getLocation(){
return new Point(posx, posy);
}
public void setColor(){
c = newGrid[posx][posy];
setBackground(c);
}
public Dimension getPreferredSize(){
return new Dimension(10,10);
}
}
}
This is the timer section:
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int x = 0; x < cellGrid.length; x++)
for (int y = 0; y < cellGrid[x].length; y++){
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
//Display processing for next frame
for (int x = 0; x < currentGrid.length; x++)
for (int y = 0; y < currentGrid[x].length; y++){
int b = checkNeighbors(y,x);
if (b > 4 || b < 2)
newGrid[x][y] = black;
else newGrid[x][y] = white;
}
if(!run) timer.stop();
}
});
I'm planning on adding more features later to give the user more control over various variables such as the grid size and iteration speed, but I want to get the core functionality of the display working. I'm fairly sure the issue is in how I'm using the Timer class since it's the timing that's broken.
My first question is: Am I using the Timer class right? If so, then what is the issue? If not, how should I be using it?
Update
That's a good idea, MadProgrammer, and it's good to know I'm using Timer correctly. I realized that the part where it was "running" was actually how long it took each individual cell to update its color, so really my program is just absurdly slow and inefficient as it is now.
Here's my idea to improve the speed and efficiency. Mainly, I would use the timer delay to process the output of the next iteration, then the next time the timer "fires" I would change a "tick" variable that each cell would use as their signal to change color, as suggested. To accomplish this, I've added a timer to each cell (how good/bad an idea is this?) that kill time for a bit, then, in a blocking while loop, wait to see that the internal "tick" is equivalent to the global "tick" and immediately change color when that happens.
The end result is that it freezes as soon as it starts.
This is the timer I added to the Cell class constructor:
c_timer = new Timer(500, new ActionListener(){
public void actionPerformed(ActionEvent e){
c_timer.stop();
while (c_tick != tick);
setBackground(currentGrid[posx][posy]);
c_tick = 1 - c_tick;
if(run) timer.restart();
}
});
c_timer.start();
And this is how I've modified the global timer:
timer = new Timer(1000, new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
currentGrid[y][x] = newGrid[y][x];
tick = 1 - tick;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++){
if (b[y][x] > 6 || b[y][x] < 1) newGrid[y][x] = white;
else newGrid[y][x] = black;
}
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
b[y][x] = checkNeighbors(x,y);
if(!run) timer.stop();
}
});
Other than these changes, I removed the setColor() method in the Cell class. Can anyone point out the mistake that I'm making?
UPDATE 2
I should have updated earlier, but simply put, I discovered this is entirely the wrong way to do it. Instead of making a panel full of components and changing their backgrounds, you should instead just paint the panel with a grid:
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
for (int h = 0; h < board_size.height; h++){
for (int w = 0; w < board_size.width; w++){
try{
if (grid[h][w] == BLACK)
g.setColor(BLACK);
else g.setColor(WHITE);
g.fillRect(h * cell_size, w * cell_size, cell_size, cell_size);
} catch (ConcurrentModificationException cme){}
}
}
}
On each timer "tick" you first repaint the grid, then you process the next iteration to be painted on the next tick. Far more efficient, and updates instantly.
My I used a modified JPanel as the main grid component which implements an ActionListener to process every action the user performs on the rest of the gui as well as each timer tick:
public void actionPerformed(ActionEvent e) {
//Timer tick processing: count surrounding black cells, define next iteration
//using current rule set, update master grid
if (e.getSource().equals(timer)){
//Processing for each tick
}
else if(e.getSource()...
//Process events dispached by other components in gui
}
Of course, you'd have to set the board panel as the action listener for the timer.
Your usage of the Timer class in the first part of the question indeed looks correct. What is happening with a java.swing.Timer is that the ActionListener is triggered on the Event Dispatch Thread at specific intervals, specified with the delay parameter.
This also means that the code you put in the ActionListener should execute quickly. While your ActionListener code is executing, the UI cannot update as the UI thread (the Event Dispatch Thread) is occupied executing the ActionListener code. This is clearly documented in the javadoc of that class.
Although all Timers perform their waiting using a single, shared thread (created by the first Timer object that executes), the action event handlers for Timers execute on another thread -- the event-dispatching thread. This means that the action handlers for Timers can safely perform operations on Swing components. However, it also means that the handlers must execute quickly to keep the GUI responsive.
This is exactly what you encountered in your first update
new Timer(500, new ActionListener(){
public void actionPerformed(ActionEvent e){
//...
while (c_tick != tick){}
//...
}
});
With the while loop here you are blocking the Event Dispatch Thread. The c_tick != tick check will never change as the variables involved are only adjusted on the EDT, and you are blocking it with the loop.
Your second update seems to suggest everything is working now by switching from a panel. There are however two weird looking things:
The catch ConcurrentModificationException cme code block. In the code you posted I cannot immediately spot where you would encounter a ConcurrentModificationException. Remember that Swing is single-threaded. All actions which could interact with Swing components should be executed on the EDT, making the chance on encountering a ConcurrentModificationException a lot smaller compared to a multi-threaded application.
You stated
Of course, you'd have to set the board panel as the action listener for the timer
This seems untrue. Whatever ActionListener attached to the Timer needs to swap the current grid and the next grid, and calculate the next grid. Once the next grid is calculated, it needs to schedule a repaint of the grid panel. Whether or not this ActionListener is an anonymous/inner/separate class or the grid panel itself is irrelevant (at least functionality wise, design wise I would never opt to let the grid panel be a listener).
Side note: when you need to swap the current and new grid you use the following code
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
currentGrid[y][x] = newGrid[y][x];
}
}
If you still have performance problems, you can try using System.arrayCopy which is probably much faster then looping over the array manually.
Here is a game of life that updates the screen every half second in a conventional Java Swing manner.
It would be pretty simple to add controls for setting grid size and update rate and also an edit mode where the animation stops and cells can be set with the mouse. To change the update rate, call lifePane.run(newUpdateInterval) or lifePane.run(0) to pause. Call lifePane.setGenSize(width, height) to change the grid.
The main value in using a separate thread for the generation computation (as has been suggested, but I haven't done here) is that the animation will continue while you manipulate the GUI. For example if you use a slider to control speed, the animation will not pause is it will ifgenerations are computed in the UI thread.
Addition For grins, I added controls and used a java.utils.timer rather than Swing timer to get the effect of an extra thread for the rendering in this Gist.
But if you don't mind the pause while manipulating "mouse down" GUI items, single threading is fine. My old laptop runs a generation size of 1000x1000 at 20 updates per second in the Swing event thread with the GUI still behaving very nicely.
The method update() fills in the next generation from the current one and then swaps buffers. The override of paintComponent just draws the current generation. With this combination, all the timer needs to do is update and repaint.
Other conventions that may be useful to you are the method of handling window resizing and organizing the neighbor computation. Knowing good idioms helps avoid verbose code.
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class Life {
protected LifePane lifePane;
public static class LifePane extends JComponent {
private int rows, cols;
private byte[][] thisGen, nextGen;
private Timer timer;
public LifePane(int rows, int cols) {
setGenSize(rows, cols);
}
public final void setGenSize(int rows, int cols) {
this.rows = rows;
this.cols = cols;
thisGen = new byte[rows][cols];
nextGen = new byte[rows][cols];
Random gen = new Random();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
thisGen[i][j] = toByte(gen.nextBoolean());
}
}
}
#Override
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
// Clear the background.
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// Set the 1-valued cells black.
g.setColor(Color.BLACK);
int y0 = 0;
for (int i = 1; i < rows; i++) {
int y1 = i * height / (rows - 1);
int x0 = 0;
for (int j = 1; j < cols; j++) {
int x1 = j * width / (cols - 1);
if (thisGen[i][j] != 0) {
g.fillRect(x0, y0, x1 - x0, y1 - y0);
}
x0 = x1;
}
y0 = y1;
}
}
/**
* Make the next generation current.
*/
private void swapGens() {
byte [][] tmp = thisGen;
thisGen = nextGen;
nextGen = tmp;
}
private static byte toByte(boolean booleanVal) {
return booleanVal ? (byte) 1 : (byte) 0;
}
// Implementation of Conway's Game of Life rules.
private void updateCell(int x0, int x, int x1, int y0, int y, int y1) {
int n = thisGen[y0][x0] + thisGen[y0][x] + thisGen[y0][x1] +
thisGen[y] [x0] + thisGen[y] [x1] +
thisGen[y1][x0] + thisGen[y1][x] + thisGen[y1][x1];
nextGen[y][x] =
(thisGen[y][x] == 0) ? toByte(n == 3) : toByte(n >> 1 == 1);
}
private void updateRow(int y0, int y, int y1) {
updateCell(cols - 1, 0, 1, y0, y, y1);
for (int j = 1; j < cols - 1; ++j) {
updateCell(j - 1, j, j + 1, y0, y, y1);
}
updateCell(cols - 2, cols - 1, 0, y0, y, y1);
}
// Update the grid as a toroid and swap buffers.
public void update() {
updateRow(rows - 1, 0, 1);
for (int i = 1; i < rows - 1; i++) {
updateRow(i - 1, i, i + 1);
}
updateRow(rows - 2, rows - 1, 0);
swapGens();
}
/**
* Run the life instance with given update interval.
*
* #param updateInterval interval in milliseconds, <= 0 to stop
* #return this
*/
public LifePane run(int updateInterval) {
if (timer != null) {
timer.stop();
timer = null;
}
if (updateInterval > 0) {
timer = new Timer(updateInterval, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
});
timer.start();
}
return this;
}
}
public void run(int width, int height, int updateInterval) {
JFrame frame = new JFrame("Life");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
lifePane = new LifePane(width, height).run(updateInterval);
frame.setContentPane(lifePane);
frame.setPreferredSize(new Dimension(1024, 800));
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Life().run(100, 100, 500);
}
});
}
}
I'm planning on adding more features later to give the user more control over various variables such as the grid size and iteration speed, but I want to get the core functionality of the display working. I'm fairly sure the issue is in how I'm using the Timer class since it's the timing that's broken.
This is a good strategy, the program runs well, but it could be more efficient and scalable.
For example, I recommend using a custom SwingWorker class to execute your computation, and then send a message back to the UI.
Here is an example of how I would create this in a SwingWorker.
Here is addition information available from the Oracle resources site: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class CA_Driver extends JFrame
{
private JPanel gridPanel, buttonPanel;
private JButton start_pause, pause;
// private static Timer timer;
private Color black = Color.black;
private Color white = Color.white;
static Color[][] currentGrid, newGrid;
static Cell[][] cellGrid;
static boolean stop;
static int height = 20, width = 30, state;
boolean run;
private synchronized boolean getRun()
{
return run;
}
private synchronized void setRun(boolean run)
{
this.run = run;
}
/**
* http://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html
*
*/
SwingWorker worker = createNewWorker();
private SwingWorker createNewWorker()
{
return
new SwingWorker<Void, Void>()
{
protected Void doInBackground() throws Exception
{
while(getRun())
{
for (int x = 0; x < cellGrid.length; x++)
{
for (int y = 0; y < cellGrid[x].length; y++)
{
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
}
//Display processing for next frame
for (int x = 0; x < currentGrid.length; x++)
{
for (int y = 0; y < currentGrid[x].length; y++)
{
int b = checkNeighbors(y,x);
if (b > 4 || b < 2)
{
newGrid[x][y] = black;
}
else
{
newGrid[x][y] = white;
}
}
}
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
return null;
}
#Override
protected void done()
{
super.done();
}
};
}
public CA_Driver()
{
stop = false;
setRun(false);
currentGrid = new Color[height][width];
newGrid = new Color[height][width];
cellGrid = new Cell[height][width];
//Initialize grid values
for(int x = 0 ; x < currentGrid.length ; x++)
for(int y = 0 ; y < currentGrid[x].length ; y++)
{
int z = (int) (Math.random() * 2);
if(z == 0)
currentGrid[x][y] = newGrid[x][y] = white;
else
currentGrid[x][y] = newGrid[x][y] = black;
}
//Create grid panel
gridPanel = new JPanel();
gridPanel.setLayout(new GridLayout(height, width));
//Populate grid
for(int x = 0 ; x < newGrid.length ; x++)
for(int y = 0 ; y < newGrid[x].length ; y++)
{
cellGrid[x][y] = new Cell(x, y);
cellGrid[x][y].setBackground(newGrid[x][y]);
int z = (int) Math.random();
if(z == 0)
cellGrid[x][y].setBackground(black);
else
cellGrid[x][y].setBackground(currentGrid[x][y]);
gridPanel.add(cellGrid[x][y]);
}
//Create buttons
state = 0;
start_pause = new JButton();
start_pause.setText("Start");
start_pause.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
if(state == 0)
{
start_pause.setText("Pause");
setRun(true);
worker = createNewWorker();
worker.execute();
// timer.start();
state += 1;
}
else
{
start_pause.setText("Start");
setRun(false);
// timer.stop();
state -= 1;
}
}
});
buttonPanel = new JPanel(new BorderLayout());
buttonPanel.add(start_pause, BorderLayout.NORTH);
// buttonPanel.add(pause, BorderLayout.EAST);
//Initialize and display frame
this.add(gridPanel, BorderLayout.NORTH);
this.add(buttonPanel, BorderLayout.SOUTH);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
//this.setSize(500, 500);
pack();
this.setVisible(true);
worker.execute();
/*
//Initialize timer
timer = new Timer(1000, new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
for(int x = 0 ; x < cellGrid.length ; x++)
for(int y = 0 ; y < cellGrid[x].length ; y++)
{
cellGrid[x][y].setColor();
currentGrid[x][y] = newGrid[x][y];
}
//Display processing for next frame
for(int x = 0 ; x < currentGrid.length ; x++)
for(int y = 0 ; y < currentGrid[x].length ; y++)
{
int b = checkNeighbors(y, x);
if(b > 4 || b < 2)
newGrid[x][y] = black;
else
newGrid[x][y] = white;
}
if(!getRun())
timer.stop();
}
});
*/
}
public static void main(String[] args)
{
new CA_Driver();
}
private int checkNeighbors(int w, int h)
{
int b = 0;
//Top Left
if((w != 0) && (h != 0) && (currentGrid[h - 1][w - 1] == black))
b++;
//Top Middle
if((h != 0) && (currentGrid[h - 1][w] == black))
b++;
//Top Right
if((w != width - 1) && (h != 0) && (currentGrid[h - 1][w + 1] == black))
b++;
//Middle Left
if((w != 0) && (currentGrid[h][w - 1] == black))
b++;
//Middle Right
if((w != width - 1) && (currentGrid[h][w + 1] == black))
b++;
//Bottom left
if((w != 0) && (h != height - 1) && (currentGrid[h + 1][w - 1] == black))
b++;
//Bottom Middle
if((h != height - 1) && (currentGrid[h + 1][w] == black))
b++;
//Bottom Right
if((w != width - 1) && (h != height - 1) &&
(currentGrid[h + 1][w + 1] == black))
b++;
return b;
}
private class Cell extends JPanel
{
private Color c;
private int posx, posy;
public Cell(int x, int y)
{
posx = x;
posy = y;
}
public Point getLocation()
{
return new Point(posx, posy);
}
public void setColor()
{
c = newGrid[posx][posy];
setBackground(c);
}
public Dimension getPreferredSize()
{
return new Dimension(10, 10);
}
}
}

Frogger Type Game Collision Detection

I need some help figuring out how to capture and store collisions in a frogger game. I've managed to get the game to run fairly smoothly and now want to add collisions to the game. After some research I've found that rectangles seem like a viable option for my game, however, I'm not sure where I should start to actually capture a collision from an "enemy" and a "player". Here is my code so far:
public class myJPanel0 extends JPanel implements KeyListener, ActionListener
//This panel will contain the game
{
JButton menu;
Image myImage;
Graphics g;
myJPanel1 p1;
// Border[] gameBorder = new Border[]{BorderFactory.createTitledBorder("Border types")};
gameOverJPanel gp;
JButton lion = new JButton(new ImageIcon("images/download.jpg"));
JButton yard = new JButton("50 Yardline");
JButton score = new JButton("Touchdown");
JButton start = new JButton("Start");
JButton scoreKeeper = new JButton("Your score is");
JButton lives = new JButton("Lives left = ");
Timer tim;
int delay = 100;
int x = 296;
int y = 685;
int counter = 0;
ButtonObject[] enemies = new ButtonObject[10];
ButtonObject[] enemies1 = new ButtonObject[10];
ImageIcon icon[] = new ImageIcon[10];
public myJPanel0(myJPanel1 informedp1, gameOverJPanel informedgp)
{
super();
setLayout(null);
p1 = informedp1;
gp = informedgp;
setBackground(Color.MAGENTA);
menu = new JButton("Menu");
scoreKeeper.setBounds(16,80,200,55);
lives.setBounds(417, 80,200,55);
lion.setBounds(x,y,40,55);
score.setBounds(16,135,601,55);
yard.setBounds(16,410,601,55);
start.setBounds(16,685,601,55);
menu.setBounds(new Rectangle(250,5,80,30));
add(menu);
add(lives);
add(lion);
add(yard);
add(start);
add(scoreKeeper);
add(score);
setFocusable(true);
addKeyListener(this);
tim = new Timer(delay, this);
tim.start();
for (int i = 0; i <= 3; i++){ // loop that cycles through first half of enemy creation
String text = String.valueOf(i);
int y = 630 - (i * 55);
int x = 16;
enemies[i] = new ButtonObject(text+"??", x, y, 40, 55);
enemies[i].setBounds(new Rectangle(x, y, 40,55));
add(enemies[i]);
}
for (int i = 0; i <= 3; i++){ // second have enemy creation
String text = String.valueOf(i);
int y = 355 - (i * 55);
int x = 16;
enemies1[i] = new ButtonObject(text+"??", x, y, 40, 55);
enemies1[i].setBounds(new Rectangle(x, y, 40,55));
add(enemies1[i]);
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Image myLion = Toolkit.getDefaultToolkit().getImage("images/download.jpg");
g.drawImage(myLion,296,355,40,55,this);
if(p1.myImage1 == 1)
{
super.paintComponent(g);
Image myImage = Toolkit.getDefaultToolkit().getImage("images/snow.jpg");//Place holder for now, we can come up with our own image.
g.drawImage(myImage, 0, 0,680,880, this);
requestFocusInWindow();
}else if(p1.myImage1 == 2)
{
super.paintComponent(g);
Image myImage = Toolkit.getDefaultToolkit().getImage("images/grass.jpg");//Place holder for now, we can come up with our own image.
g.drawImage(myImage, 0, 0,680,880, this);
requestFocusInWindow();
}
else{
super.paintComponent(g);
Image myImage = Toolkit.getDefaultToolkit().getImage("images/stone.jpg");//Place holder for now, we can come up with our own image.
g.drawImage(myImage, 0, 0,680,880, this);
requestFocusInWindow();
}
}
public void keyPressed(KeyEvent evt)
{
System.out.println("Key pressed");
int kk = evt.getKeyCode();
if(kk == evt.VK_LEFT) {x=x-40;}
else if(kk == evt.VK_RIGHT) {x=x+40;}
else if(kk == evt.VK_UP) {y=y-55;}
else if(kk == evt.VK_DOWN) {y=y+55;}
lion.setBounds(x,y,40,55);
System.out.println(x);
System.out.println(y);
if(y <= 135){
counter = counter + 1;
scoreKeeper.setText("Your score is " + counter);
y = 740;
}
}
public void keyReleased(KeyEvent evt) { }
public void keyTyped(KeyEvent evt) { }
public void actionPerformed(ActionEvent event) {
for (int i = 0; i <= 3; i++)
{
enemies[i].move();
}
for (int i = 0; i <= 3; i++)
{
enemies1[i].move();
}
}
}

How can I let JToolBars wrap to the next line (FlowLayout) without them being hidden ty the JPanel below them?

I am in the process of making a GUI which shows three JToolBars above a big JPanel. These toolbars are collectively very large, so I'm using a FlowLayout to make them wrap to the next line if they reach the JFrame border. The problem is that when they wrap to the next line, they become hidden by the JPanel below.. I wish I could force the JPanel containing the toolbars to grow enough to show all toolbars..
Is there a way to do this? Or is there another way to make these toolbars visible?
I have run into this problem before. I found the best solution is to use a modified version of FlowLayout that takes into account vertical changes and wraps them to the next line. Here is the code for such a layout.
import java.awt.*;
/**
* A modified version of FlowLayout that allows containers using this
* Layout to behave in a reasonable manner when placed inside a
* JScrollPane
* #author Babu Kalakrishnan
* Modifications by greearb and jzd
*/
public class ModifiedFlowLayout extends FlowLayout {
public ModifiedFlowLayout() {
super();
}
public ModifiedFlowLayout(int align) {
super(align);
}
public ModifiedFlowLayout(int align, int hgap, int vgap) {
super(align, hgap, vgap);
}
public Dimension minimumLayoutSize(Container target) {
// Size of largest component, so we can resize it in
// either direction with something like a split-pane.
return computeMinSize(target);
}
public Dimension preferredLayoutSize(Container target) {
return computeSize(target);
}
private Dimension computeSize(Container target) {
synchronized (target.getTreeLock()) {
int hgap = getHgap();
int vgap = getVgap();
int w = target.getWidth();
// Let this behave like a regular FlowLayout (single row)
// if the container hasn't been assigned any size yet
if (w == 0) {
w = Integer.MAX_VALUE;
}
Insets insets = target.getInsets();
if (insets == null){
insets = new Insets(0, 0, 0, 0);
}
int reqdWidth = 0;
int maxwidth = w - (insets.left + insets.right + hgap * 2);
int n = target.getComponentCount();
int x = 0;
int y = insets.top + vgap; // FlowLayout starts by adding vgap, so do that here too.
int rowHeight = 0;
for (int i = 0; i < n; i++) {
Component c = target.getComponent(i);
if (c.isVisible()) {
Dimension d = c.getPreferredSize();
if ((x == 0) || ((x + d.width) <= maxwidth)) {
// fits in current row.
if (x > 0) {
x += hgap;
}
x += d.width;
rowHeight = Math.max(rowHeight, d.height);
}
else {
// Start of new row
x = d.width;
y += vgap + rowHeight;
rowHeight = d.height;
}
reqdWidth = Math.max(reqdWidth, x);
}
}
y += rowHeight;
y += insets.bottom;
return new Dimension(reqdWidth+insets.left+insets.right, y);
}
}
private Dimension computeMinSize(Container target) {
synchronized (target.getTreeLock()) {
int minx = Integer.MAX_VALUE;
int miny = Integer.MIN_VALUE;
boolean found_one = false;
int n = target.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = target.getComponent(i);
if (c.isVisible()) {
found_one = true;
Dimension d = c.getPreferredSize();
minx = Math.min(minx, d.width);
miny = Math.min(miny, d.height);
}
}
if (found_one) {
return new Dimension(minx, miny);
}
return new Dimension(0, 0);
}
}
}
Take a look at WrapLayout. It worked for me. Here is the code.

Categories

Resources