How how to get the cirlces to respond to the timer? - java

I am in the middle of doing a project and I need some help with make the circles that I am drawing to respond to my timer. I want to make them go slower and faster using my slider that I put in. Attached is the code below. (I know that my code is going to be a jumbled mess, I am using some source code that our professor provide to get the slider on my screen. I am just having a hard time getting the slider/timer talk to my circles.)
import java.util.ArrayList;
import java.util.Random;
import java.awt.*;
import java.awt.event.*;
import javax.swing. *;
import javax.swing.event.*;
import SpeedControlPanel.SlideListener;
public class DotsPanel extends JPanel
{
private final int SIZE = 6; // radius of each dot
private static final long serialVersionUID = 1L;
private ArrayList<Point> pointList;
private Timer timer;
private JLabel lLabel;
private JSlider sSlider;
private JPanel pPanel;
private Circle bouncingBall;
int sSliderHt;
private int moveX, moveY;
AnimationListener animationList;
//-----------------------------------------------------------------
// Constructor: Sets up this panel to listen for mouse events.
//-----------------------------------------------------------------
public DotsPanel()
{
timer = new Timer(30, new AnimationListener());
this.setLayout(new BorderLayout());
bouncingBall = new Circle(SIZE);
Random rand = new Random();
pointList = new ArrayList<Point>();
addMouseListener (new DotsListener());
addMouseMotionListener( new DotsListener());
setBackground(Color.black);
setPreferredSize(new Dimension(500, 300));
lLabel= new JLabel("Timer Delay");
lLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
sSlider = new JSlider(JSlider.HORIZONTAL, 0, 200, 30);
sSlider.setMajorTickSpacing(40);
sSlider.setMinorTickSpacing(10);
sSlider.setPaintTicks(true);
sSlider.setPaintLabels(true);
sSlider.setAlignmentX(Component.LEFT_ALIGNMENT);
sSlider.addChangeListener(new SlideListener());
pPanel= new JPanel();
pPanel.add(lLabel);
pPanel.add(sSlider);
add(pPanel, BorderLayout.SOUTH);
animationList = new AnimationListener();
animationList.timer.start();
/*int[] xArray = new int[1000];
int[] yArray = new int[1000];
for(int i = 0; i < xArray.length; i++)
{
xArray[i] = rand.nextInt(10) + 1;
yArray[i] = rand.nextInt(10) + 1;
}*/
}
//-----------------------------------------------------------------
// Draws all of the dots stored in the list.
//-----------------------------------------------------------------
public void paintComponent(Graphics page)
{
Random rand = new Random();
int R = rand.nextInt(256);
int G = rand.nextInt(256);
int B = rand.nextInt(256);
super.paintComponent(page);
page.setColor(new Color(R%255, (G*3)%255, (B+128)%255));
for (Point spot : pointList)
//page.fillOval(spot.x-SIZE, spot.y-SIZE, SIZE*2, SIZE*2);
page.fillOval(spot.x, spot.y, SIZE*2, SIZE*2);
page.drawString("Count: " + pointList.size(), 5, 15);
}
private class AnimationListener implements ActionListener {
int xRand[];
int yRand[];
Random rand;
public AnimationListener() {
rand = new Random();
xRand = new int[1000];
yRand = new int[1000];
//Filling random values between 0 to 10
for (int i = 0; i < 1000; i++) {
xRand[i] = rand.nextInt(10) + 1;
yRand[i] = rand.nextInt(10) + 1;
}
}
private Timer timer = new Timer(50, this);
#Override
public void actionPerformed(ActionEvent e) {
//Put here for the bounce off of the wall
Rectangle window = getBounds();
for (int i = 0; i < pointList.size(); i++) {
Point spot = pointList.get(i);
spot.x += xRand[i];
spot.y += yRand[i];
if (spot.x <= 0) {
xRand[i] = Math.abs(xRand[i]);
} else if (spot.x >= window.width - (SIZE*2)) {
xRand[i] = -Math.abs(xRand[i]);
}
if (spot.y <= 0) {
yRand[i] = Math.abs(yRand[i]);
} else if (spot.y >= window.height - (SIZE*2)) {
yRand[i] = -Math.abs(yRand[i]);
}
}
bouncingBall.move(moveX, moveY);
// change direction if ball hits a side
int x = bouncingBall.getX();
int y = bouncingBall.getY();
if (x < 0 || x >= WIDTH - SIZE)
{
moveX = moveX * -1;
}
if (y <= 0 || y >= HEIGHT - SIZE)
{
moveY = moveY * -1;
}
sSliderHt =sSlider.getSize().height;
repaint();
repaint();
}
}
//*****************************************************************
// Represents the listener for mouse events.
//*****************************************************************
private class DotsListener implements MouseListener, MouseMotionListener
{
//--------------------------------------------------------------
// Adds the current point to the list of points and redraws
// the panel whenever the mouse button is pressed.
//--------------------------------------------------------------
public void mousePressed(MouseEvent event)
{
pointList.add(event.getPoint());
repaint();
}
public void mouseDragged(MouseEvent event) {
pointList.add(event.getPoint());
repaint();
}
//--------------------------------------------------------------
// Provide empty definitions for unused event methods.
//--------------------------------------------------------------
public void mouseClicked(MouseEvent event) {}
public void mouseReleased(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
}
private class SlideListener implements ChangeListener
{
// ------------------------------------------------
// Called when the state of the slider has changed;
// resets the delay on the timer.
// ------------------------------------------------
public void stateChanged (ChangeEvent event)
{
//int sSliderHt =sSlider.getSize().height;
timer.setDelay(sSlider.getValue());
}
}
}

I want to make them go slower and faster using my slider that I put in.
So it looks to me like you already have logic to make each circle move a random distance each time the Timer fires.
So, just use the slider to change the interval at which the timer fires. Then each time the Timer fires you set the new location of the circles based on your random distance.
Other issues:
The paintComponent(...) method is for painting only. It should NOT set properties of the class. For example you should not be generating random values in the method. You can't control when the paintComponent() is invoked and you don't want to randomly change the properties of the objects you are painting.
Don't use an Array to hold random values. First of all you don't know how many object will be added so you don't want to set a size limit. Instead use an ArrayList.
Create a custom object to contain all the properties you want to control. So you would have the size/location/color etc for each circle you want to paint in a custom object.
See: get width and height of JPanel outside of the class for an example using the above suggestions.

Related

JPanel creates twice

I am trying to make a GUI for a battleship game. One class is for creating the GUI itself and a second class is on top of it to manage the board in the game. My problem is that the JPanel creates twice once a mouse click happens (the mouse click is supposed to be where one is firing in the game and then marks that as a hit/miss). I'm not sure why it is creating twice. Is it because of the passing of a panel? Code below and a photo of what the code generates.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class BattleshipApplet extends JApplet implements MouseListener {
private final JButton playButton = new JButton("Play");
private final JLabel msgBar = new JLabel("Click Play to start game");
private BoardPanel panel;
public BattleshipApplet(){
playButton.addActionListener(this::playButtonClicked);
addMouseListener(this);
}
public void init(){
configureGui();
}
private void configureGui(){
setLayout(new BorderLayout());
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEFT));
buttons.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
buttons.add(playButton);
add(buttons, BorderLayout.NORTH);
msgBar.setBorder(BorderFactory.createEmptyBorder(10,10,5,5));
add(createBoardPanel(), BorderLayout.CENTER);
add(msgBar, BorderLayout.SOUTH);
}
private BoardPanel createBoardPanel(){
panel = new BoardPanel();
return panel;
}
private void displayMessage(String msg){
msgBar.setText(msg);
}
private void playButtonClicked(ActionEvent event){
displayMessage("Play button clicked!");
}
public void mouseClicked(MouseEvent e) {
panel.mouseClickedAt(e.getX(), e.getY());
e.consume();
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
}
The board class using JPanel
[![import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class BoardPanel extends JPanel {
int mx, my;
boolean rect1Clicked;
//gamePlay a;
public void init(){
rect1Clicked = false;
}
/***Your applet shall show the status of the board before and after
each shot, including the number of shots made and the status of
each place (no shot or hit/miss shot). ***/
public void paint(Graphics g){
boolean miss = false;
for (int i=0; i<11; i++){
g.setColor(Color.blue);
g.drawLine(20,20+i*28, 300, 20+i*28);
}
for (int i=0; i<11; i++)
g.drawLine(20+i*28,20,20+i*28,300);
//if inside board
if(rect1Clicked == true){
g.setColor(Color.green);
//aligns to square to check in computer board for hit/miss
int bx =(my-20)/28;
int by =(mx-20)/28;
//check hit on board
//if shot was a miss
if(miss == true ){
//update to white
g.setColor(Color.white);
}
//if shot was a hit
if(miss == false){
//update to red
g.setColor(Color.red);
}
//compare to line for fill
int fillx = mx/2;
int filly = my/2 ;
if(mx<=47){
fillx = 20;
}
if(mx>47 && mx<=75){
fillx = 48;
}
if(mx>75 && mx<=103){
fillx = 76;
}
if(mx>103 && mx <=131){
fillx = 104;
}
if(mx>131 && mx<=159){
fillx = 132;
}
if(mx>159 && mx<=187){
fillx = 160;
}
if(mx>187 && mx <=215){
fillx = 188;
}
if(mx>215 && mx <=243){
fillx = 216;
}
if(mx>243 && mx <=271){
fillx = 244;
}
if(mx>271 && mx<=299){
fillx = 272;
}
if(mx>299){
fillx = 300;
}
//y comparisons
if(my<=47){
filly = 20;
}
if(my>47 && my<=75){
filly = 48;
}
if(my>75 && my<=103){
filly = 76;
}
if(my>103 && my <=131){
filly = 104;
}
if(my>131 && my<=159){
filly = 132;
}
if(my>159 && my<=187){
filly = 160;
}
if(my>187 && my <=215){
filly = 188;
}
if(my>215 && my <=243){
filly = 216;
}
if(my>243 && my <=271){
filly = 244;
}
if(my>271 && my<=299){
filly = 272;
}
if(my>299){
filly = 300;
}
g.drawString("("+mx+","+my+")",mx,my);
//25 describes size of square
g.fillOval(fillx, filly, 25, 25);
}
}
public void game(BoardPanel p){
//while game plays
}
public void mouseClickedAt(int x, int y){
mx = x;
my = y;
//user clicked inside of board space
if(mx>20 && mx<300 && my>20 && my<300){
//send to board in MainBattleship
rect1Clicked = true;
}
//updates board
repaint();
}
}][1]][1]
I am so lost, thank you for any help!
Suggestions:
Don't override a JPanel's paint method but rather its paintComponent method as this is safer, and later when you want to do animation, will result in smoother animation.
Most important you almost always need to call the super's painting method within your own, else the JPanel will not remove previous image artifacts that need to be cleaned up. So if you continue to override paint (although I recommend against, this) the first line of your override should be super.paint(g);, or if you override paintComponent then the first line should be super.paintComponent(g);, of course assuming that you're methods use a Graphics parameter named g.
Also, add the MouseListener to the JPanel, not to the applet, since it is the mouse click location on the panel that matters to you.
Also, use a grid of components or some math to greatly simplify your code -- that ugly list of if blocks should be replaced by a much simpler for loop, one using basic math.
Consider extracting that logic discussed in the point above out of your painting method and into a model of some kind, perhaps a 2D array of boolean.
You're using a lot of "magic" numbers in your code, numbers that should be changed to a combination of constants and mathematically derived numbers.
Notice what happens if you click on your GUI, and then resize it, or if you minimize and then restore it -- you lose all red circles except for the last one pressed. This is another reason to use a grid of boolean or other model to hold state of the game, and then use this model when drawing your GUI.
On further thinking, you might want a 2D array of an enum or an int array, since the grid cell state will likely be more than 2 values (true or false), but rather will be three values -- untested, hit, and miss, and you'll likely want to fill your oval with red if a hit or white if a miss.
For example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
import javax.swing.*;
public class GridExample {
private static void createAndShowGui() {
final GridPanel gridPanel = new GridPanel();
JButton resetBtn = new JButton(new AbstractAction("Reset") {
#Override
public void actionPerformed(ActionEvent e) {
gridPanel.reset();
}
});
JPanel btnPanel = new JPanel();
btnPanel.add(resetBtn);
JFrame frame = new JFrame("GridExample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(gridPanel);
frame.getContentPane().add(btnPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class GridPanel extends JPanel {
private static final int ROWS = 10;
private static final int CELL_WIDTH = 28;
private static final int PAD = 20;
private static final int PREF_W = ROWS * CELL_WIDTH + 2 * PAD;
private static final int PREF_H = PREF_W;
private static final Color GRID_COLOR = Color.blue;
private static final Color CIRCLE_COLOR = Color.red;
private static final int SML_GAP = 2;
private boolean[][] grid = new boolean[ROWS][ROWS];
public GridPanel() {
addMouseListener(new MyMouse());
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public void reset() {
grid = new boolean[ROWS][ROWS]; // fills grid with false
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw grid:
g2.setColor(GRID_COLOR);
for (int i = 0; i <= ROWS; i++) {
int x1 = PAD + i * CELL_WIDTH;
int y1 = PAD;
int x2 = x1;
int y2 = PAD + CELL_WIDTH * ROWS;
g2.drawLine(x1, y1, x2, y2);
g2.drawLine(y1, x1, y2, x2);
}
// iterate through the grid boolean array
// draw red circles if the grid value is true.
g2.setColor(CIRCLE_COLOR);
int w = CELL_WIDTH - 2 * SML_GAP; // width of the circle to draw
int h = w;
// nested for loop to go through the grid array
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c]) {
int x = PAD + c * CELL_WIDTH + SML_GAP;
int y = PAD + r * CELL_WIDTH + SML_GAP;
g2.fillOval(x, y, w, h);
}
}
}
}
private class MyMouse extends MouseAdapter {
public void mousePressed(MouseEvent e) {
int x = e.getPoint().x;
int y = e.getPoint().y;
if (x < PAD || y < PAD) {
// clicked above or to right of grid
return;
}
int r = (y - PAD) / CELL_WIDTH;
int c = (x - PAD) / CELL_WIDTH;
// if clicked to right or below grid.
// the < 0 part is likely unnecessary, but why not be extra safe?
if (r >= ROWS || c >= ROWS || r < 0 || c < 0) {
return;
}
grid[r][c] = true;
repaint();
}
}
}

Java - Removing my 'worm' from an arraylist and JFrame

My program creates randomly colored and sized worms that expand on a JFrame/JPanel. As time progresses the worms are constantly expanding in random directions. When new worm is clicked a new worm is born somewhere on the screen.
Where my issue arises:
I having trouble understanding how I would then kill worms. When I click the kill worm button I want the worm to disappear (OR stop growing) on screen and it to be removed from the arraylist. I have no idea how to even begin doing this. I personally think removing an instance of the arraylist would be the best way, but how would I go about actually removing the worm from the screen.
Below is my code:
Main Class:
package Main;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
ThreadFrame myFrame = new ThreadFrame();
myFrame.setSize(new Dimension(640, 480));
myFrame.setLocationRelativeTo(null);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setTitle("Worms! - Jonathan Perron");
myFrame.setVisible(true);
myFrame.setResizable(false);
}
}
ThreadFrame Class:
package Main;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ThreadFrame extends JFrame implements ActionListener {
int index = 0;
JButton btnNewWorm, btnKillWorm;
JPanel myPanel2 = new JPanel();
ArrayList<DrawThread> worms = new ArrayList<DrawThread>();
public JPanel getMyPanel2(){
return this.myPanel2;
}
public ThreadFrame() {
JPanel myPanel = new JPanel();
btnNewWorm = new JButton("New Worm");
btnKillWorm = new JButton("Kill Worm");
myPanel.setBounds(0, 400, 640, 80);
myPanel.setLayout(new FlowLayout());
myPanel2.setSize(new Dimension(640, 400));
myPanel2.setLayout(null);
myPanel2.setBackground(Color.WHITE);
btnNewWorm.setBounds(100, 410, 200, 30);
btnKillWorm.setBounds(340, 410, 200, 30);
add(btnNewWorm);
add(btnKillWorm);
add(myPanel2);
add(myPanel);
btnNewWorm.addActionListener(this);
btnKillWorm.addActionListener(this);
}
public void actionPerformed(ActionEvent AE) {
if(AE.getSource() == btnNewWorm){
DrawThread dw = new DrawThread(myPanel2);
worms.add(dw);
System.out.println("New worm!");
}
if(AE.getSource() == btnKillWorm){
//stop worms from growing or complete disappear from JFrame
System.out.println("Kill worm!");
}
}
}
DrawThread Class:
package Main;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.util.Random;
import javax.swing.JPanel;
public class DrawThread extends Thread implements Runnable{
Random rand = new Random();
JPanel panel2 = new JPanel();
Graphics2D g, graph;
private int sleepTime, wormDiameter, hue, saturation, brightness, randomWidth, randomHeight;
public DrawThread(int sleepTime, int wormDiameter, int hue, int saturation, int brightness, int randomWidth, int randomHeight, JPanel myPanel2) {
this.sleepTime = sleepTime;
this.wormDiameter = wormDiameter;
this.brightness = brightness;
this.hue = hue;
this.saturation = saturation;
this.randomWidth = randomWidth;
this.randomHeight = randomHeight;
g = (Graphics2D) myPanel2.getGraphics();
}
public void setColor(int hue){
this.hue = hue;
}
public int getSleepTime(){
return sleepTime;
}
public void setSleepTime(int sleepTime){
this.sleepTime = sleepTime;
}
public DrawThread(JPanel myPanel2){
//get panel dimensions
int panelWidth = myPanel2.getWidth();
int panelHeight = myPanel2.getHeight();
//worm location
randomWidth = rand.nextInt(panelWidth);
randomHeight = rand.nextInt(panelHeight);
//worm size
wormDiameter = rand.nextInt(7)+3;
//worm color
hue = rand.nextInt(255);
saturation = rand.nextInt(255);
brightness = rand.nextInt(255);
Color color = new Color(hue, saturation, brightness);
//sleep
sleepTime = rand.nextInt(80) + 20;
//Graphics
g = (Graphics2D) myPanel2.getGraphics();
g.setColor(color);
Ellipse2D.Double ellipse2D = new Ellipse2D.Double(randomWidth, randomHeight, wormDiameter, wormDiameter);
g.fill(ellipse2D);
Thread thread1 = new Thread(new DrawThread(sleepTime, wormDiameter, hue, saturation, brightness, randomWidth, randomHeight, myPanel2));
thread1.start();
}
public void run(){
try {
while(true) {
Thread.sleep(sleepTime);
Color color = new Color(hue, saturation, brightness);
g.setColor(color);
int addedHeight = 0, addedWidth = 0;
int random = rand.nextInt(8);
//determining the worms next move location
if(random == 0){ addedWidth = 0; addedHeight = 1; } //North
if(random == 1){ addedWidth = 1; addedHeight = 1; } //North-East
if(random == 2){ addedWidth = 1; addedHeight = 0; } //East
if(random == 3){ addedWidth = 1; addedHeight = -1; } //South-East
if(random == 4){ addedWidth = 0; addedHeight = -1; } //South
if(random == 5){ addedWidth = -1; addedHeight = -1; } //South-West
if(random == 6){ addedWidth = -1; addedHeight = 0; } //West
if(random == 7){ addedWidth = -1; addedHeight = 1; } //North-West
//Prevent worms from getting off the screen
if(randomHeight >= 480){ addedHeight = -1; }
if(randomHeight <= 0){ addedHeight = 1; }
if(randomWidth >= 640){ addedWidth = -1; }
if(randomWidth <= 0){ addedWidth = 1; }
randomWidth += addedWidth;
randomHeight += addedHeight;
Ellipse2D.Double e = new Ellipse2D.Double(randomWidth, randomHeight, wormDiameter, wormDiameter);
g.fill(e);
}
}
catch (InterruptedException e) {
System.out.println("ERROR!");
}
}
public String toString() {
String result = "SleepTime: " + sleepTime + "\nWorm Diameter: " + wormDiameter
+ "\nHue: " + hue + "\nSaturation: " + saturation + "\nBrightness: "
+ brightness + "\nWidth: " + randomWidth + "\nHeight: " + randomHeight;
return result;
}
}
Any help is greatly appreciated! :)
EDIT: This is the assignment that my teacher has given to write this program.
===========================================================================
In this assignment, we’re going to write a program that draws images of “worms” in a window. The
worms will grow with time, moving in random directions. Each worm will be a different color and will
grow at a different rate. A separate Thread object will manage the drawing of each worm. Here’s an
example of what the window will look like after two worms have grown for a while.
Write a class called ThreadFrame that extends JFrame. This class should include a main method that
creates one instance of this class. This should produce a GUI with an appearance similar to what you see
above. Make the window 640 by 480 pixels, and do not allow the user to resize it. Add two JPanels to
the content pane, a white one in the center region on which the threads will be drawn, and a gray one in
the south region to hold the JButtons marked “New Worm” and “Kill Worm”.
Add an action listener to the “New Worm” button, so that when you click on it, it creates a new instance
of a class called DrawThread (to be described shortly), adds it to an ArrayList, and starts it. Add an action
listener to the “Kill Worm” button, so that when you click on it, it removes the first DrawThread from
the ArrayList and interrupts it.
The class DrawThread extends Thread, and does most of the work of the program. This class will draw
on the upper panel of the ThreadFrame, so a reference to this panel must be passed to the constructor
of DrawThread, when this constructor is called from ThreadFrame. The constructor should perform the
following tasks:
Assign the JPanel reference (received as an argument) to an instance variable of this object.
Get the graphics context for the JPanel (use the getGraphics method), cast it to type Graphics2D, and
assign it to an instance variable.
Determine the width and height of this JPanel, and save these values.
Create a Color object, with randomly-chosen values for the three arguments to set the red, green, and
blue intensities, and assign this object to an instance variable.
Randomly choose a sleep time for this thread, between 20 and 100 milliseconds. This will determine
how rapidly the image grows.
Regarding your instructions
Write a class called ThreadFrame that extends JFrame. This class should include a main method that creates one instance of this class. This should produce a GUI with an appearance similar to what you see above. Make the window 640 by 480 pixels, and do not allow the user to resize it. Add two JPanels to the content pane, a white one in the center region on which the threads will be drawn, and a gray one in the south region to hold the JButtons marked “New Worm” and “Kill Worm”.
OK, you've got this.
Add an action listener to the “New Worm” button, so that when you click on it, it creates a new instance of a class called DrawThread (to be described shortly), adds it to an ArrayList, and starts it.
Again, you've done this.
Add an action listener to the “Kill Worm” button, so that when you click on it, it removes the first DrawThread from the ArrayList and interrupts it.
Break it down:
Get the most recent worm from the ArrayList -- you would use two ArrayList methods, size() and get(...) to achieve this. Give it a try.
Then interrupt the thread. Once you get the object from the array list, you will need to call a Thread method on it, and I'm guessing that you'll be able figure out which method, right (read the instructions for this, i.e, "and interrupts it")? ;)
Edit
Note, that his recommendations are not good, and I would not hire your instructor or course director as a Swing programmer if I needed one.
Edit
Just for grins, here is another way to code this sort of thing. Note that it does not satisfy your assignment requirements (which is one reason I don't hesitate to post it), but it shows what I believe are better Swing behaviors:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
public class MyWormMain {
private static void createAndShowGui() {
MyWormDrawPanel drawPanel = new MyWormDrawPanel();
MyWormButtonPanel btnPanel = new MyWormButtonPanel(drawPanel);
JFrame frame = new JFrame("Worms");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(drawPanel, BorderLayout.CENTER);
frame.getContentPane().add(btnPanel.getMainPanel(), BorderLayout.SOUTH);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class MyWormDrawPanel extends JPanel {
private static final int PREF_W = 640;
private static final int PREF_H = 480;
private static final Color BACKGROUND = Color.WHITE;
private static final int TIMER_DELAY = 50;
private List<MyWorm> wormList = new ArrayList<>();
private Timer wormTimer;
private Random random = new Random();
public MyWormDrawPanel() {
setBackground(BACKGROUND);
wormTimer = new Timer(TIMER_DELAY, new WormTimerListener());
wormTimer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (MyWorm worm : wormList) {
worm.draw(g2);
}
}
public void addWorm() {
int r = random.nextInt(128) + 128;
int g = random.nextInt(128) + 128;
int b = random.nextInt(128) + 128;
int rand = random.nextInt(3);
switch (rand) {
case 0:
r /= 3;
break;
case 1:
g /= 3;
break;
case 2:
b /= 3;
default:
break;
}
Color color = new Color(r, g, b);
int x = random.nextInt(PREF_W);
int y = random.nextInt(PREF_H);
Point head = new Point(x, y);
wormList.add(new MyWorm(color, head, PREF_W, PREF_H));
}
public void killWorm() {
if (wormList.size() > 0) {
wormList.remove(wormList.size() - 1);
}
}
private class WormTimerListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
for (MyWorm worm : wormList) {
worm.grow();
}
repaint();
};
}
}
#SuppressWarnings("serial")
class MyWormButtonPanel {
private static final int GAP = 15;
private JPanel mainPanel = new JPanel();
private MyWormDrawPanel drawPanel;
public MyWormButtonPanel(MyWormDrawPanel drawPanel) {
this.drawPanel = drawPanel;
mainPanel.setLayout(new GridLayout(1, 0, GAP, GAP));
mainPanel.setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
mainPanel.add(new JButton(new AddWormAction("Add Worm", KeyEvent.VK_A)));
mainPanel.add(new JButton(new KillWormAction("Kill Worm", KeyEvent.VK_K)));
}
public JComponent getMainPanel() {
return mainPanel;
}
private class AddWormAction extends AbstractAction {
public AddWormAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
drawPanel.addWorm();
}
}
private class KillWormAction extends AbstractAction {
public KillWormAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
drawPanel.killWorm();
}
}
}
class MyWorm {
private static final int MAX_DIR = 360;
private static final int SEG_WIDTH = 20;
private static final int MAX_RAND_DIR = 60;
private Color color;
private List<Point> body = new ArrayList<>();
private Random random = new Random();
private int direction;
private int maxX;
private int maxY;
public MyWorm(Color color, Point head, int maxX, int maxY) {
this.color = color;
body.add(head);
direction = random.nextInt(MAX_DIR);
this.maxX = maxX;
this.maxY = maxY;
}
public void grow() {
Point lastPt = body.get(body.size() - 1);
int x = lastPt.x
+ (int) (SEG_WIDTH * 3 * Math.cos(Math.toRadians(direction)) / 4.0);
int y = lastPt.y
+ (int) (SEG_WIDTH * 3 * Math.sin(Math.toRadians(direction)) / 4.0);
if (x < 0) {
x = maxX - 1;
}
if (x > maxX) {
x = 0;
}
if (y < 0) {
y = maxY - 1;
}
if (y > maxY) {
y = 0;
}
Point nextPoint = new Point(x, y);
direction += random.nextInt(MAX_RAND_DIR) - MAX_RAND_DIR / 2;
body.add(nextPoint);
}
public void draw(Graphics2D g2) {
Graphics2D g2b = (Graphics2D) g2.create();
g2b.setColor(color);
for (Point p : body) {
int x = p.x - SEG_WIDTH / 2;
int y = p.y - SEG_WIDTH / 2;
int width = SEG_WIDTH;
int height = SEG_WIDTH;
g2b.fillOval(x, y, width, height);
}
g2b.dispose();
}
}

