user494461
user494461

Reputation:

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);

Upvotes: 4

Views: 9179

Answers (4)

jwehrle
jwehrle

Reputation: 4774

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));
    }
}

Upvotes: 0

Pascalius
Pascalius

Reputation: 14669

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;
            };
      });
   }

}

Upvotes: 0

William Morrison
William Morrison

Reputation: 11006

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.

  1. 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);
    
  2. 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));
            };
        }
    }
    
  3. 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));
        };
    }
    
  4. 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.");    
     }
 });

Upvotes: 3

Jyro117
Jyro117

Reputation: 4529

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() { }
}

Upvotes: 2

Related Questions