Panels disappear from Box when event is triggered - java

I am fairly new to Java programming, especially as far as GUIs are concerned.
I am trying to create a game similar to "Don't step on the white tile" and for this I need 4 rows, each with 4 rectangles, one of them being black. For this, I have created a subclass of JPanel( called MyImagePanel) in which I have overriden the method paintComponent. An object of this type represents one row out of the 4. My idea was to add 4 obbjects of type MyImagePanel to a Box and register a mouseListener with each of them. If the user clicks on the black tile in the bottom row, a new row should appear at the top and the bottom one should disappear( as if the user is stepping forwards). Otherwisem the user loses( for the moment, this only prints out a message with "Lose").
However, what actually happens is that a new row is generated at the top and the rows at the bottom simply disappear, without being replaced. I do not understand why this occurs.
This is the code for the Test class:
public class Test {
ArrayList<MyImagePanel> rows;
JFrame frame;
Box mainPanel;
public Test(){
rows=new ArrayList<MyImagePanel>();
mainPanel=new Box(BoxLayout.Y_AXIS);
}
public void go(){
frame=new JFrame();
for(int i=0;i<4;i++){
MyImagePanel panel=createPanel();
rows.add(panel);
mainPanel.add(panel);
}
frame.add(mainPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(240, 440);
frame.setResizable(false);
frame.setVisible(true);
}
class MyMouseListener extends MouseAdapter{
public void mouseClicked(MouseEvent ev){
int x=ev.getX();
System.out.println("X coord "+x);
MyImagePanel panel=(MyImagePanel)ev.getComponent();
Color c=panel.getColor(x);
if(panel.equals(rows.get(3)) && c.equals(Color.BLACK)){
System.out.println("Ok");
rows.remove(3);
rows.add(0,createPanel());
System.out.println("List length "+rows.size());
mainPanel.remove(3);
mainPanel.add(rows.get(0), 0);
System.out.println("Components in box "+mainPanel.getComponentCount());
mainPanel.repaint();
}
else{
System.out.println("Lose");
}
}
}
private MyImagePanel createPanel(){
MyImagePanel panel=new MyImagePanel();
panel.setSize(240,100);
panel.addMouseListener(new MyMouseListener());
return panel;
}
public static void main(String[] args){
Test t=new Test();
t.go();
}
}

Because the layout is changing, you'll have to (re)validate and possibly repaint the enclosing mainPanel. The action listener in this related example replaces all of the components that might have moved.
private void createPane() {
this.removeAll();
for (JLabel label : list) add(label);
this.validate();
}
In contrast, this example updates each button's icon in place.
private void update() {
Collections.shuffle(list);
int index = 0;
for (JToggleButton b : buttons) {
b.setIcon(list.get(index++));
}
}
A more flexible approach would be to use the MVC pattern as shown here. Each time the model is updated, the listening view updates itself in response. Instead of replacing components, you update the component in place, e.g. by changing its color.

Related

How to add JButtons in a for loop?

So I am making an UI in a minecraft plugin, and it adds a button for every player, and when we click the button, it kicks the player.
This is the for loop:
for (final Player p : Bukkit.getOnlinePlayers())
{
System.out.println("Looping.");
final JButton b = new JButton();
b.setName(p.getName());
b.setText(p.getName());
b.setToolTipText("Kick " + b.getText());
b.setBackground(Color.GREEN);
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!b.getBackground().equals(Color.RED))
{
Bukkit.getScheduler().runTask(main, new Runnable() {
public void run() {
Bukkit.getPlayer(b.getText()).kickPlayer(jtf.getText());
b.setBackground(Color.RED);
}
});
}
}
});
System.out.println("Button added.");
f.add(b);
}
And let's say there are 2 players in the server, asd and dsa. When this for loop runs, it just adds button for asd, but prints Button added (and Looping) two times.
(f is a public static JFrame, and jtf is a public static JTextField)
So... Why is it not working?
It happens because the defult LayoutManager is BorderLayout, so the method add(JComponent comp) puts the JComponent in the center of the JFrame, and the JComponent fills the entire JFrame.
Use the method f.setLayout(new GridLayout(number_of_players, 1));, it will split the JFrmae to rows as the number of your players, so a multiply number of JComponents could be displayed.

