Find index of button in 2D Array Java? - java

I just have a question on the following. I've got a 2D array of buttons that require me to run another method when I click on them. My current method relies on the following input, a String (which I can get from the user easily) and two integer values. These are dictated by the buttons position in the array.
I have a button listener attached to these buttons but I am not too sure how I can work out what the button's position actually is. I've made my own button class (because I wanted some specific settings and I thought it would be easier) and when making it I implemented a method called getXPos and getYPos which basically hold the values for the actual button in the array when it was created. Thing is I don't know how to retrieve these values now as the listener doesn't actually know what button is being pressed does it?
I can use the getSource() method but I then don't know how to invoke that source's methods. For example I tried to do the following.
int x = event.getSource().getXPos(); but I am unable to do this. Is there any way of telling what button I have pressed so I can access it's internal methods or something similar? Thanks!

To call a method on the source, you have to cast it first. If you're never adding your ActionListener to anything but an instance of your special MyButton with its x and y variables, you can do this:
MyButton button = (MyButton) event.getSource();
int x = button.getXPos();
int y = button.getYPos();
Then MyButton contains the x and y:
public class MyButton extends JButton {
private int xPos;
private int yPos;
// ...
public int getXPos() {
return xPos;
}
public int getYPos() {
return yPos;
}
}
And make sure you're always adding your listener to instances of MyButton:
MyButton myButton = new MyButton();
// ...
myButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
MyButton button = (MyButton) e.getSource();
int xPos = button.getXPos();
int yPos = button.getYPos();
// Do something with x and y
}
});

Related

Designing a simple event-driven GUI