Animating Separate Objects

I've been working on an "elevator simulator" where I have to animate "elevators". I was able to create different elevator objects, which I did by having each Elevator Object have the parameters of width, height, and coordinates. I then stored them all into an array and used a for loop to draw them into my frame using JPanel's PaintComponent method.
Can you guys suggest a way or give me advice to animate them separately from each other, like say move them up and down independently? I was able to make it move when I only had ONE elevator, but when I tried to apply it to multiple elevators, it did not work.
My previous attempt involved an ActionListener (that listens to a button press to "start" the animation) that simply changes the x and y coordinates of the SINGLE elevator. So How do I go and do that with several elevators (the number of elevators is arbitrary to the user). Do I have to make as many ActionListeners as there are elevators, so it can listen to them independently?
Here's the ActionListener that worked when there's only ONE elevator. It only moves up so far.
private ActionListener timerActionUp = new ActionListener()
{
private int iterator = 0;
private int top = 0;
public void actionPerformed(ActionEvent e)
{
if(top<floorQueue.length){
if(iterator<floorQueue[top]){ //floorQueue is so that the elevator knows where to stop
if(movefloorup<VERTICALFLOORDISTANCE*6){ //this is when the elevator reaches the "top" floor that can fit into the screen, and moves to the next column representing the floors further up
repaint();
movefloorup = movefloorup + VERTICALFLOORDISTANCE;
System.out.println(movefloorup);
iterator++;
}
else{
//timer.stop();
repaint();
switchmarker = 1; //makes elevator moves to the next column
movefloorup = 0;
iterator++;
}
}
else
{
System.out.println("Picking up passengers...");
elevatorCapacity = elevatorCapacity + peopleQueue[floorQueue[top]];
System.out.println("Passengers in elevator: "+elevatorCapacity);
peopleQueue[floorQueue[top]] = 0; //Updates the number of people in the elevator
top++;
if(elevatorCapacity >= 5)
{
System.out.println("WARNING! ELEVATOR FULL!");
elevfull = 1;
}
//timer.stop();
}
}
else
{
System.out.println("Done picking up passengers.");
timer.stop();
}
}
};
Thank you very much!
"Do I have to make as many ActionListeners as there are elevators, so it can listen to them independently?"
No, that would require multiple Timers. Avoid doing this whenever you can.
"Can you guys suggest a way or give me advice to animate them separately from each other, like say move them up and down independently?"
What you should do is try and implement the business logic in methods within your Elevator class and just call the those methods while looping through all the Elevators in your array.
Two make the Elevators to appear to move independently, you can have flags, say in your move method, like
public void move() {
if (move) {
// do something
}
}
What ever is your reason for making the elevator move, that will be the reason the raise the flag. And vice versa. Maybe something like if (onFloor) { elevator.move = false }, maybe for a duration of 20 timer "iterations", and keep a count in the elevator class, that will reset back to 0 when the count hits 20, then move will be back at true.
Here's an example you can play with. I was working on it for about 20 minutes then gave up. The logic is a bit off, but it basically points out the ideas i mentioned. Maybe you'll have better luck with it. You can also see a good working example of moving different object here
import java.awt.Dimension;
import java.awt.Graphics;
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.SwingUtilities;
import javax.swing.Timer;
public class ElevatorAnimate extends JPanel {
private static final int D_W = 300;
private static final int FLOORS = 6;
private static final int FLOOR_HEIGHT = 100;
private static final int BUILDING_HEIGHT = FLOORS * FLOOR_HEIGHT;
private static final int D_H = BUILDING_HEIGHT;
private static final int BUILDING_BASE = BUILDING_HEIGHT;
private static final int ELEVATOR_HEIGHT = 60;
private static final int ELEVATOR_WIDTH = 30;
private final List<Elevator> elevators;
public ElevatorAnimate() {
elevators = createElevators();
Timer timer = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (Elevator el : elevators) {
el.move();
}
repaint();
}
});
timer.start();
}
private List<Elevator> createElevators() {
List<Elevator> list = new ArrayList<>();
list.add(new Elevator(6, 30));
list.add(new Elevator(4, 90));
list.add(new Elevator(2, 150));
return list;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawFloors(g);
for (Elevator el : elevators) {
el.drawElevator(g);
}
}
private void drawFloors(Graphics g) {
for (int i = 1; i <= FLOORS; i++) {
g.drawLine(0, FLOOR_HEIGHT * i, D_W, FLOOR_HEIGHT * i);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public class Elevator {
int floor, x, y;
boolean move = false;
boolean up = true;
int stopCount = 0;
public Elevator(int floor, int x) {
this.floor = floor;
y = BUILDING_HEIGHT - (floor * FLOOR_HEIGHT);
this.x = x;
}
public void drawElevator(Graphics g) {
g.fillRect(x, y, ELEVATOR_WIDTH, ELEVATOR_HEIGHT);
}
public void move() {
if (y <= 0) {
up = false;
} else if (y >= BUILDING_BASE + ELEVATOR_HEIGHT) {
up = true;
}
if (isOnFloor()) {
move = false;
}
if (move) {
if (up) {
y -= 2;
} else {
y += 2;
}
} else {
if (stopCount >= 20) {
move = true;
stopCount = 0;
} else {
stopCount++;
}
}
}
private boolean isOnFloor() {
return y / FLOOR_HEIGHT == 100;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new ElevatorAnimate());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Starting from this related Subway simulation, the following variation adds two independent panels, each of which contains its own view and control panel.
// Common initialization for either JApplet or JFrame
private static void initContainer(Container container) {
container.add(createPanel(), BorderLayout.NORTH);
container.add(createPanel(), BorderLayout.SOUTH);
}
private static JPanel createPanel() {
JPanel panel = new JPanel(new BorderLayout());
ButtonPanel control = new ButtonPanel();
SubwayPanel subway = new SubwayPanel(control);
panel.add(subway, BorderLayout.NORTH);
panel.add(control, BorderLayout.SOUTH);
subway.beginOperation();
return panel;
}

add object to JPanel

I don't usually do this, and I hate when other does... But I found my self spending the last 6 hours to figure out where I wrong.
I rewrote those lines dozen of times, change several approaches, and still couldn't figure out what the hake is wrong!
The issue is as followed:
Clicking on the [Add] button should add additional circle to the panel (starting from x=0,y=0 - top left corner).
currently only the first circle is loaded, the rest is loaded to the arrayList but not to the panel.
Any thoughts?
BallControl:
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.event.*;
import java.awt.*;
import java.util.ArrayList;
public class BallControl extends JPanel {
private int delay = 10; // Create a timer with delay 1000 ms
private Timer timer = new Timer(delay, new TimerListener());
private ArrayList<Ball> balls = new ArrayList<Ball>();
private JButton jbtSuspend = new JButton("Suspend");
private JButton jbtResume = new JButton("Resume");
private JButton jbtAdd = new JButton("Add");
private JButton jbtRemove = new JButton("Remove");
private JButton jbtDirection = new JButton("Direction");
private JScrollBar jsbDelay = new JScrollBar();
private JPanel jplBalls = new JPanel(new BorderLayout());
public BallControl() { // Group buttons in a panel
JPanel panel = new JPanel();
panel.add(jbtSuspend);
panel.add(jbtResume);
panel.add(jbtAdd);
panel.add(jbtRemove);
panel.add(jbtDirection);
balls.add(new Ball());
// Add panel-ball and buttons to the panel
jsbDelay.setOrientation(JScrollBar.HORIZONTAL);
setDelay(jsbDelay.getMaximum());
timer.setDelay(delay);
jplBalls.setBorder(new LineBorder(Color.blue));
jplBalls.setVisible(true);
setLayout(new BorderLayout());
add(jsbDelay, BorderLayout.NORTH);
add(jplBalls, BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
timer.start();
jplBalls.add(balls.get(balls.size()-1));
// Register listeners
jbtSuspend.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
timer.stop();
}
});
jbtResume.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
timer.start();
}
});
jbtAdd.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addNewBallToPanel();
if(jbtRemove.isEnabled()==false)
jbtRemove.setEnabled(true);
}
});
jbtRemove.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeBallFromPanel();
if (balls.size()==0)
jbtRemove.setEnabled(false);
}
});
jbtDirection.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for(Ball b : balls)
b.changeDirection();
}
});
jsbDelay.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
setDelay(jsbDelay.getMaximum() - e.getValue());
timer.setDelay(delay);
}
});
}
public void addNewBallToPanel(){
balls.add(new Ball());
jplBalls.add(balls.get(balls.size()-1));
}
public void paintComponent(Graphics g){
super.paintComponent(g);
for (Ball b : balls){
b.paintComponent(g);
}
}
private class TimerListener implements ActionListener {
/** Handle the action event */
public void actionPerformed(ActionEvent e) {
repaint();
}
}
public void removeBallFromPanel(){
jplBalls.remove(balls.get(balls.size()-1));
balls.remove(balls.size()-1);
}
public void setDelay(int delay) {
this.delay = delay;
}
}
Ball:
import javax.swing.Timer;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Ball extends JPanel {
//private int delay = 10; // Create a timer with delay 1000 ms
//private Timer timer = new Timer(delay, new TimerListener());
private int x = 0;
private int y = 0; // Current ball position
private int radius = 5; // Ball radius
private int dx = 2; // Increment on ball's x-coordinate
private int dy = 2; // Increment on ball's y-coordinate
private Color c;
public void setC(Color c) {
this.c = c;
}
public Ball() {
c = new Color((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(c);
if (x < radius)
dx = Math.abs(dx); // Check boundaries
if (x > getWidth() - radius)
dx = -Math.abs(dx);
if (y < radius)
dy = Math.abs(dy);
if (y > getHeight() - radius)
dy = -Math.abs(dy);
x += dx; // Adjust ball position
y += dy;
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
public void changeDirection(){
dx=-dx;
dy=-dy;
}
}
BounceBallApp:
import java.awt.*;
import javax.swing.*;
public class BounceBallApp extends JApplet {
public BounceBallApp() {
add(new BallControl());
}
public void init(){
this.setSize(500, 300);
}
}
Two edits seems to fix problem:
In BallControl
private JPanel jplBalls = new JPanel(null);
jplBalls.setOpaque(false);
In Ball
if (x > getParent().getWidth() - radius)
dx = -Math.abs(dx);
if (y > getParent().getHeight() - radius)
dy = -Math.abs(dy);
But i should suggest you to have one panel and draw balls from array in it's paintComponent method.
Now you have created a sandwich from multiple panels and they are overlap each other.
Another way is make ball a widget, and change it's coordinates.
Swing components use layout managers. You created your balls panel with a BorderLayout. A BorderLayout can only display a single component in the CENTER, which is where you are adding all your Balls.
When you add additional Balls you never revalidate() the panel, so by default all the Balls have a 0 size which means there is nothing to paint. Even if you did revalidate() the panel the only the last panel added with display since by default a panel is opaque so the last ball added would paint over top of the other Balls. So the simple answer to your question is that you probably need to make the Balls non-opaque.
However, that is still not a very good design because you will potentially be adding many Balls to the panel and each ball will be sized at roughly (500, 300). Since all the Balls are non-opaque the painting system will need to do a lot of work to find an opaque background which needs to be painted before all the Balls are painted.
If you want to deal with components. Then each component should be non-opaque and the size of each component should only be the size of the actual oval that you are painting. You would need to use a null layout so you can randomly position every ball on the panel. Then you need to set the size/location of every Ball so that it can be painted properly.
Or another approach is to not use components at all but instead to just keep information about every Ball in an ArrayList and then do custom painting that simply iterates through the ArrayList to paint each Ball.
For an example of this last approach check out the DrawOnComponent example from Custom Painting Approaches. The code is not exactly what you want, but the concept is the same except your user will be clicking a button to add a circle instead of using the mouse.
Your problem is that your Ball is a panel. You really only want the Ball to be a data class that holds the data of how to draw a class. The problem with your code is that you r trying to add a new ball panel when really you only want to add a ball to an existing panel. So the logic from your Ball class, you need to move that to the BallControll. Here's a running example. I tweeked your code. You can get an idea from it, what needs to be fixed.
import javax.swing.Timer;
import java.util.ArrayList;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class BounceBallApp extends JFrame {
public BounceBallApp() {
add(new BallControl());
}
public static void main(String[] args) {
JFrame frame = new BounceBallApp();
frame.setTitle("MultipleBallApp");
frame.setSize(300, 200);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
class BallControl extends JPanel {
private BallPanel ballPanel = new BallPanel();
private JButton jbtSuspend = new JButton("Suspend");
private JButton jbtResume = new JButton("Resume");
private JButton jbtAdd = new JButton("Add");
private JButton jbtSubtract = new JButton("Remove");
private JScrollBar jsbDelay = new JScrollBar();
public BallControl() {
// Group buttons in a panel
JPanel panel = new JPanel();
panel.add(jbtSuspend);
panel.add(jbtResume);
panel.add(jbtAdd);
panel.add(jbtSubtract);
// Add ball and buttons to the panel
ballPanel.setBorder(
new javax.swing.border.LineBorder(Color.red));
jsbDelay.setOrientation(JScrollBar.HORIZONTAL);
ballPanel.setDelay(jsbDelay.getMaximum());
setLayout(new BorderLayout());
add(jsbDelay, BorderLayout.NORTH);
add(ballPanel, BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
// Register listeners
jbtSuspend.addActionListener(new Listener());
jbtResume.addActionListener(new Listener());
jbtAdd.addActionListener(new Listener());
jbtSubtract.addActionListener(new Listener());
jsbDelay.addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
ballPanel.setDelay(jsbDelay.getMaximum() - e.getValue());
}
});
}
class Listener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == jbtSuspend) {
ballPanel.suspend();
} else if (e.getSource() == jbtResume) {
ballPanel.resume();
} else if (e.getSource() == jbtAdd) {
ballPanel.add();
} else if (e.getSource() == jbtSubtract) {
ballPanel.subtract();
}
}
}
}
class BallPanel extends JPanel {
private int delay = 10;
private ArrayList<Ball> list = new ArrayList<Ball>();
// Create a timer with the initial delay
protected Timer timer = new Timer(delay, new ActionListener() {
#Override
/**
* Handle the action event
*/
public void actionPerformed(ActionEvent e) {
repaint();
}
});
public BallPanel() {
timer.start();
}
public void add() {
list.add(new Ball());
}
public void subtract() {
if (list.size() > 0) {
list.remove(list.size() - 1); // Remove the last ball
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < list.size(); i++) {
Ball ball = (Ball) list.get(i); // Get a ball
g.setColor(ball.color); // Set ball color
// Check boundaries
if (ball.x < 0 || ball.x > getWidth()) {
ball.dx = -ball.dx;
}
if (ball.y < 0 || ball.y > getHeight()) {
ball.dy = -ball.dy;
}
// Adjust ball position
ball.x += ball.dx;
ball.y += ball.dy;
g.fillOval(ball.x - ball.radius, ball.y - ball.radius,
ball.radius * 2, ball.radius * 2);
}
}
public void suspend() {
timer.stop();
}
public void resume() {
timer.start();
}
public void setDelay(int delay) {
this.delay = delay;
timer.setDelay(delay);
}
}
class Ball {
int x = 0;
int y = 0; // Current ball position
int dx = 2; // Increment on ball's x-coordinate
int dy = 2; // Increment on ball's y-coordinate
int radius = 5; // Ball radius
Color color = new Color((int) (Math.random() * 256),
(int) (Math.random() * 256), (int) (Math.random() * 256));
}
}
Note: I left the direction part out. Id was confusing me. You can fix that.