Toggling between screens using setVisible(boolean) in Swing

I have created a simple game in Swing which has a screen. Clicking on a cell results in color change of two adjacent cells. This is achieved by this code:
public class SelfGrid extends BattleGrid {
#Override
protected JPanel getCell()
{
JPanel panel = new JPanel();
panel.setBackground(Color.black);
panel.setBorder(BorderFactory.createLineBorder(Color.blue, 1));
panel.setPreferredSize(new Dimension(20, 20));
panel.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e)
{
panel.setSize(new Dimension(20,80));
panel.setBackground(Color.orange);
}
}
});
return panel;
}
}
public abstract class Battle extends JPanel {
public BattleGrid() {
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel grid = new JPanel();
grid.setLayout(new GridLayout(0,10));
JPanel panel = new JPanel();
panel = getCell();
grid.add(panel);
}
}
this.add(grid);
}
protected abstract JPanel getCell();
}
When I use setVisible(boolean) method to toggle between two screens and the original screen is brought back, only the cells that were clicked on remain colored. In other words, the dimension of each JPanel is restored to 20,20. I was told that this is because setVisible() method actually repaints components on the screen. How can I bring back the original screen without any changes being made to it contents? Thank you.
Instead of making programatic color changes directly to the UI elements, create a two dimensional array that represents the colors of the cells, and modify that. Then, repaint the cells based off the values in the array each time visibility changes or a cell is clicked.

How do I change button labels in a grid?