I am creating a simple event-driven GUI for a video game I am making with LibGDX. It only needs to support buttons (rectangular) with a single act() function called when they are clicked. I would appreciate some advice on structuring because the solution I've thought of so far seems to be far from ideal.
My current implementation involves all buttons extending a Button class. Each button has a Rectangle with its bounds and an abstract act() method.
Each game screen (e.g. main menu, character select, pause menu, the in-game screen) has a HashMap of Buttons. When clicked, the game screen iterates through everything in the HashMap, and calls act() on any button that was clicked.
The problem I'm having is that Buttons have to have their act() overridden from their superclass in order to perform their action, and that the Buttons aren't a member of the Screen class which contains all the game code. I am subclassing Button for each button in the game. My main menu alone has a ButtonPlay, ButtonMapDesigner, ButtonMute, ButtonQuit, etc. This is going to get messy fast, but I can't think of any better way to do it while keeping a separate act() method for each button.
Since my mute button isn't a part of the main menu screen and can't access game logic, it's act() is nothing more than mainMenuScreen.mute();. So effectively, for every button in my game, I have to create a class class that does nothing more than <currentGameScreen>.doThisAction();, since the code to actually do stuff must be in the game screen class.
I considered having a big if/then to check the coordinates of each click and call the appropriate action if necessary. For example,
if (clickWithinTheseCoords)
beginGame();
else if(clickWithinTheseOtherCoords)
muteGame();
...
However, I need to be able to add/remove buttons on the fly. When a unit is clicked from the game screen, a button to move it needs to appear, and then disappear when the unit is actually moved. With a HashMap, I can just map.add("buttonMove", new ButtonMove()) and map.remove("buttonMove") in the code called when a unit is clicked or moved. With the if/else method, I won't need a separate class for every button, but I would need to keep track of whether each clickable area tested is visible and clickable by the user at this point in the game, which seems like an even bigger headache that what I have right now.
I would provide a runnable to all the buttons which u will run in the act method. To give u a simple example.
private final Map<String, Button> buttons = new HashMap<>();
public void initialiseSomeExampleButtons() {
buttons.put("changeScreenBytton", new Button(new Runnable() {
#Override
public void run() {
//Put a change screen action here.
}
}));
buttons.put("muteButton", new Button(new Runnable() {
#Override
public void run() {
//Do a mute Action here
}
}));
}
public class Button {
//Your other stuff like rectangle
private final Runnable runnable;
protected Button(Runnable runnable) {
this.runnable = runnable;
}
public void act() {
runnable.run();
}
}
You keep track of your buttons via the map and just need to pass a runnable action to every button in the constructor. I intentionally skipped some code so that you can try yourself. If you have any questions, let me know.
Sneh's response reminded me of a fairly major oversight - instead of having to create a separate class for every button, I could use anonymous inner classes whenever I created a button, specifying its coordinates and act() method every time. I explored lambda syntax as a possible shorter method to do this, but ran into limitations with it. I ended up with a flexible solution, but ended up reducing it a bit further to fit my needs. Both ways are presented below.
Each game screen in my game is subclassed from a MyScreen class, which extends LibGDX's Screen but adds universal features like updating the viewport on resize, having a HashMap of Buttons, etc. I added to the MyScreen class a buttonPressed() method, which takes in as its one parameter an enum. I have ButtonValues enum which contains all the possible buttons (such as MAINMENU_PLAY, MAINMENU_MAPDESIGNER, etc.). In each game screen, buttonPressed() is overriden and a switch is used to perform the correct action:
public void buttonPressed(ButtonValues b) {
switch(b) {
case MAINMENU_PLAY:
beginGame();
case MAINMENU_MAPDESIGNER:
switchToMapDesigner();
}
}
The other solution has the button store a lambda expression so that it can perform actions on its own, instead of requiring buttonPressed() to act as an intermediary that performs the correct action based on what button was pressed.
To add a button, it is created with its coordinates and type (enum), and added to the HashMap of buttons:
Button b = new Button(this,
new Rectangle(300 - t.getRegionWidth() / 2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()),
tex, ButtonValues.MAINMENU_PLAY);
buttons.put("buttonPlay", b);
To remove it, just buttons.remove("buttonPlay"). and it'll disappear from the screen and be forgotten by the game.
The arguments are the game screen which owns it (so the button can call buttonPressed() on the game screen), a Rectangle with its coordinates, its texture (used to draw it), and its enum value.
And here's the Button class:
public class Button {
public Rectangle r;
public TextureRegion image;
private MyScreen screen;
private ButtonValues b;
public Button(MyScreen screen, Rectangle r, TextureRegion image, ButtonValues b) {
this.screen = screen;
this.r = r;
this.image = image;
this.b = b;
}
public void act() {
screen.buttonPressed(b);
}
public boolean isClicked(float x, float y) {
return x > r.x && y > r.y && x < r.x + r.width && y < r.y + r.height;
}
}
isClicked() just takes in an (x, y) and checks whether that point is contained within the button. On mouse click, I iterate through all the buttons and call act() if a button isClicked.
The second way I did it was similar, but with a lambda expression instead of the ButtonValues enum. The Button class is similar, but with these changes (it's a lot simpler than it sounds):
The field ButtonValues b is replaced with Runnable r, and this is removed from the constructor. Added is a setAction() method which takes in a Runnable and sets r to the Runnable passed to it. The act() method is just r.run(). Example:
public class Button {
[Rectangle, Texture, Screen]
Runnable r;
public Button(screen, rectangle, texture) {...}
public void setAction(Runnable r) { this.r = r; }
public void act() { r.run(); }
}
To create a button, I do the following:
Button b = new Button(this,
new Rectangle(300 - t.getRegionWidth() / 2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()),
tex);
b.setAction(() -> b.screen.doSomething());
buttons.put("buttonPlay", b);
First, a button is created with its containing game screen class, its bounding box, and its texture. Then, in the second command, I set its action - in this case, b.screen.doSomething();. This can't be passed to the constructor, because b and b.screen don't exist at that point. setAction() takes a Runnable and sets it as that Button's Runnable that is called when act() is called. However, Runnables can be created with lambda syntax, so you don't need to create an anonymous Runnable class and can just pass in the function it performs.
This method allows much more flexibility, but with one caveat. The screen field in Button holds a MyScreen, the base screen class from which all of my game screens are extended. The Button's function can only use methods that are part of the MyScreen class (which is why I made buttonPressed() in MyScreen and then realized I could just scrap the lambda expressions completely). The obvious solution is to cast the screen field, but for me it wasn't worth the extra code when I could just use the buttonPressed() method.
If I had a beginGame() method in my MainMenuScreen class (which extends MyScreen), the lambda expression passed to the button would need to involve a cast to MainMenuScreen:
b.setAction(() -> ((MainMenuScreen) b.screen).beginGame());
Unfortunately, even wildcard syntax doesn't help here.
And finally, for completeness, the code in the game loop to operate the buttons:
public abstract class MyScreen implements Screen {
protected HashMap<String, Button> buttons; // initialize this in the constructor
// this is called in every game screen's game loop
protected void handleInput() {
if (Gdx.input.justTouched()) {
Vector2 touchCoords = new Vector2(Gdx.input.getX(), Gdx.input.getY());
g.viewport.unproject(touchCoords);
for (HashMap.Entry<String, Button> b : buttons.entrySet()) {
if (b.getValue().isClicked(touchCoords.x, touchCoords.y))
b.getValue().act();
}
}
}
}
And to draw them, located in a helper class:
public void drawButtons(HashMap<String, Button> buttons) {
for (HashMap.Entry<String, Button> b : buttons.entrySet()) {
sb.draw(b.getValue().image, b.getValue().r.x, b.getValue().r.y);
}
}

GridLayout Representing an ArrayList of JLabels issue

I'm looking for a bit of help with a problem I'm having. I am creating a GridLayout on my GUI and in each Grid there will be a JLabel. Along side this I have an ArrayList which contains images which will be displayed in each Grid.
What I am trying to do is when I click a specific grid, it will add an image from the ArrayList and place it in the grid position. What I would like is have a left click to add the item in the ArrayList and a right click to remove the item in the list.
The ArrayList and GUI code are in different classes and the ArrayList is implemented in the main method. I have tried to no avail, I cannot seem to get the grids to represent the list.
Basically I need a GridLayout to give a visual representation of an ArrayList, that can be manipulated with mouse interaction
Can anyone point me in the right direction?
Code for the Grids:
for (int i = 0; i < 12; i++)
{
JLabel assetLabel = new JLabel("Test"+(i+1));
System.out.println("assetLabel"+(i));
assetLabel.addMouseListener(new ParcelInfo(i));
assetLabel.setBackground(Color.WHITE);
assetLabel.setOpaque(true);
assetGrid.add(assetLabel);
}
Code for the items I need in the JLabel:
public class test
{
private ImageIcon img;
test(ImageIcon i)
{
this.img=i;
}
}
This is how you define a Listener for it.
public List<Image> arrayList = new ArrayList<>();
public OtherClassWithArray foo = new OtherClassWithArray();
jLabel.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1) // left click
jLabel.setIcon(arrayList.get(xyz)); // access arraylist here
// jLabel.setIcon(foo.getArrayList.get(xyz); // access arrayList from other class
else // right click
// do smth here
}
});