What's the easiest way to draw a (monochrome) array using a mouse with Swing?

I've been searching for a way to draw a black-and-white array on screen. It's a simple array, just 20x20. What I plan to do is to draw on an array with the mouse so that each pixel "toggles" from black to white and back when clicked, then pass the array as a set of booleans (or integers) to another function. Currently I'm using Swing. I do remember to have used Swing for drawing on a canvas, but I still can't find the actual usage. Should I use a canvas, or instead rely on JToggleButtons?
You can simply use a JFrame (or other Swing component) and override the paint(Graphics) method to draw a representation of the boolean matrix (note that in the case of a lightweight component such as JPanel you should override paintComponent(Graphics). This will give you the click-and-drag capability you require (which is very difficult to achieve using a grid of individual Swing components).
As other people have commented, AWT Canvas doesn't give you anything not provided by Swing components and you'll see in the example below that I've used the createBufferStrategy method also present on JFrame to ensure a non-flicker display.
Note that my example is fairly simple in that it toggles every pixel you drag across rather than the click operation establishing whether you're in "paint" mode or "erase" mode and then exclusively applying black or white pixels for the duration of the drag.
public class Grid extends JFrame {
private static final int SCALE = 10; // 1 boolean value == 10 x 10 pixels.
private static final int SIZE = 20;
private boolean[][] matrix = new boolean[SIZE][SIZE];
private boolean painting;
private int lastX = -1;
private int lastY = -1;
public Grid() throws HeadlessException {
setPreferredSize(new Dimension(SIZE * SCALE, SIZE * SCALE));
setResizable(false);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBackground(Color.WHITE);
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
painting = true;
tryAdjustValue(e.getPoint());
}
public void mouseReleased(MouseEvent e) {
painting = false;
lastX = -1;
lastY = -1;
}
});
addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
tryAdjustValue(e.getPoint());
}
public void mouseMoved(MouseEvent e) {
tryAdjustValue(e.getPoint());
}
});
}
private void tryAdjustValue(Point pt) {
int newX = pt.x / SCALE;
int newY = pt.y / SCALE;
if (painting && isInRange(newX) && isInRange(newY) && (newX != lastX || newY != lastY)) {
// Only invert "pixel" if we're currently in painting mode, both array indices are valid
// and we're not attempting to adjust the same "pixel" as before (important for drag operations).
matrix[newX][newY] = !matrix[newX][newY];
lastX = newX;
lastY = newY;
repaint();
}
}
private boolean isInRange(int val) {
return val >= 0 && val < SIZE;
}
public void paint(Graphics g) {
super.paint(g);
for (int x=0; x<SIZE; ++x) {
for (int y=0; y<SIZE; ++y) {
if (matrix[x][y]) {
g.fillRect(x * SCALE, y * SCALE, SCALE, SCALE);
}
}
}
}
public static void main(String[] args) {
Grid grid = new Grid();
grid.pack();
grid.setLocationRelativeTo(null);
grid.createBufferStrategy(2);
grid.setVisible(true);
}
}
Why not a simple 20 x 20 grid of JPanel held in a GridLayout(20, 20), and flip the panel's background color if clicked via a MouseListener's mousePressed method. You could hold the panels in a 2D array and query their background color whenever the need arises.
You could also use JLabels for this, but you'd have to remember to turn their opaque properties to true. A JButton would work as well or a JToggleButton, ... the options are almost limitless. I do not recommend though that you use AWT (Canvas) as their's no need to step backwards in functionality since Swing handles this so well.
If you get stuck on this, why not come back and show us your code and we'll better be able to give you more specific help.
Another way to solve this is to use a single JPanel and override its paintComponent method. You could give it an int[][] array to serve as its model, and then in the paintComponent method draw rectangles of whatever color desired based on the state of the model. Then give it a MouseListener that changes the state of the model and calls repaint.
e.g.,
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class BlackWhiteGridPanel extends JPanel {
// can have multiple colors if desired
// public static final Color[] COLORS = {Color.black, Color.red, Color.blue, Color.white};
public static final Color[] COLORS = {Color.black, Color.white};
public static final int SIDE = 20;
private static final int BWG_WIDTH = 400;
private static final int BWG_HEIGHT = BWG_WIDTH;
private int[][] model = new int[SIDE][SIDE]; // filled with 0's.
public BlackWhiteGridPanel() {
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
myMousePressed(e);
}
});
}
private void myMousePressed(MouseEvent e) {
// find relative position of mouse press on grid.
int i = (e.getX() * SIDE) / getWidth();
int j = (e.getY() * SIDE) / getHeight();
int value = model[i][j];
// the model can only hold states allowed by the COLORS array.
// So if only two colors, then value can only be 0 or 1.
value = (value + 1) % COLORS.length;
model[i][j] = value;
repaint();
}
public int[][] getModel() {
// return a copy of model so as not to risk corruption from outside classes
int[][] copy = new int[model.length][model[0].length];
for (int i = 0; i < copy.length; i++) {
System.arraycopy(model[i], 0, copy[i], 0, model[i].length);
}
return copy;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int ht = getHeight();
for (int i = 0; i < model.length; i++) {
for (int j = 0; j < model[i].length; j++) {
Color c = COLORS[model[i][j]];
g.setColor(c);
int x = (i * width) / SIDE;
int y = (j * ht) / SIDE;
int w = ((i + 1) * width) / SIDE - x;
int h = ((j + 1) * ht) / SIDE - y;
g.fillRect(x, y, w, h);
}
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(BWG_WIDTH, BWG_HEIGHT);
}
private static void createAndShowGui() {
BlackWhiteGridPanel mainPanel = new BlackWhiteGridPanel();
JFrame frame = new JFrame("BlackWhiteGrid");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

Categories

Resources