I'm trying to make a game which involves moving pieces by dragging and dropping them from square to square. However, I'm trying to do so using buttons with labels (as an exercise). So, for instance, a button with the label "W" should change its label to "" (blank) when I press the mouse on it and release on a second valid button (one with another blank label). Then that second button should change its label from "" (blank) to "W".
Using graphics in Java is entirely new to me. Suffice it to say, I'm not sure how to accomplish the aforementioned task. Here's my code so far:
import javax.swing.*;
import javax.swing.JButton;
import java.awt.GridLayout;
import java.awt.event.*;
class Boardgame extends JFrame implements MouseListener {
JFrame frame = new JFrame("Boardgame");
JButton[][] bogrid;
public Boardgame ()
{
frame.setLayout(new GridLayout(8,8));
bogrid = new JButton[8][8];
for (int i=0;i<8;i++)
{
for (int j=0;j<2;j++)
{
bogrid[j][i] = new JButton("B");
frame.add(bogrid[j][i]);
}
for (int j=2;j<6;j++)
{
bogrid[j][i] = new JButton();
frame.add(bogrid[j][i]);
}
for (int j=6;j<8;j++)
{
bogrid[j][i] = new JButton("W");
frame.add(bogrid[j][i]);
}
}
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setSize(405, 450);
frame.setVisible(true);
}
public void mouseClicked(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
public void mousePressed(MouseEvent e)
{
}
class MyAction implements ActionListener {
Boardgame bo;
MyAction(Boardgame b)
{
bo = b;
}
}
}
First off, you'll need to use a MouseListener to listen for mousePressed and mouseReleased actions. This means that you most definitely should not be using JButtons for this type of program but rather either JLabels, or a logical grid of images, since JButtons should respond to ActionListeners, not MouseListeners (with rare exceptions). I suggest using JLabels, since it will be easy for them to hold and set text and to give you the text they hold.
Next, you would add your MouseListener to your JLabels, and on mousePressed, get the text held by the pressed JLabel. The MouseEvent parameter's getSource() method will return to you the pressed (and released) JLabel.
So inside of your for loops, you will need to add something like:
bogrid[j][i] = new JLabel("B");
bogrid[j][i].addMouseListener(myMouseListener);
frame.add(bogrid[j][i]);
Where myMouseListener is your MouseListener object.
Edit
You ask in comment:
Initial question: using JLabel instead of JButton yields a window that appears to be a large empty field with a bunch of floating labels. How do I make it appear as a grid with lines demarcating the individual spaces?
Consider giving your JLabel a border, and consider giving the GridLayout some horizontal and vertical gaps.
For the layout use the GridLayout constructor that takes 4 int parameters, not 2, with the 3rd and 4th parameters being for the horizontal and vertical gaps:
int gap = 4; // or whatever number looks nice
frame.setLayout(new GridLayout(8, 8, gap, gap));
For the border, consider using a LineBorder that is added to the JLabel in the loop where you create it. If that Border crowds your text too much, you could use a CompoundBorder where the inner border is an EmptyBorder with suitable constants and the outer border is a LineBorder.

Click button and get draw rectangle

I am new to Java....I studied that we can add two things on frame... I added button and in response by clicking on button I want rectangle as output....but i don't understand that..Why i am not getting output.....
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class customizedgui5 implements ActionListener {
JButton button;
JFrame frame;
public static void main(String[] args) {
customizedgui5 hi = new customizedgui5();
hi.go();
}
public void go() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton("click me");
button.addActionListener(this);
myclass a = new myclass();
frame.getContentPane().add(button, BorderLayout.CENTER);
frame.getContentPane().add(a, BorderLayout.SOUTH);
frame.setSize(100, 100);
frame.setVisible(true);
}
public void actionPerformed(ActionEvent event) {
frame.repaint();
frame.revalidate();
}
}
class myclass extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.orange);
g.fillRect(20, 50, 100, 100);
}
}
I would start by taking a look at Performing Custom Painting.
The main problem in your code is you are getting NullPointerException when you click the button because the reference of frame is null.
It is null because you've shadowed it in the constructor (basically, declared another variable of the same name within the constructor)...
// I'm null..
JFrame frame;
public void go() {
// Not the same as frame above...
JFrame frame = new JFrame();
You are also going to not see any changes because of a number of reasons...
The myclass panel has no size. With BorderLayout, this won't be "too" much of a problem, but...
You've drawing outside of the visible range of the panel. The rectangle you are painting won't appear because it is being painted outside of the width and height of the panel.
The rectangle will appear before you press the button as paintComponent will be called to update the state of the panel once it's made visible on the screen...
The first thing you need to is provide some kind of size hints to the BorderLayout. Try adding...
#Override
public Dimension getPreferredSize() {
return new Dimension(120, 150);
}
To myclass.
You also don't need to repaint the frame, what you really want to repaint is the instance of myclass. Try updating customizedgui5 so that a becomes a instance variable (like frame...
//...
myclass a;
//...
public void go() {
//...
a = new myclass();
//...
}
public void actionPerformed(ActionEvent event) {
a.repaint();
}
Now, the rectangle will still be shown the moment that the panel is made visible on the screen. Sure you could try setting it invisible, but this will affect the layout of the frame, hiding your component to start with, so, instead, we need some kind of flag we can trip so we know when to paint the rectangle. This is easily achieved by using a simple boolean variable, for example...
class myclass extends JPanel {
private boolean paintRect;
public void setPaintRect(boolean paint) {
paintRect = paint;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(120, 150);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (paintRect) {
g.setColor(Color.orange);
g.fillRect(20, 50, 100, 100);
}
}
}
Then in you actionPerformed method, you just need to set the flag...
public void actionPerformed(ActionEvent event) {
a.setPaintRect(true);
}
You may also want to take a read through Code Conventions for the Java Programming Language. It will make it easier for people to read your code...
When you click on your button, you're calling the method actionPerformed(ActionEvent event)
Take a look at what you did there. Currently, you repaint and re-validate the frame. If you want to add a rectangle to your frame, you need to do so by adding a new component to the frame that will draw the rectangle.
You could add another instance of your myclass JPanel which paints a rectangle like so:
public void actionPerformed(ActionEvent event) {
frame.getContentPane().add(new myclass(), BorderLayout.NORTH);
frame.repaint();
}
This would add your custom rectangle-drawing panel to the North section of your BorderLayout. If you want to add the rectangle "on top of" your button, you should embed your button within a JPanel, then add the rectangle-drawing panel to that instead of your main JFrame

Java, change size of Frame when Panel changes size, homework

Newbie, so bear with me. I am writing a Sokoban game in Java, I got the game up and going but I am having problems with pack(). I want my frame to resize itself depending on the size of the level, different levels have different sizes. The panel is being painted correctly for the different sizes, so if I just maximize the frame then everything is good, but how do I invoke pack() to automatically resize the frame? I tried to put pack() in my Main method, but I suspect the solution is not so simple (probably the way I have structured my program does not help either). Putting pack() in the Main method produces the image attached, a very small rectangle with basically just the min,max and close buttons.
The recommended solution that I would like to implement is as follows:
write your Sokoban constructor so that it takes the surrounding JFrame as a reference parameter that your object then remembers in a field. Then after you change the preferred size of your Sokoban component, call the method pack for this stored surrounding JFrame.
I have attached the code for my constructor and Main method.
public class Sokoban extends JPanel {
LevelReader lReader = new LevelReader();
private static final int SQUARESIZE = 50; // square size in pixels
int currentLevel = 0;
int height = 0;
int width = 0;
int x = 0;
int y = 0;
Contents [][] mapArray;
public Sokoban(String fileName) {
lReader.readLevels(fileName);
initLevel(currentLevel);
KeyListener listener = new MyKeyListener();
addKeyListener(listener);
this.setPreferredSize(new Dimension(500,500));
setFocusable(true);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Sokoban");
Sokoban sokoban = new Sokoban("m1.txt");
frame.add(sokoban);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
write your Sokoban constructor so that it takes the surrounding JFrame as a reference parameter that your object then remembers in a field. Then after you change the preferred size of your Sokoban component, call the method pack for this stored surrounding JFrame
Answer was updated to use key binding as suggested by #mKorbel (thanks for tip, code looks cleaner now)
What your fried was trying to tell is to create something like this (I removed your code that wasn't necessary in this example)
class Sokoban extends JPanel {
private JFrame frame;
private class MyAction extends AbstractAction {
private Dimension dimension;
public MyAction(Dimension dimension) {
this.dimension = dimension;
}
#Override
public void actionPerformed(ActionEvent e) {
//we will pack only when dimensions will need to change
if (!getPreferredSize().equals(dimension)) {
setPreferredSize(dimension);
frame.pack();
}
}
}
public Sokoban(String fileName, JFrame tframe) {
this.frame = tframe;
setFocusable(true);
setPreferredSize(new Dimension(100, 100));
setBackground(Color.red);
add(new JLabel("press A, S, D"));
getInputMap().put(KeyStroke.getKeyStroke('a'), "typed a");
getInputMap().put(KeyStroke.getKeyStroke('s'), "typed s");
getInputMap().put(KeyStroke.getKeyStroke('d'), "typed d");
getActionMap().put("typed a", new MyAction(new Dimension(100, 100)));
getActionMap().put("typed s", new MyAction(new Dimension(200, 100)));
getActionMap().put("typed d", new MyAction(new Dimension(100, 200)));
}
public static void main(String[] args) {
JFrame frame = new JFrame("Sokoban");
Sokoban sokoban = new Sokoban("m1.txt", frame);
frame.setContentPane(sokoban);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
In constructor public Sokoban(String fileName, JFrame tframe) you need to pass reference to frame that will contain your Sokoban panel. You will need to store object from that reference somewhere in your class, like in class field private JFrame frame;.
Now thanks to that reference whenever you change size of your panel you can use it to change size of frame containing that panel by invoking frame.pack() and make it to adapt to new size.

Categories