Need help in using scene2d button in libgdx

I am new to libGDX. I am trying to create a custom button by extending com.badlogic.gdx.scenes.scene2d.ui.Button.
I want all the button related logic in this class. But I am not getting how to make the click work. I read many tutorials regarding adding Event Listeners but nothing is working.
public class RestartButton extends Button {
public RestartButton(ButtonStyle style) {
super(style);
}
#Override
public void draw(SpriteBatch batch, float parentAlpha) {
batch.draw(TextureProvider.getInstance().getRestart(), 175, 100);
}
}
And i am trying to add my button in the screen(i.e in show method) like this
RestartButton restartButton;
restartButton=new RestartButton(new ButtonStyle());
Stage stage;
stage.addActor(restartButton);
I am able to see my button on the screen. Now what i want to do is add some code which gets invoked when button is clicked or touched. Can someone please help ?
restartButton = new RestartButton(new ButtonStyle());
button.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
System.out.println("Restart clicked!");
}
});
stage.addActor(restartButton);
It does not work because you need to setBounds for your Button. If you wanted to draw the button in the position (175, 100) you could just create a Button directly from Button Class and call
button.setBounds(x, y, width, height);
Then adding the listener will work because now your button will actually have a position and an area in the stage. If you still need to extend the button class for your own reasons, you can set the bounds in the extended class ditectly or you can pass another argument in your RestartButton class. Similar to:
public RestartButton(ButtonStyle style, Vector2 position, Vector2 size) {
super(style);
this.setBounds(position.x, position.y, size.x, size.y);
}
Then the button will automatically be drawn to the position you want without the need of overriding draw method. add the listener by using this.addListener(yourListener);
Hope it helps.

