I have a chessboard with 64 JPanels representing each square on the board. The pieces are represented using JLabels which are placed on the JPanels. I am trying to remove all the JLabels off the board. I am confused why this doesn't work:
private void removePieces()
{
for(int i = 0; i < 64; i ++)
{
Component c = chessBoard.getComponent(i);
if(c instanceof JLabel)
{
Container parent = c.getParent();
parent.remove((JLabel)c);
parent.revalidate();
parent.repaint();
}
}
}
chessboard is the big JPanel with the 64 JPanels inside it. After some debugging it looks like the if loop is never being entered. I don't understand why it wouldn't enter the if loop if one of the components is a JLabel?
Looks like your trying to remove your JPanels from your chessboard if they are JLabels (which obviously makes no sense, and is why the if code is never firing). Instead you want to remove the chessBoard's components' JLabel component. Example below.
private void removePieces() {
for(int i = 0; i < 64; i ++) {
if(chessBoard.getComponent(i) instanceof JPanel) {
JPanel c = (JPanel)chessBoard.getComponent(i);
c.removeAll();
c.revalidate();
c.repaint();
}
}
}
I am using removeAll() because I am presuming your JPanels have no other components in them other than the potential JLabels.
Why remove the labels, rather than simply set the icon to null or text to ""?
E.G. using text for the pieces.
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.*;
import javax.swing.border.LineBorder;
class ChessBoard2 {
static ChessMoveMouseListener cmml = new ChessMoveMouseListener();
/** Unicode strings for chess pieces & empty string for blank squares. */
static String[][] pieces = {
{"\u2654", "\u2655", "\u2656", "\u2657", "\u2658", "\u2659"},
{"\u265A", "\u265B", "\u265C", "\u265D", "\u265E", "\u265F"},
{""}
};
static int[] order = new int[]{2, 4, 3, 0, 1, 3, 4, 2};
static int[] pawns = new int[]{5, 5, 5, 5, 5, 5, 5, 5};
static int[] blank = new int[]{0, 0, 0, 0, 0, 0, 0, 0};
static int white = 0;
static int black = 1;
static int space = 2;
public static JLabel getColoredLabel(String string, int color) {
JLabel l = new JLabel(string);
l.setFont(l.getFont().deriveFont(50f));
Color c = (color % 2 == 0 ? Color.WHITE : Color.LIGHT_GRAY);
l.setBackground(c);
l.setOpaque(true);
l.addMouseListener(cmml);
return l;
}
public static void addRowToContainer(
Container c,
int[] order,
int row,
int count) {
for (int ii : order) {
c.add(getColoredLabel(pieces[row][ii], count++));
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
JPanel chessboard = new JPanel(new GridLayout(0, 8, 1, 1));
chessboard.setBackground(Color.BLACK);
chessboard.setBorder(new LineBorder(Color.BLACK));
int count = 0;
// black pieces..
addRowToContainer(chessboard, order, black, count);
addRowToContainer(chessboard, pawns, black, ++count);
// middle squares..
addRowToContainer(chessboard, blank, space, ++count);
addRowToContainer(chessboard, blank, space, ++count);
addRowToContainer(chessboard, blank, space, ++count);
addRowToContainer(chessboard, blank, space, ++count);
// white pieces..
addRowToContainer(chessboard, pawns, white, ++count);
addRowToContainer(chessboard, order, white, ++count);
JOptionPane.showMessageDialog(null, chessboard,
"Click two squares to move from/to",
JOptionPane.INFORMATION_MESSAGE);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
class ChessMoveMouseListener extends MouseAdapter {
String s = null;
#Override
public void mouseClicked(MouseEvent e) {
JLabel l = (JLabel) e.getSource();
if (s == null) {
if (l.getText().trim().length() > 0) {
s = l.getText();
l.setText("");
}
} else {
l.setText(s);
s = null;
}
}
}
Think, when you're doing:
Component c = chessBoard.getComponent(i);
you're getting one of the JPanels, that contains your JLabels. And of course they are not instances of JLabel.
So you need to get JLabel from that JPanel and then remove it.
Related
I am in the middle of designing a game very similar to Flow free. I have run into an issue in my code where I want to be able to click on a circle in the grid, be able to gauge its color, and then check if the color is white. If the color is white, I will then print error, which is a placeholder for now. However, even when I click on non-white tiles, I still see the error message being printed. What Am I doing wrong?
My Code:
import javax.swing.JFrame; //JFrame Class
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Shape;
import java.util.ArrayList;
import javax.swing.JPanel;
import java.awt.geom.Ellipse2D;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class DriverV2 extends JPanel {
static ArrayList<Shape> circles;
static ArrayList<Color> colors;
public DriverV2() {
circles = new ArrayList<Shape>();
colors = new ArrayList<Color>();
colors.add( Color.CYAN);//1
colors.add( Color.BLUE);//2
colors.add( Color.WHITE);//3
colors.add( Color.WHITE);//4
colors.add( Color.GREEN);//5
colors.add( Color.WHITE);//6
colors.add( Color.CYAN);//7
colors.add( Color.WHITE);//8
colors.add( Color.WHITE);//9
colors.add( Color.RED);//10
colors.add( Color.WHITE);//11
colors.add( Color.YELLOW);//12
colors.add( Color.WHITE);//13
colors.add( Color.GREEN);//14
colors.add( Color.WHITE);//15
colors.add( Color.WHITE);//16
colors.add( Color.BLUE);//17
colors.add( Color.WHITE);//18
colors.add( Color.RED);//19
colors.add( Color.WHITE);//20
colors.add( Color.WHITE);//21
colors.add( Color.WHITE);//22
colors.add( Color.WHITE);//23
colors.add( Color.WHITE);//24
colors.add( Color.YELLOW);//25
int rows = 5;
for (int y=0;y< rows;y++)
{
for (int x=0;x<rows;x++)
{
circles.add( new Ellipse2D.Double((x + 1) * 150, (y + 1) *150, 100, 100) );
}
}
}
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
Color foreground = g2d.getColor();
int h = 0;
for (Shape shape : circles)
{
g2d.setColor( colors.get(h));
g2d.draw( shape );
g2d.fill(shape);
h++;
}
}
public static boolean checkLinearity(int index1, int index2){
if((index1%5) == (index2 %5)) { //vertical line check
return true;
}
if(Math.abs(index1-index2) < 5) { //horizantal line check
return true;
}
else {
return false;
}
}
static int counter = 0;
static int index1 = 0;
static int index2 = 0;
public static void main(String[] args)
{
JFrame f = new JFrame("Flow"); //new JFrame
DriverV2 t = new DriverV2();
f.setSize(900,900);//sets size of frame
// f.setLocation(100,50);//sets location of frame
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//sets default close operation
// f.setContentPane(new Panel()); //sets content pane
f.setVisible(true);//makes panel visible
f.add(t);
f.addMouseListener( new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
for (Shape shape: circles) {
if (shape.contains(x,y)) {
counter++;
if(counter % 2 == 1) {
index1 = circles.indexOf(shape)+1;
if(Color.WHITE.equals(colors.get(index1))) {
counter--;
System.out.println("error");
}
}
else{
index2 = circles.indexOf(shape) +1;
if(checkLinearity(index1, index2) == true) {
//I want to be able to draw a rectange here
}
}
}
}
}
});
}
}
Your problem seems another example of why many people (including me) advocate to always add curly braces (even on 1-statement blocks - they might not stay that way):
Have a look at this snippet:
if (shape.contains(x,y))
counter++;
if(counter % 2 == 1) {
...
It doesn't do what you think, i.e. if you hit the shape counter get's increased but the rest of the checks is performed for every shape. That's because to the compiler the code looks like this:
if (shape.contains(x,y))
counter++;
if(counter % 2 == 1) {
...
Thus, when you hit a circle counter will be 1 and hence counter % 2 == 1 will be true for every circle being checked afterwards.
So, add curly braces around the indented code. Additionally you might want to break the loop once you've detected a hit since you're probably not interested in check all the other circles as well.
You might also want to introduce a class Circle which might look like this:
class Circle {
Shape shape;
Color color;
... //additional properties, e.g. position, and methods
public boolean wasHit(int x, int y) {
return shape.contains(x,y);
}
public boolean isValid() {
return Color.WHITE.equals( color );
}
}
Then you'd iterate over those like this:
for( Circle circle : circles ) {
if( circle.wasHit(x, y) {
if( circle.isValid() ) {
//do what you'd need here, e.g. change the shape to a rectangle
} else {
//error
}
//break the loop since other circles can't be hit or we're not interested in those
break;
}
}
Also, in addition to what Thomas mentioned (1+ to his answer) don't add your MouseListener to the JFrame. Add it instead to the JPanel that is doing the drawing, else your x and y positions will be off.
Myself, I would use a grid of JLabels, labels that hold icons. You can even give the labels row and column values as properties that can be retrieved. This would allow easy swapping of Icons if that is your goal, and would allow easy retrieval of which grid cell was pressed.
Some advangages:
If the JLabels are also placed into a List<JLabel> then they can be shuffled easily if you need random placement of colors
Error testing for white is easy. Simply get the icon from the selected JLabel and test for equality with the white icon: if (label.getIcon == whiteIcon).
Again, getting the row/column position of the selected disk is easy since it is an intrinsic property of the JLabel itself and can be retrieved by calling getClientProperty on the JLabel, for example: label1.getClientProperty(ROW);
If you need to check for two mouse presses in a row, save the first mouse press result into a field of the class, here a JLabel called label1.
For example:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.*;
public class DriverV2b extends JPanel {
private static final String ROW = "row";
private static final String COLUMN = "column";
private static final int DIM = 5;
private static final int ICON_WIDTH = 150;
private static final int CIRCLE_WIDTH = 100;
private JLabel[][] labelGrid = new JLabel[DIM][DIM];
private Icon whiteIcon = createIcon(Color.WHITE);
private Icon blueIcon = createIcon(Color.BLUE);
private Icon greenIcon = createIcon(Color.GREEN);
private Icon yellowIcon = createIcon(Color.YELLOW);
private Icon redIcon = createIcon(Color.RED);
private Icon[] coloredIcons = {blueIcon, greenIcon, yellowIcon, redIcon};
private JLabel label1 = null;
public DriverV2b() {
// grid to hold the JLabels
setLayout(new GridLayout(DIM, DIM));
// mouse listener to add to all JLabels
MyMouse myMouse = new MyMouse();
// list of JLabels so we can shuffle them for random placement
List<JLabel> labels = new ArrayList<>();
for (int row = 0; row < labelGrid.length; row++) {
for (int col = 0; col < labelGrid[row].length; col++) {
// initially give all a white icon
labelGrid[row][col] = new JLabel(whiteIcon);
// add mouselistener
labelGrid[row][col].addMouseListener(myMouse);
// client property so we know what row and column the label is on
labelGrid[row][col].putClientProperty(ROW, row);
labelGrid[row][col].putClientProperty(COLUMN, col);
// add JLabel to the array list
labels.add(labelGrid[row][col]);
// add it to the GUI
add(labelGrid[row][col]);
}
}
// shuffle the array list of JLabels, so we can add colored icons randomly
Collections.shuffle(labels);
// for each colored icon in the array, add *two** to the list
for (int i = 0; i < coloredIcons.length; i++) {
int index = 2 * i;
labels.get(index).setIcon(coloredIcons[i]);
labels.get(index + 1).setIcon(coloredIcons[i]);
}
}
private Icon createIcon(Color color) {
int w = ICON_WIDTH;
BufferedImage img = new BufferedImage(w, w, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
// rendering hints used to smooth the image edges
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
int width = CIRCLE_WIDTH;
int x = (ICON_WIDTH - CIRCLE_WIDTH) / 2;
g2.fillOval(x, x, width, width);
g2.dispose();
return new ImageIcon(img);
}
private class MyMouse extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel label = (JLabel) e.getSource();
if (label.getIcon() == whiteIcon) {
System.out.println("error -- ignoring this press");
return;
}
if (label1 == null) {
// first press
label1 = label;
} else {
JLabel label2 = label;
int row1 = (int) label1.getClientProperty(ROW);
int col1 = (int) label1.getClientProperty(COLUMN);
int row2 = (int) label2.getClientProperty(ROW);
int col2 = (int) label2.getClientProperty(COLUMN);
Icon icon1 = label1.getIcon();
Icon icon2 = label2.getIcon();
// just to show that this works:
System.out.printf("Locaions: [%d, %d] [%d, %d]%n",
row1, col1, row2, col2);
if (checkLinearity(row1, col1, row2, col2)) {
// TODO: do something
}
label1 = null; // reset this
}
}
private boolean checkLinearity(int row1, int col1, int row2, int col2) {
// TODO finish this method
return false;
}
}
private static void createAndShowGui() {
DriverV2b mainPanel = new DriverV2b();
JFrame frame = new JFrame("DriverV2b");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
added error testing for whiteIcon press
I have the weirdest bug ever.
I have this puzzle game that moves puzzle pieces (which really are buttons with images attached to them).
Everything worked fine until I tried to change the text of some label (to indicate how many steps the player has done).
Everytime I call someControl.setText("text");, the puzzle pieces that moved are set back to the their first position. I have no idea why, but they just do.
Here's my window:
It consists of two panels, each uses a GridBagLayout.
The main frame uses a gridBagLayout as well, which consists of the two panels.
I know it's weird as hell, but I can't figure out what may cause this GUI bug. Any idea?
The pieces of code:
increaseSteps which is called everytime I click a puzzle button
void increaseSteps() {
_steps++;
_lblSteps.setText("Steps: " + _steps);
}
Creation of the puzzle panel (the left panel)
private JPanel puzzlePanel() {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
for (int i = 0; i < _splitImage.getSize(); i++)
for (int j = 0; j < _splitImage.getSize(); j++) {
int valueAtPos = _board.getMatrix()[i][j];
if (valueAtPos == 0)
continue;
int imageRow = _board.getImageRowFromValue(valueAtPos);
int imageCol = _board.getImageColFromValue(valueAtPos);
ImageIcon imageIcon = new ImageIcon(_splitImage.getImages()[imageRow][imageCol]);
JButton btn = new JButton(imageIcon);
_tileButtons[i][j] = new TileButton(btn, i, j);
btn.setPreferredSize(new Dimension(_splitImage.getImages()[i][j].getWidth(null),
_splitImage.getImages()[i][j].getHeight(null)));
// add action listener
btn.addActionListener(this);
btn.addKeyListener(this);
gbc.gridx = j;
gbc.gridy = i;
panel.add(_tileButtons[i][j].getButton(), gbc);
}
return panel;
}
actionPerformed:
#Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JButton))
return;
JButton btn = (JButton) e.getSource();
TileButton tile = getTileButtonFromBtn(btn);
if (tile == null)
return;
// check if we can move the tile
String moveDir = _board.canMoveTile(tile.getRow(), tile.getCol());
if (moveDir.equals("no"))
return;
increaseSteps();
int dirx = 0;
int diry = 0;
if (moveDir.equals("left")) {
dirx = -1;
_board.move("left", true);
tile.setCol(tile.getCol() - 1);
} else if (moveDir.equals("right")) {
dirx = 1;
_board.move("right", true);
tile.setCol(tile.getCol() + 1);
} else if (moveDir.equals("up")) {
diry = -1;
_board.move("up", true);
tile.setRow(tile.getRow() - 1);
} else { // down
diry = 1;
_board.move("down", true);
tile.setRow(tile.getRow() + 1);
}
moveButton(btn, dirx, diry, MOVE_SPEED);
if (_board.hasWon())
win();
}
moveButton: (moves the button in a seperate thread, calling btn.setLocation())
private void moveButton(JButton btn, int dirx, int diry, int speed) {
Point loc = btn.getLocation();
// get start ticks, calculate distance etc...
StopWatch stopper = new StopWatch();
int distance;
if (dirx != 0)
distance = _splitImage.getImages()[0][0].getWidth(null) * dirx;
else
distance = _splitImage.getImages()[0][0].getHeight(null) * diry;
if (speed > 0) {
// run the animation in a new thread
Thread thread = new Thread() {
public void run() {
int currentTicks;
int elapsed;
do {
int newX = loc.x;
int newY = loc.y;
elapsed = stopper.getElapsed();
int moved = (int) ((double) distance * (double) (elapsed / (double) speed));
if (dirx != 0)
newX += moved;
else
newY += moved;
btn.setLocation(newX, newY);
} while (elapsed <= MOVE_SPEED);
// make sure the last location is exact
btn.setLocation(loc.x + (dirx == 0 ? 0 : distance), loc.y + (diry == 0 ? 0 : distance));
}
};
thread.start();
}
else
btn.setLocation(loc.x + (dirx == 0 ? 0 : distance), loc.y + (diry == 0 ? 0 : distance));
}
You're trying to set the absolute position of a component via setLocation(...) or setBounds(...), one that is held by a container that uses a layout manager. This may work temporarily, but will fail if the container's layout manager is triggered to re-do the layout of its contained components. When that happens, the GridBagConstraints will take over and the components will move to their gridbag constraints assigned location.
The solution is to not do this, and instead to place the location of your components in concert with the layout managers used.
Another problem is that your current code is not Swing thread-safe since you're making Swing state changes from within a background thread. This won't always cause problems, but since it's a threading issue, risks causing intermittent hard to debug problems (ones that usually only occur when your boss or instructor are trying to run your code).
Possible solutions:
For a grid of images, you could use a grid of JLabels (or JButtons if you must) held in a container that uses GridLayout. When you need to reposition components, remove all components held by that JPanel, and then re-add, using the order of addition to help you position the components.
Easiest though would be to use a grid of non-moving JLabels, give them MouseListeners, and instead of moving the JLabels, remove and add Icons to them, including a blank Icon.
If you need to do Swing animation, use a Swing Timer to drive the animation. This will allow your code to make repetitive calls with delay between the calls, and with these calls being made on the Swing event thread, the EDT (event dispatch thread).
Demo proof of concept example code that shows swapping icons, but without animation, and without test of solution yet:
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class ImageShuffle extends JPanel {
private static final int SIDES = 3;
public static final String IMG_PATH = "https://upload.wikimedia.org/wikipedia/commons/"
+ "thumb/5/5a/Hurricane_Kiko_Sep_3_1983_1915Z.jpg/"
+ "600px-Hurricane_Kiko_Sep_3_1983_1915Z.jpg";
private List<Icon> iconList = new ArrayList<>(); // shuffled icons
private List<Icon> solutionList = new ArrayList<>(); // in order
private List<JLabel> labelList = new ArrayList<>(); // holds JLabel grid
private Icon blankIcon;
public ImageShuffle(BufferedImage img) {
setLayout(new GridLayout(SIDES, SIDES, 1, 1));
fillIconList(img); // fill array list with icons and one blank one
Collections.shuffle(iconList);
MyMouseListener myMouse = new MyMouseListener();
for (Icon icon : iconList) {
JLabel label = new JLabel(icon);
label.addMouseListener(myMouse);
add(label);
labelList.add(label);
}
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel selectedLabel = (JLabel) e.getSource();
if (selectedLabel.getIcon() == blankIcon) {
return; // don't want to move the blank icon
}
// index variables to hold selected and blank JLabel's index location
int selectedIndex = -1;
int blankIndex = -1;
for (int i = 0; i < labelList.size(); i++) {
if (selectedLabel == labelList.get(i)) {
selectedIndex = i;
} else if (labelList.get(i).getIcon() == blankIcon) {
blankIndex = i;
}
}
// get row and column of selected JLabel
int row = selectedIndex / SIDES;
int col = selectedIndex % SIDES;
// get row and column of blank JLabel
int blankRow = blankIndex / SIDES;
int blankCol = blankIndex % SIDES;
if (isMoveValid(row, col, blankRow, blankCol)) {
Icon selectedIcon = selectedLabel.getIcon();
labelList.get(selectedIndex).setIcon(blankIcon);
labelList.get(blankIndex).setIcon(selectedIcon);
// test for win here by comparing icons held by labelList
// with the solutionList
}
}
private boolean isMoveValid(int row, int col, int blankRow, int blankCol) {
// has to be on either same row or same column
if (row != blankRow && col != blankCol) {
return false;
}
// if same row
if (row == blankRow) {
// then columns must be off by 1 -- they're next to each other
return Math.abs(col - blankCol) == 1;
} else {
// or else rows off by 1 -- above or below each other
return Math.abs(row - blankRow) == 1;
}
}
public void shuffle() {
Collections.shuffle(iconList);
for (int i = 0; i < labelList.size(); i++) {
labelList.get(i).setIcon(iconList.get(i));
}
}
}
private void fillIconList(BufferedImage img) {
// get the width and height of each individual icon
// which is 1/3 the image width and height
int w = img.getWidth() / SIDES;
int h = img.getHeight() / SIDES;
for (int row = 0; row < SIDES; row++) {
int y = (row * img.getWidth()) / SIDES;
for (int col = 0; col < SIDES; col++) {
int x = (col * img.getHeight()) / SIDES;
// create a sub image
BufferedImage subImg = img.getSubimage(x, y, w, h);
// create icon from the image
Icon icon = new ImageIcon(subImg);
// add to both icon lists
iconList.add(icon);
solutionList.add(icon);
}
}
// create a blank image and corresponding icon as well.
BufferedImage blankImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
blankIcon = new ImageIcon(blankImg);
iconList.remove(iconList.size() - 1); // remove last icon from list
iconList.add(blankIcon); // and swap in the blank one
solutionList.remove(iconList.size() - 1); // same for the solution list
solutionList.add(blankIcon);
}
private static void createAndShowGui(BufferedImage img) {
JFrame frame = new JFrame("ImageShuffle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ImageShuffle(img));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
URL imgUrl = null;
BufferedImage img;
try {
imgUrl = new URL(IMG_PATH);
img = ImageIO.read(imgUrl);
SwingUtilities.invokeLater(() -> createAndShowGui(img));
} catch (IOException e) {
e.printStackTrace();
}
}
}
If I wanted animation, again, I'd raise the icon into the JFrame's glasspane, animate it to the new position using a Swing Timer, and then place the icon into the new JLabel. I'd also disable the MouseListener using a boolean field, a "flag", until the animation had completed its move.
I have a Board 14x14 which has JButtons and every Jbutton has a different color. When you click one of those buttons, it checks the neighbors with the same color and removes them. When it removes them, theres a blank space between the board so the above buttons, should move down to fill the blank space. I tried with GridLayout but I don't know how to move the above buttons.
This actually is a case where you can hardly use a layout manager at all.
A LayoutManager is supposed to compute the layout of all components at once. It is triggered by certain events (e.g. when the parent component is resized). Then it computes the layout and arranges the child components accordingly.
In your case, the situation is quite different. There is no layout manager that can sensibly represent the "intermediate" state that appears while the upper buttons are falling down. While the components are animated, they cannot be part of a proper layout.
The animation itself may also be a bit tricky, but can fortunately be solved generically. But you still have to keep track of the information about where each component (i.e. each button) is currently located in the grid. When one button is removed, you have to compute the buttons that are affected by that (namely, the ones directly above it). These have to be animated. After the animation, you have to assign the new grid coordinates to these buttons.
The following is a MCVE that shows one basic approach. It simply removes the button that was clicked, but it should be easy to generalize it to remove other buttons, based on other conditions.
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class FallingButtons
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int rows = 8;
int cols = 8;
GridPanel gridPanel = new GridPanel(rows, cols);
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
JButton button = new JButton(r+","+c);
gridPanel.addComponentInGrid(r, c, button);
button.addActionListener(e ->
{
Point coordinates = gridPanel.getCoordinatesInGrid(button);
if (coordinates != null)
{
gridPanel.removeComponentInGrid(
coordinates.x, coordinates.y);
}
});
}
}
f.getContentPane().add(gridPanel);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class GridPanel extends JPanel
{
private final int rows;
private final int cols;
private final JComponent components[][];
GridPanel(int rows, int cols)
{
super(null);
this.rows = rows;
this.cols = cols;
this.components = new JComponent[rows][cols];
addComponentListener(new ComponentAdapter()
{
#Override
public void componentResized(ComponentEvent e)
{
layoutGrid();
}
});
}
private void layoutGrid()
{
int cellWidth = getWidth() / cols;
int cellHeight = getHeight() / rows;
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
JComponent component = components[r][c];
if (component != null)
{
component.setBounds(
c * cellWidth, r * cellHeight, cellWidth, cellHeight);
}
}
}
}
Point getCoordinatesInGrid(JComponent component)
{
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
if (components[r][c] == component)
{
return new Point(r, c);
}
}
}
return null;
}
void addComponentInGrid(int row, int col, JComponent component)
{
add(component);
components[row][col] = component;
layoutGrid();
}
JComponent getComponentInGrid(int row, int col)
{
return components[row][col];
}
void removeComponentInGrid(int row, int col)
{
remove(components[row][col]);
components[row][col] = null;
List<Runnable> animations = new ArrayList<Runnable>();
for (int r=row-1; r>=0; r--)
{
JComponent component = components[r][col];
if (component != null)
{
Runnable animation =
createAnimation(component, r, col, r + 1, col);
animations.add(animation);
}
}
for (Runnable animation : animations)
{
Thread t = new Thread(animation);
t.setDaemon(true);
t.start();
}
repaint();
}
private Runnable createAnimation(JComponent component,
int sourceRow, int sourceCol, int targetRow, int targetCol)
{
int cellWidth = getWidth() / cols;
int cellHeight = getHeight() / rows;
Rectangle sourceBounds = new Rectangle(
sourceCol * cellWidth, sourceRow * cellHeight,
cellWidth, cellHeight);
Rectangle targetBounds = new Rectangle(
targetCol * cellWidth, targetRow * cellHeight,
cellWidth, cellHeight);
Runnable movement = createAnimation(
component, sourceBounds, targetBounds);
return () ->
{
components[sourceRow][sourceCol] = null;
movement.run();
components[targetRow][targetCol] = component;
repaint();
};
}
private static Runnable createAnimation(JComponent component,
Rectangle sourceBounds, Rectangle targetBounds)
{
int delayMs = 10;
int steps = 20;
Runnable r = () ->
{
int x0 = sourceBounds.x;
int y0 = sourceBounds.y;
int w0 = sourceBounds.width;
int h0 = sourceBounds.height;
int x1 = targetBounds.x;
int y1 = targetBounds.y;
int w1 = targetBounds.width;
int h1 = targetBounds.height;
int dx = x1 - x0;
int dy = y1 - y0;
int dw = w1 - w0;
int dh = h1 - h0;
for (int i=0; i<steps; i++)
{
double alpha = (double)i / (steps - 1);
int x = (int)(x0 + dx * alpha);
int y = (int)(y0 + dy * alpha);
int w = (int)(w0 + dw * alpha);
int h = (int)(h0 + dh * alpha);
SwingUtilities.invokeLater(() ->
{
component.setBounds(x, y, w, h);
});
try
{
Thread.sleep(delayMs);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
SwingUtilities.invokeLater(() ->
{
component.setBounds(x1, y1, w1, h1);
});
};
return r;
}
}
You could try using a 2-dimensional array of JButtons
JButton[][] buttons = new JButton[14][14];
for (int i=0; i < buttons.length; i++) {
for (int j=0; j < buttons[i].length; j++) {
buttons[i][j] = new JButton("Button [" + i + "][" + j + "]");
}
}
// Then do whatever,remove,change color,check next element in array
// and compare colors etc
buttons[2][3].setText("changed text");
If you want the above buttons to take more space to fill the empty space when you remove a component well, this is not possible using GridLayout, but you can add some empty components like JLabels to fill the space.
You can add a component in a container at a specific index for this purpose, by using Container's add (Component comp, int index) method.
This code snippet will replace a button at a specified index (45, just for example) with a blank component in a panel which has a GridLayout set:
JPanel boardPanel = new JPanel (new GridLayout (14, 14));
// ... add your buttons ...
// This code could be invoked inside an ActionListener ...
boardPanel.remove (45);
boardPanel.add (new JLabel (""), 45);
boardPanel.revalidate ();
boardPanel.repaint ();
This way, the rest of the components will not move, and you will just see a blank space replacing your button.
You can achieve more: if you add the empty label at index = 0, all the buttons will move to the right (remember that the number of components should not change, else the components will resize and you could obtain bad behaviour), and so on, you can "move" a single component by simply removing it and adding it at a different index.
Another way to go would be to store a 2-dimensional array of objects representing your model logic (you can store color and all the stuff you need), and painting them on your own by overriding paintComponent method.
For an example of a custom painting approach, take a look at this MadProgrammer's answer, where he shows how to highlight a specific cell in a grid (in this case he uses a List to store objects, but a 2d array will work as well).
I have created a program to add an ImageIcon to the next empty JLabel, the compiler shows no errors so why isn't this working? The setEmptySpace contains a loop that returns an empty space and the getCar method is supposed to set the icon.
package CarPark;
//add imports
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Park extends JPanel
{
//declare a JLabel array for parking space
JLabel[] parkingSpace;
//declare ImageIcon class for cars
ImageIcon car;
public Park()
{
//create a new instance of the parking space array with 12 spaces
parkingSpace = new JLabel[12];
//populate array
for (int i = 0; i < parkingSpace.length; i++)
{
//create parking space JLabels
//parkingSpace[i] = new JLabel(String.valueOf(i + 1));
parkingSpace[i] = new JLabel();
//set parking space colour
parkingSpace[i].setBackground(new Color(85, 55, 170));
//make JLabels opaque
parkingSpace[i].setOpaque(true);
}
//create grid for parking space setting rows, columns, horizontal gap and vertical gap
this.setLayout(new GridLayout(4, 3, 4, 4));
//set background colour to white
this.setBackground(new Color(255, 255, 255));
//add 12 parking spaces to panel
for (int i = 0; i < 12; i++)
{
this.add(parkingSpace[i]);
//create a red border with a thickness of 4 pixels
parkingSpace[i].setBorder(BorderFactory.createLineBorder(Color.RED, 4));
}
}
//find an empty parking space
int setEmptySpace()
{
for(int i = 0; i < parkingSpace.length; i++)
{
if(parkingSpace[i] == null)
{
return i;
}
}
return -1;
}
//add car image to empty parking space
void getCar(int getEmptySpace)
{
car = new ImageIcon("car.png");
parkingSpace[getEmptySpace].setIcon(car);
}
}
Based on this from your constructor...
for (int i = 0; i < parkingSpace.length; i++) {
//create parking space JLabels
//parkingSpace[i] = new JLabel(String.valueOf(i + 1));
parkingSpace[i] = new JLabel();
//set parking space colour
parkingSpace[i].setBackground(new Color(85, 55, 170));
//make JLabels opaque
parkingSpace[i].setOpaque(true);
}
It's impossible for this...
int setEmptySpace() {
for (int i = 0; i < parkingSpace.length; i++) {
if (parkingSpace[i] == null) {
return i;
}
}
return -1;
}
to return anything other than -1, because every parkingSpace contains a JLabel
What I think you want to do, is check to see if the JLabel's icon property is null, for example
int setEmptySpace() {
for (int i = 0; i < parkingSpace.length; i++) {
if (parkingSpace[i].getIcon() == null) {
return i;
}
}
return -1;
}
I've tried simplifying my code into more methods that only do one thing each instead of trying to do multiple tasks per method. What I want to know is does the setCarIconToLabel() actually set the icon to a JLabel and if it doesn't how could it be altered. Also is this method the correct type since I don't think it should be void but I don't know what else to set it to?
//find an empty parking space and return it's number
int emptySpaceNo()
{
for(int i = 0; i < parkingSpace.length; i++)
{
System.out.println("Test method 1 Park P 1");
if(parkingSpace[i].getIcon() == null)
{
System.out.println("Test method 1 P 2");
return i;
}
}
return -1;
}
//return JLabel that is null
JLabel findEmptySpace()
{
return parkingSpace[emptySpaceNo()];
}
//create new car image icon
ImageIcon setCarIcon()
{
ImageIcon carIcon = new ImageIcon("car.png");
return carIcon;
}
//set car icon to JLabel parking space
void setCarIconToLabel()
{
findEmptySpace().setIcon(setCarIcon());
}
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
I don't have much experienc with swing/awt.
My problem is:
I need to draw something like chessboard (NxN).
In general I need to get access to every cell to make changes (when program is running e.g. I click button and something is happen with cells on that board).
It will be greate if a Component let me to set in cell a Image.
I try to use GridLayout but I get nothing what satisfy me.
Do you have any idea how to simply resolve that problem?
I made a Chess game myself in Java back sometime and found the code I used.
So here is something to start you off:
ChessBoardTest:
public class ChessBoardTest {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
try {
Image blackBlock=ImageIO.read(new File("c:/bblock.jpg"));
Image whiteBlock=ImageIO.read(new File("c:/wblock.jpg"));
Board board = new Board(whiteBlock,blackBlock);
//add pieces to board
board.addPiece(new ImageIcon("c:/castle.jpg"), "A1");//just one example
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Board.java:
import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/*
* #author David Kroukamp
*/
public class Board extends JFrame {
//intialize variables
private Image boardImage1;
private Image boardImage2;
//intialize components
private JPanel centerPanel = new JPanel();
private JPanel southPanel = new JPanel();
private JPanel westPanel = new JPanel();
//initialze arrays to hold panels and images of the board
private JLabel[] labels = new JLabel[64];
private ImagePanel[] panels = new ImagePanel[64];
public Board(Image boardImage1, Image boardImage2) {
this.boardImage1 = boardImage1;
this.boardImage2 = boardImage2;
createAndShowGUI();//call method to create gui
}
private void createAndShowGUI() {
setTitle("Chess board example");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
addComponentsToPane(getContentPane());
setSize(800, 600);
setLocationRelativeTo(null);
setVisible(true);
}
/**
* Adds all the necessary components to the content pane of the JFrame, and
* adds appropriate listeners to components.
*/
private void addComponentsToPane(Container contentPane) {
GridLayout gridLayout = new GridLayout(8, 8);
centerPanel.setLayout(gridLayout);
//call mehod to add labels to south panel
addLabelsToSouthPanel();
//call method to add oanels to west panel
addLabelsToWestPanel();
//call method to add panels and labels to the center panel which holds the board
addPanelsAndLabels();
//add all panels to frame
contentPane.add(centerPanel, BorderLayout.CENTER);
contentPane.add(southPanel, BorderLayout.SOUTH);
contentPane.add(westPanel, BorderLayout.WEST);
}
private void addLabelsToSouthPanel() {
GridLayout gridLayout = new GridLayout(0, 8);
southPanel.setLayout(gridLayout);
JLabel[] lbls = new JLabel[8];
String[] label = {"A", "B", "C", "D", "E", "F", "G", "H"};
for (int i = 0; i < 8; i++) {
lbls[i] = new JLabel(label[i] + "");
southPanel.add(lbls[i]);
}
}
private void addLabelsToWestPanel() {
GridLayout gridLayout = new GridLayout(8, 0);
westPanel.setLayout(gridLayout);
JLabel[] lbls = new JLabel[8];
int[] num = {8, 7, 6, 5, 4, 3, 2, 1};
for (int i = 0; i < 8; i++) {
lbls[i] = new JLabel(num[i] + "");
westPanel.add(lbls[i]);
}
}
private void addPanelsAndLabels() {
//call methd to create panels with backgound images and appropriate names
addPanelsAndImages();
for (int i = 0; i < panels.length; i++) {
labels[i] = new JLabel();
//used to know the postion of the label on the board
labels[i].setName(panels[i].getName());
panels[i].add(labels[i]);
//adds panels created in addPanelsAndImages()
centerPanel.add(panels[i]);
}
}
//this method will create panels with backround images of chess board and set its name according to 1-8 for rows and A-H for coloumns
private void addPanelsAndImages() {
int count = 0;
String[] label = {"A", "B", "C", "D", "E", "F", "G", "H"};
int[] num = {8, 7, 6, 5, 4, 3, 2, 1};
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if ((col + row) % 2 == 0) {//even numbers get white pieces
panels[count] = new ImagePanel(boardImage1);
} else {//odd numbers get black pieces
panels[count] = new ImagePanel(boardImage2);
}
panels[count].setName(label[col] + num[row]);
count++;
}
}
}
//method sets image of a label at a certain position in the board according to the block name i.e D4
public void addPiece(ImageIcon img, String block) {
for (int s = 0; s < labels.length; s++) {
if (labels[s].getName().equalsIgnoreCase(block)) {
labels[s].setIcon(img);
}
}
}
//nested class used to set the background of frame contenPane
class ImagePanel extends JPanel {
private Image image;
/**
* Default constructor used to set the image for the background for the
* instance
*/
public ImagePanel(Image img) {
image = img;
}
#Override
protected void paintComponent(Graphics g) {
//draws image to background to scale of frame
g.drawImage(image, 0, 0, null);
}
}
}
HTH to get you started.
There is actually quite a reasonable example online on roseindia.