I am trying to create a grid comprised of 100 squares. My code below is extremely buggy and I am not sure why.
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
public class snake extends JFrame
{
public static void main(String[] args)
{
Border whiteLine = BorderFactory.createLineBorder(Color.white);
//-----------FRAME
JFrame frame = new JFrame();
frame.setSize(1000,1000);
frame.setTitle("Snake");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.getContentPane().setBackground(Color.black);
frame.setVisible(true);
frame.setLayout(new GridLayout(10,10));
//-----------FRAME
//-----------PANELS
Dimension panelDimension = new Dimension(20,20);
int counter = 0;
JPanel[][] p = new JPanel[10][10];
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
p[i][j] = new JPanel();
//p[i][j].setPreferredSize(panelDimension);
p[i][j].setBackground(Color.red);
//p[i][j].setLocation(490,490);
p[i][j].setBorder(whiteLine);
p[i][j].setVisible(true);
frame.getContentPane().add(p[i][j]);
counter+=1;
}
}
System.out.println("counter: " + counter);
}
}
When I run the code like this it shows a grid comprised of 2 columns the first column has 7 rows and the second column has 6. Sometimes it even shows other incorrect numbers of columns and rows. I am not sure why it doesn't create a grid of 10 rows 10 columns.
You've got several problems including:
Calling setVisible(true) on the JFrame before adding components, before calling pack() on the top-level window. This can lead to wonky positioned components within our GUI's or even GUI's that remain empty
Not calling pack() on the JFrame after adding components and before setting it visible
Setting the size of the JFrame. Let the layout managers, containers and components do this for you (which is what calling pack() is for)
Setting it to a bad size, a "perfect square", one that ignores the menu bar that the OS adds,
...
For example:
package foo01;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class SnakePanel extends JPanel {
private static final int CELL_WIDTH = 80;
private static final Dimension CELL_DIMENSION = new Dimension(CELL_WIDTH, CELL_WIDTH);
private static final int COLUMNS = 10;
private static final int GAP = 2;
private static final Color BG_COLOR = Color.WHITE;
private static final Color CELL_COLOR = Color.RED;
public SnakePanel() {
setBackground(BG_COLOR);
// add a white line around the grid
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
// create a grid with gaps that show the background (white) color
setLayout(new GridLayout(COLUMNS, COLUMNS, GAP, GAP));
for (int row = 0; row < COLUMNS; row++) {
for (int col = 0; col < COLUMNS; col++) {
JPanel cell = new JPanel(); // create a new cell
cell.setPreferredSize(CELL_DIMENSION); // cheating here. Better to override getPreferredSize()
cell.setBackground(CELL_COLOR);
add(cell);
// give the cell JPanel some simple behavior:
cell.addMouseListener(new MyMouse(col, row));
}
}
}
private class MyMouse extends MouseAdapter {
private int col;
private int row;
public MyMouse(int col, int row) {
this.col = col;
this.row = row;
}
#Override
public void mousePressed(MouseEvent e) {
System.out.printf("Mouse pressed row and column: [%d, %d]%n", row, col);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// create the main JPanel
SnakePanel snakePanel = new SnakePanel();
// create the JFrame
JFrame frame = new JFrame("Snake");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// add the main JPanel to the JFrame
frame.add(snakePanel);
// pack the JFrame -- tells the layout managers to do their things
frame.pack();
// if we want to center the GUI:
frame.setLocationRelativeTo(null);
// only *now* do we display the GUI
frame.setVisible(true);
});
}
}
Some notes on the code:
Any code within the Runnable passed into the SwingUtilities.invokeLater(...) method is called on the Swing event thread, which is a wise thing to do when creating a Swing GUI
SwingUtilities.invokeLater(() -> {
// ....
});
First, create the main JPanel that is held by the JFrame:
SnakePanel snakePanel = new SnakePanel();
Then create the JFrame, add that JPanel and call pack(). The pack call tells the layout managers to do there thing, to lay out components within containers, to size things based on their preferred sizes and their layouts:
JFrame frame = new JFrame("Snake");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(snakePanel);
frame.pack();
if we want to center the GUI:
frame.setLocationRelativeTo(null);
only now do we display the GUI
frame.setVisible(true);
I am trying to hide a JSplitPane with animation. By hide, I mean to setDividerLocation(0) so its left component is invisible (technically it is visible, but with zero width):
public class SplitPaneTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60));
for (int i = 0; i < 60 * 60; i++) {
// rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
timer.stop();
});
timer.start();
}
}
If you run it, will see that everything seems good, and the animation runs smooth.
However, in the real application the right of the JSplitPane is a JPanel with CardLayout and each card has a lot of components.
If you uncomment this line in order to simulate the number of components:
// rightPanel.add(new JLabel("s"));
and re-run the above example, you will see that the animation no longer runs smoothly. So, the question is, is is possible to make it smooth(-ier)?
I have no idea how to approach a solution - if any exists.
Based on my research, I registered a global ComponentListener:
Toolkit.getDefaultToolkit()
.addAWTEventListener(System.out::println, AWTEvent.COMPONENT_EVENT_MASK);
and saw the tons of events that are being fired. So, I think the source of the problem is the tons of component events that are being fired for each component. Also, it seems that components with custom renderers (like JList - ListCellRenderer and JTable - TableCellRenderer), component events are firing for all of the renderers. For example, if a JList has 30 elements, 30 events (component) will be fired only for it. It also seems (and that's why I mentioned it) that for CardLayout, events are taking place for the "invisible" components as well.
I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.
I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.
The layout manager is invoked every time the divider location is changed which would add a lot of overhead.
One solution might be to stop invoking the layout manager as the divider is animating. This can be done by overriding the doLayout() method of the right panel:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SplitPaneTest2 {
public static boolean doLayout = true;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60))
{
#Override
public void doLayout()
{
if (SplitPaneTest2.doLayout)
super.doLayout();
}
};
for (int i = 0; i < 60 * 60; i++) {
rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
SplitPaneTest2.doLayout = false;
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
{
timer.stop();
SplitPaneTest2.doLayout = true;
splitPane.getRightComponent().revalidate();
}
});
timer.start();
}
}
Edit:
I was not going to include my test on swapping out the panel full of components with a panel that uses an image of components since I fell the animation is the same, but since it was suggested by someone else here is my attempt for your evaluation:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;
public class SplitPaneTest2 {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60));
for (int i = 0; i < 60 * 60; i++) {
rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
Component right = splitPane.getRightComponent();
Dimension size = right.getSize();
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
right.paint( g );
g.dispose();
JLabel label = new JLabel( new ImageIcon( bi ) );
label.setHorizontalAlignment(JLabel.LEFT);
splitPane.setRightComponent( label );
splitPane.setDividerLocation( splitPane.getDividerLocation() );
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
{
timer.stop();
splitPane.setRightComponent( right );
}
});
timer.start();
}
}
#GeorgeZ. I think the concept presented by #camickr has to do with when you actually do the layout. As an alternative to overriding doLayout, I would suggest subclassing the GridLayout to only lay out the components at the end of the animation (without overriding doLayout). But this is the same concept as camickr's.
Although if the contents of your components in the right panel (ie the text of the labels) remain unchanged during the animation of the divider, you can also create an Image of the right panel when the user clicks the button and display that instead of the actual panel. This solution, I would imagine, involves:
A CardLayout for the right panel. One card has the actual rightPanel contents (ie the JLabels). The second card has only one JLabel which will be loaded with the Image (as an ImageIcon) of the first card.
As far as I know, by looking at the CardLayout's implementation, the bounds of all the child components of the Container are set during layoutContainer method. That would probably mean that the labels would be layed out inspite being invisible while the second card would be shown. So you should probably combine this with the subclassed GridLayout to lay out only at the end of the animation.
To draw the Image of the first card, one should first create a BufferedImage, then createGraphics on it, then call rightPanel.paint on the created Graphics2D object and finally dispose the Graphics2D object after that.
Create the second card such that the JLabel would be centered in it. To do this, you just have to provide the second card with a GridBagLayout and add only one Component in it (the JLabel) which should be the only. GridBagLayout always centers the contents.
Let me know if such a solution could be useful for you. It might not be useful because you could maybe want to actually see the labels change their lay out profile while the animation is in progress, or you may even want the user to be able to interact with the Components of the rightPanel while the animation is in progress. In both cases, taking a picture of the rightPanel and displaying it instead of the real labels while the animation takes place, should not suffice. So it really depends, in this case, on how dynamic will be the content of the rightPanel. Please let me know in the comments.
If the contents are always the same for every program run, then you could probably pre-create that Image and store it. Or even, a multitude of Images and store them and just display them one after another when the animation turns on.
Similarly, if the contents are not always the same for every program run, then you could also subclass GridLayout and precalculate the bounds of each component at startup. Then that would make GridLayout a bit faster in laying out the components (it would be like encoding a video with the location of each object), but as I am testing it, GridLayout is already fast: it just calculates about 10 variables at the start of laying out, and then imediately passes over to setting the bounds of each Component.
Edit 1:
And here is my attempt of my idea (with the Image):
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.IntBinaryOperator;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SplitPaneTest {
//Just a Timer which plays the animation of the split pane's divider going from side to side...
public static class SplitPaneAnimationTimer extends Timer {
private final JSplitPane splitPane;
private int speed, newDivLoc;
private IntBinaryOperator directionf;
private Consumer<SplitPaneAnimationTimer> onFinish;
public SplitPaneAnimationTimer(final int delay, final JSplitPane splitPane) {
super(delay, null);
this.splitPane = Objects.requireNonNull(splitPane);
super.setRepeats(true);
super.setCoalesce(false);
super.addActionListener(e -> {
splitPane.setDividerLocation(directionf.applyAsInt(newDivLoc, splitPane.getDividerLocation() + speed));
if (newDivLoc == splitPane.getDividerLocation()) {
stop();
if (onFinish != null)
onFinish.accept(this);
}
});
speed = 0;
newDivLoc = 0;
directionf = null;
onFinish = null;
}
public int getSpeed() {
return speed;
}
public JSplitPane getSplitPane() {
return splitPane;
}
public void play(final int newDividerLocation, final int speed, final IntBinaryOperator directionf, final Consumer<SplitPaneAnimationTimer> onFinish) {
if (newDividerLocation != splitPane.getDividerLocation() && Math.signum(speed) != Math.signum(newDividerLocation - splitPane.getDividerLocation()))
throw new IllegalArgumentException("Speed needs to be in the direction towards the newDividerLocation (from the current position).");
this.directionf = Objects.requireNonNull(directionf);
newDivLoc = newDividerLocation;
this.speed = speed;
this.onFinish = onFinish;
restart();
}
}
//Just a GridLayout subclassed to only allow laying out the components only if it is enabled.
public static class ToggleGridLayout extends GridLayout {
private boolean enabled;
public ToggleGridLayout(final int rows, final int cols) {
super(rows, cols);
enabled = true;
}
#Override
public void layoutContainer(final Container parent) {
if (enabled)
super.layoutContainer(parent);
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
}
//How to create a BufferedImage (instead of using the constructor):
private static BufferedImage createBufferedImage(final int width, final int height, final boolean transparent) {
final GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice gdev = genv.getDefaultScreenDevice();
final GraphicsConfiguration gcnf = gdev.getDefaultConfiguration();
return transparent
? gcnf.createCompatibleImage(width, height, Transparency.TRANSLUCENT)
: gcnf.createCompatibleImage(width, height);
}
//This is the right panel... It is composed by two cards: one for the labels and one for the image.
public static class RightPanel extends JPanel {
private static final String CARD_IMAGE = "IMAGE",
CARD_LABELS = "LABELS";
private final JPanel labels, imagePanel; //The two cards.
private final JLabel imageLabel; //The label in the second card.
private final int speed; //The speed to animate the motion of the divider.
private final SplitPaneAnimationTimer spat; //The Timer which animates the motion of the divider.
private String currentCard; //Which card are we currently showing?...
public RightPanel(final JSplitPane splitPane, final int delay, final int speed, final int rows, final int cols) {
super(new CardLayout());
super.setBorder(BorderFactory.createLineBorder(Color.red));
spat = new SplitPaneAnimationTimer(delay, splitPane);
this.speed = Math.abs(speed); //We only need a positive (absolute) value.
//Label and panel of second card:
imageLabel = new JLabel();
imageLabel.setHorizontalAlignment(JLabel.CENTER);
imageLabel.setVerticalAlignment(JLabel.CENTER);
imagePanel = new JPanel(new GridBagLayout());
imagePanel.add(imageLabel);
//First card:
labels = new JPanel(new ToggleGridLayout(rows, cols));
for (int i = 0; i < rows * cols; ++i)
labels.add(new JLabel("|"));
//Adding cards...
final CardLayout clay = (CardLayout) super.getLayout();
super.add(imagePanel, CARD_IMAGE);
super.add(labels, CARD_LABELS);
clay.show(this, currentCard = CARD_LABELS);
}
//Will flip the cards.
private void flip() {
final CardLayout clay = (CardLayout) getLayout();
final ToggleGridLayout labelsLayout = (ToggleGridLayout) labels.getLayout();
if (CARD_LABELS.equals(currentCard)) { //If we are showing the labels:
//Disable the laying out...
labelsLayout.setEnabled(false);
//Take a picture of the current panel state:
final BufferedImage pic = createBufferedImage(labels.getWidth(), labels.getHeight(), true);
final Graphics2D g2d = pic.createGraphics();
labels.paint(g2d);
g2d.dispose();
imageLabel.setIcon(new ImageIcon(pic));
imagePanel.revalidate();
imagePanel.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_IMAGE);
}
else { //Else if we are showing the image:
//Enable the laying out...
labelsLayout.setEnabled(true);
//Revalidate and repaint so as to utilize the laying out of the labels...
labels.revalidate();
labels.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_LABELS);
}
}
//Called when we need to animate fully left motion (ie until reaching left side):
public void goLeft() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
minDivLoc = splitPane.getMinimumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc > minDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(minDivLoc, -speed, Math::max, ignore -> flip()); //Start the animation to the left.
}
}
//Called when we need to animate fully right motion (ie until reaching right side):
public void goRight() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
maxDivLoc = splitPane.getMaximumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc < maxDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(maxDivLoc, speed, Math::min, ignore -> flip()); //Start the animation to the right.
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
int rows, cols;
rows = cols = 60;
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
final RightPanel rightPanel = new RightPanel(splitPane, 10, 3, rows, cols);
splitPane.setLeftComponent(leftPanel);
splitPane.setRightComponent(rightPanel);
JButton left = new JButton("Go left"),
right = new JButton("Go right");
left.addActionListener(e -> rightPanel.goLeft());
right.addActionListener(e -> rightPanel.goRight());
final JPanel buttons = new JPanel(new GridLayout(1, 0));
buttons.add(left);
buttons.add(right);
frame.add(splitPane, BorderLayout.CENTER);
frame.add(buttons, BorderLayout.PAGE_START);
frame.setSize(1000, 800);
frame.setMaximumSize(frame.getSize());
frame.setLocationByPlatform(true);
frame.setVisible(true);
splitPane.setDividerLocation(0.5);
});
}
}
I have another problem with my project. I have created this GameBoard made out of JButtons (it works as it's supposed to now) but I would need to add it to a JPanel. I need to display other things (in other panels) in the window. But when I tried to add the Button Array to a panel, and add that panel to the window, crazy things started happening (the buttons were very small, the grid was completely broken etc.). How could I add this Button Array to a JPanel and put that in the center of the window?
Here is my code:
package city;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GameBoard extends JFrame implements ActionListener {
// The overall box count in chess board
public static final int squareCount = 64;
// The Array of buttons (the GameBoard itself)
public Button[][] button = new Button[8][8];
// Colors for all the buttons
public Color defaultColor = Color.WHITE;
public Color darkRedColor = Color.RED;
public Color darkBlueColor = Color.BLUE;
public Color lightBlueColor = Color.CYAN;
public Color lightRedColor = Color.PINK;
public GameBoard(String title) {
// Creates the buttons in the array
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
button[i][j] = new Button();
add(button[i][j]);
button[i][j].setBackground(defaultColor);
}
}
// Build the window
this.setTitle(title); // Setting the title of board
this.setLayout(new GridLayout(8, 18)); // GridLayout will arrange elements in Grid Manager 8 X 8
this.setSize(650, 650); // Size of the chess board
this.setVisible(true); // Sets the board visible
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // If you close the window, the program will terminate
this.setResizable(false); //The window is not resizable anymore ;)
// Sets some text on the buttons
button[0][3].setText("Red's");
button[0][4].setText("Gate");
button[7][3].setText("Blue's");
button[7][4].setText("Gate");
// Colors the buttons
newGame();
}
// Colors the buttons
public void newGame() {
button[0][0].setBackground(lightRedColor);
button[0][1].setBackground(lightRedColor);
button[0][2].setBackground(lightRedColor);
button[0][3].setBackground(darkRedColor);
button[0][4].setBackground(lightRedColor);
button[0][5].setBackground(lightRedColor);
button[0][6].setBackground(lightRedColor);
button[0][7].setBackground(lightRedColor);
button[1][3].setBackground(darkRedColor);
button[7][0].setBackground(lightBlueColor);
button[7][1].setBackground(lightBlueColor);
button[7][2].setBackground(lightBlueColor);
button[7][3].setBackground(lightBlueColor);
button[7][4].setBackground(darkBlueColor);
button[7][5].setBackground(lightBlueColor);
button[7][6].setBackground(lightBlueColor);
button[7][7].setBackground(lightBlueColor);
button[6][4].setBackground(darkBlueColor);
}
// The ActionListener is not yet used
public void actionPerformed(ActionEvent ae) {
String action = ae.getActionCommand();
}
public static void main(String[] args) {
String title = "City - A Two-Player Strategic Game";
GameBoard gameBoard = new GameBoard(title); // Creating the instance of gameBoard
}
}
And here is a screenshot of the bad GUI that generated:
Bad GUI
Thanks for any help, and good luck everyone with a similar problem!
Is this what you are looking for? I am not very sure what´s the problem.
This adds the buttons to a new JPanel and adds this panel to the frame.
JPanel panel = new JPanel(new GridLayout(8, 8));
panel.setSize(650,650);
getContentPane().add(panel, BorderLayout.CENTER);
// Creates the buttons in the array
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
button[i][j] = new JButton();
panel.add(button[i][j]);
button[i][j].setBackground(defaultColor);
}
}
// Build the window
this.setTitle(title); // Setting the title of board
getContentPane().setLayout(new BorderLayout());
this.setSize(650, 650); // Size of the chess board
this.setVisible(true); // Sets the board visible
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // If you close the window, the program will terminate
this.setResizable(false); //The window is not resizable anymore ;)
Current code
ThreeColorButton class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ThreeColorButton {
private static CompositeIcon icons = new CompositeIcon();
private static JPanel panel = new JPanel();
private static JFrame frame = new JFrame();
private static JLabel label = new JLabel();
public static void main(String[] args) {
//create rgb buttons
JButton redButton = new JButton("Red");
JButton greenButton = new JButton("Green");
JButton blueButton = new JButton("Blue");
//add rgb buttons to panel
panel.add(redButton);
panel.add(greenButton);
panel.add(blueButton);
//add action listeners to buttons
redButton.addActionListener(buttonListener(40, Color.red));
greenButton.addActionListener(buttonListener(40, Color.green));
blueButton.addActionListener(buttonListener(40, Color.blue));
frame.setLayout(new BorderLayout());
frame.add(panel, BorderLayout.NORTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private static ActionListener buttonListener(final int size,final Color color) {
return new ActionListener() {
public void actionPerformed(ActionEvent event) {
SquareIcon icon = new SquareIcon(size, color);
icons.addIcon(icon);
label.setIcon(icons);
frame.add(label, BorderLayout.SOUTH);
frame.repaint();
frame.pack();
}
};
}
}
CompositeIcon code
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class CompositeIcon implements Icon{
private ArrayList<Icon> icons;
private int size;
public CompositeIcon() {
icons = new ArrayList<Icon>();
}
public void paintIcon(Component c, Graphics g, int x, int y) {
int position = x;
for(Icon z : icons) {
z.paintIcon(c,g,position,y);
position = position + z.getIconWidth();
}
}
public int getIconHeight() {
return size;
}
public int getIconWidth() {
int total = 0;
for(Icon z : icons) {
total = total + z.getIconWidth();
}
return total;
}
public void addIcon(Icon z) {
icons.add(z);
}
}
SquareIcon class is only a simple little class that creates a square of a single color with a given size.
My question is, in my ThreeColorButton class, when I run it, it doesn't show any icons when I press either of the RGB buttons. However, in the buttonListener method, if I set label.setIcons(icons) to label.setIcons(icon), it shows a single square and it doesnt place it side by side.
I can't seem to figure out whats causing this behavior. Is there a problem with displaying an array of icons using JLabel?
There is nothing to paint in the label since the height of your Icon is 0, since you never set the size.
I would suggest code something like:
#Override
public int getIconHeight()
{
int size = 0;
for(Icon z : icons)
{
size = Math.max(size, z.getIconHeight());
}
return size;
}
You may want to check out Compound Icon. Similar to your class but it has more features. It supports horizontal/vertical/stacked icon and icon alignment options.
It doesn't support dynamically adding Icons so you would need to change that. I might looking adding that feature myself (when I get time) :)
I can't seem to figure out whats causing this behavior. Is there a problem with displaying an array of icons using JLabel?
Yes, as #mKorbel mentioned, there is a problem. A JLabel can only display a single icon. If you want to display n icons, you will need n JLabel instances.
I'm new on swing java, I maked an array of jtoggle buttons and my problem is that I want to limit the number of selected(toggled) buttons for 4 toggled buttons. Is there any property that allows me to do that ?
Here is my code example.
package adad;
import java.awt.*; import java.awt.event.*;
import javax.swing.*;
public class essayer extends JFrame
{
private JToggleButton jb_essai[] = new JToggleButton[6];
JButton pressme = new JButton("Press Me");
essayer() // the frame constructor
{
super("Toggle boutons");
setBounds(100,100,300,200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container con = this.getContentPane();
JPanel pane = getContainer();
con.add(pane);
setVisible(true);
}
class ToggleAction implements ActionListener
{
private JToggleButton jb_essai[];
public ToggleAction(JToggleButton a_jb_essai[])
{
jb_essai = a_jb_essai;
}
public void actionPerformed(ActionEvent e)
{
String etatBoutons = "";
int size = jb_essai.length;
for(int i=0;i<size;i++)
{
String tmp = "Bouton "+(i+1)+" : ";
if(jb_essai[i].isSelected()==true )
{
tmp+="enfonce";
}
else
{
tmp+="relache";
}
tmp+="\n";
etatBoutons +=tmp;
}
System.out.println(etatBoutons+"\n---------");
}
}
private JPanel getContainer()
{
GridLayout thisLayout = new GridLayout(6,2);
JPanel container = new JPanel();
ToggleAction tga = new ToggleAction(jb_essai);
container.setLayout(thisLayout);
int j=6;
for (int i=0;i<j;i++)
{
String s = String.valueOf(i+1);
container.add(jb_essai[i]= new JToggleButton(s)); // actuellement tt s'affiche sur un même colone.
jb_essai[i].addActionListener(tga);
}
return container;
}
public static void main(String[] args) {new essayer();}
}
Is there any property that allows me to do that ?
No. There is a ButtonGroup that allows 'one of many'. But that is 1, not N of many. Anything beyond that you'll need to code yourself.
Is there any property that allows me to do that ?
No, you need to write your own code.
Add a common ItemListener to every toggle button. Then when a button is selected you loop though your toggle button array to count the number of selected toggle buttons.
If the count is greater than 4 then you display a JOptionPane with an error message and your reset the last selected button to be unselected. You can use the getSource() method of the ItemListener to get the toggle button.
Or maybe you can extend the ButtonGroup class to implement similar behaviour.