dealing with tictactoe detecting the buttons

So, I'm working on a project that uses a GridLayout and an array of tictactoetile, which is a class which extends JButton.
For an example of the first thing I'm trying to do, I want to find out what button is clicked, and then set that button's text to equal either x or o depending on which turn it is. I have the logic for that down, I just don't know how to get the row and column of the button clicked.
Sorry if this is not worded well.
public class tictactoetile extends JButton implements ActionListener {
private int cory;
private int corx;
//Create your own GameObj class with the necessary members
//Some examples for members below...
private GameObj game;
public tictactoetile(int x,int y,GameObj gam) {
cory = y;
corx = x;
game = gam;
super();
}
public void actionPerformed(ActionEvent e) {
this.setText(game.getTurnMarker()); //returns either x or o
game.updateGameState(game.getTurn(),cory,corx);
game.nextTurn();
}
}

How to access other elements in click listeners in libgdx

How do I modify the Lable info's text by calling its settext method?
For e.g. depending in the button pressed I want to set the label's text appropriately
I get this error when I try accessing the label :
Cannot refer to a non-final variable i inside an inner class defined in a different
method
Skin skin = new Skin(Gdx.files.internal("uiskin.json"));
stage = new Stage();
Gdx.input.setInputProcessor(stage);
table = new Table();
table.setFillParent(true);
stage.addActor(table);
String sentence = "One two three four five six seven eight";
String[] temp = sentence.split(" ");
ArrayList<String> words = new ArrayList<String>(Arrays.asList(temp));
info = new Label( "Welcome to Android!", skin );
for(int i=0; i<words.size();i++)
{
TextButton button = new TextButton( words.get(i), skin);
table.add(button);
button.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("button","clicked");
//info.setText(Integer.toString(i)); How to make this work?
//also how do I know which button is pressed?
};
});
}
table.row();
table.add(info);
Gdx.input.setInputProcessor(stage);
There's 4 different ways to fix this. I recommend option 1. I'll present all the options, and a full solution at the end.
Declare label as final. Here's a question I answered on anonymous classes.
final Label info;
//and then in your constructor initialize it...
info = new Label("Welcome to Android",skin);
Declare an inner class extending ClickListener and reference the outer class label instance. This doesn't require the info variable be final.
public class MyLibgdxClass{
class MyClickListener implements ClickListener{
public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("button","clicked");
info.setText(Integer.toString(i));
};
}
}
Create a class extending ClickListener in its own file and pass it the label you need to manipulate.
public class MyClickListener implements ClickListener{
Label info;
public MyClickListener(Label info){
this.info = info;
}
public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("button","clicked");
info.setText(Integer.toString(i));
};
}
Jyro117 in comments suggests another approach. Create a temporary final variable, assign it to your current label instance, and reference the temporary variable.
I don't recommend this solution, I show you this only for the sake of thoroughness. Here's why:
If you reassign your label later, you'll need to remove all your button's listeners and create new listeners every time. If you don't plan on reassigning your label, why not declare the label final? Its not the best approach here.
final Label tempLabel = info;
button.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("button","clicked");
tempLabel.setText(i+"");
}
});
As for detecting which button is clicked, as you are creating these buttons in a loop, in local scope, they can be declared final without any consequence as you'll likely not be reassigning them. Here's my suggestion for a full solution.
//wherever you've declared label info, declare it as final.
//note you'll need to initialize label in your constructor!
final Label info;
//...later on...
for(int i=0; i<words.size();i++)
{
//declare button as final, so you can reference it in clicklistener
final TextButton button = new TextButton( words.get(i), skin);
//temporary final copy of i, so you can reference in clicklistener
final int tempI = i;
table.add(button);
button.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("button","clicked");
info.setText(tempI+""); //How to make this work?
//also how do I know which button is pressed?
Gdx.app.log("button",button.getText()+" was pressed.");
}
});
William Morrison listed a number of ways you can create ClickListeners. I prefer the method of anonymous classes myself and I see that is the method used in your question.
You need to make all references outside of your anonymous class final so that you can reference them inside. This is how java ensures the reference does not change. Look at my code (and comments) for a complete solution, I only had to change a couple things. (I also have my skin file located at 'skin/uiskin.json' so keep that in mind if you wish to use this code).
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ClickTest implements ApplicationListener {
private Stage stage;
private Skin skin;
#Override public void create() {
Gdx.app.log("CREATE", "App Opening");
this.skin = new Skin(Gdx.files.internal("skin/uiskin.json"));
stage = new Stage();
Gdx.input.setInputProcessor(stage);
Table table = new Table();
table.setFillParent(true);
stage.addActor(table);
String sentence = "One two three four five six seven eight";
String[] temp = sentence.split(" ");
List<String> words = new ArrayList<String>(Arrays.asList(temp));
final Label info = new Label("Welcome to Android!", skin);
for (int i = 0; i < words.size(); i++) {
// make i final here so you can reference it inside
// the anonymous class
final int index = i;
TextButton button = new TextButton(words.get(i), skin);
table.add(button);
button.addListener(new ClickListener() {
#Override public void clicked(InputEvent event, float x, float y) {
// When you click the button it will print this value you assign.
// That way you will know 'which' button was clicked and can perform
// the correct action based on it.
Gdx.app.log("button", "clicked " + index);
info.setText(Integer.toString(index));
};
});
}
table.row();
// Changed this so it actually centers the label.
table.add(info).colspan(words.size()).expandX();
Gdx.input.setInputProcessor(stage);
Gdx.gl20.glClearColor(0f, 0f, 0f, 1);
}
#Override public void render() {
this.stage.act();
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
Gdx.gl20.glEnable(GL20.GL_BLEND);
this.stage.draw();
}
#Override public void dispose() {
Gdx.app.log("DISPOSE", "App Closing");
}
#Override public void resize(final int width, final int height) {
Gdx.app.log("RESIZE", width + "x" + height);
Gdx.gl20.glViewport(0, 0, width, height);
this.stage.setViewport(width, height, false);
}
#Override public void pause() { }
#Override public void resume() { }
}
I prefer this version
public class MyClass{
float test;
TextButton button;
public void method(){
button.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
MyClass.this.test = 10.0f;
};
});
}
}
The simplest strategy I've found for this sort of objective is to create my own custom listener class. This way I can pass anything I need to pass through the constructor - including a reference to the object to which I am adding the listener.
for(int i = 0; i < buttonArray.length; ++i) {
buttonArray[i].addListener(new CustomListener(buttonArray[i]));
}
Make an inner class:
public class CustomListener extends ClickListener {
Object listeningObject;
public CustomListener(Object listeningObject) {
this.listeningObject = listeningObject;
}
#Override
public void clicked(InputEvent event, float x, float y) {
((Button)listeningObject).useAButtonMethod();
}
}
You can use this general strategy to pass anything through a constructor to a custom listener, final or not.
PS: Obviously "useAButtonMethod()" is not a method but an indication that you can use any Button method you want to.
Edit: In your case you might able to do something like this:
for (int i = 0; i < words.size(); i++)
// your stuff here.
button.addListener(new CustomListener(info, i));
}
Make an inner class:
public class CustomListener extends ClickListener {
Label label;
int index;
public CustomListener(Label label, int index) {
this.label = label;
this.index = index;
}
#Override
public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("button", "clicked " + index);
label.setText(Integer.toString(index));
}
}

Categories