-1

Writing my first program using JavaFX here. The program shows data of an object (Person) and I want to implement a 'previous' button that should refresh the Pane with the previous object in a static ArrayList 'arrayListOfPeople'.

As a noob, I hoped I could simply do currentPersonPosition-1 and loadWindow like this:

Button btnBottom = new Button(Integer.toString(currentPersonPosition-1));
    final EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent e) {
            loadWindow(currentPersonPosition, borderPane, anotherStage);
        }
    };

But, the handle() method can't use the currentPersonPosition variable as it seems the the inner method can only use final instance variables... Then how do you deal with this properly?

I wrote something that certainly isn't a proper way of doing it: I've set the button text to be the currentPersonPosition-1, and then read&parse the value within the handle method. After spending a lot of time to come with this dopey workaround, I wonder how it's done properly.

The minimal producible example below opens a window and shows Name1/Name2/Name3 in the window title bar, and contains a tiny button at the bottom left that functions as the 'previous' button.

Thanks in advance!

public class Main extends Application {
Scene scene = null;
static ArrayList<String> arrayListOfPeople = new ArrayList<>();

public static void main(String[] args) {
    launch(args);
}

@Override
public void start(final Stage primaryStage) {
    int currentPersonPosition = arrayListOfPeople.size() - 1;
    final BorderPane borderPane = new BorderPane();
    scene = new Scene(borderPane, 640, 480);
    arrayListOfPeople.add("Name1");
    arrayListOfPeople.add("Name2");
    arrayListOfPeople.add("Name3");
    loadWindow(currentPersonPosition, borderPane, primaryStage);
}

public void loadWindow(int currentPersonPosition, final BorderPane borderPane, final Stage anotherStage) {
    if (currentPersonPosition < 0) currentPersonPosition = arrayListOfPeople.size()-1;

    // BOTTOM
    Button btnBottom = new Button(Integer.toString(currentPersonPosition-1));
    final EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent e) {
            String[] testString = e.getTarget().toString().split(",");
            System.out.println(testString[0]);
            if (testString[0].contains("\"")) {
                int positionOfFirstTextQuote = testString[0].indexOf("\"");
                int positionOfLastTextQuote = testString[0].lastIndexOf("\"");
                String currentClipPositionString = testString[0].substring(positionOfFirstTextQuote + 1, positionOfLastTextQuote);
                int currentPersonPosition = Integer.parseInt(currentClipPositionString);

                loadWindow(currentPersonPosition, borderPane, anotherStage);
            }
        }
    };
    btnBottom.addEventHandler(MouseEvent.MOUSE_CLICKED, eventHandler);
    borderPane.setBottom(btnBottom);

    anotherStage.setTitle(arrayListOfPeople.get(currentPersonPosition));
    anotherStage.setScene(scene);
    anotherStage.show();
}

}

Nesher
  • 11
  • 4
  • 2
    [mcve] please .. – kleopatra Sep 10 '20 at 12:11
  • Where did you learn to do `public static void main(String[] args) { arrayListOfPeople.add("Name1"); arrayListOfPeople.add("Name2"); arrayListOfPeople.add("Name3"); launch(args); }` from? – SedJ601 Sep 10 '20 at 17:40
  • @Sedrick I just wanted to come up quickly with a 'minimal reproducible example. The actual program loads data from json files into Person objects and arrayListOfPeople is an ArrayList – Nesher Sep 11 '20 at 07:53

2 Answers2

0

There are various ways to work around this, but they all work the same way; you need to encapsulate the value inside an Object.

If you are using Java 10 or later, you can use an anonymous inner class together with local variable type inference. Declare the variable like this:

final var currentPersonPosition = new Object() {
    int value;
};

Now, you can reference the variable using currentPersonPosition.value. The downside to doing this is that you won't be able to pass the Object as a parameter and still have access to the value field.

The other option is to use a single element array instead:

final int[] currentPersonPosition = new int[1];

Now you can reference the variable using currentPersonPosition[0].

qwerty
  • 810
  • 1
  • 9
  • 26
  • Thanks, I've used the 'single element array' option, it's a lot better than my workaround. – Nesher Sep 11 '20 at 12:11
  • @Nesher If the answer solved you problem, feel free to accept the answer in order indicate that the issue has been resolved. – qwerty Sep 11 '20 at 18:03
  • I'll surely remember this 'single element array' approach in the future when using override methods where I otherwise can't access certain variables, so thanks for that! But I know now that I can use Button.setOnAction instead of EventHandler & the override handle method, so I don't have the problem in the first place. – Nesher Sep 11 '20 at 19:50
0

The key to solving this is by using a counter variable to keep up with the current position. You should not allow that counter variable to become greater than the list size - 1 or less than 0. If you are a beginner, I would recommend creating the button handler using an anonymous inner class like in the following example.

Main

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;


/**
 * JavaFX App
 * @Author Sedrick (SedJ601)
 */
public class App extends Application {

    List<Person> listOfPeople = new ArrayList();
    AtomicInteger currentPosition = new AtomicInteger();
    Label lblName;
    Label lblAge;
    
    @Override
    public void start(Stage stage) {
        listOfPeople.add(new Person("John Doe", 22));
        listOfPeople.add(new Person("Jane Doe", 21));
        listOfPeople.add(new Person("Kim Doe", 5));
        listOfPeople.add(new Person("John Doe Jr", 3));
        
        //Load the first Person
        stage.setTitle(listOfPeople.get(currentPosition.get()).getName());
        Label lblNameTag = new Label("Name: ");
        lblName = new Label(listOfPeople.get(currentPosition.get()).getName());
        Label lblAgeTag = new Label("Age: ");
        lblAge = new Label(Integer.toString(listOfPeople.get(currentPosition.get()).getAge()));    
        
        //Use a GridPane to display the name and age.
        GridPane gridPane = new GridPane();
        gridPane.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
        gridPane.add(lblNameTag, 0, 0);
        gridPane.add(lblName, 1, 0);
        gridPane.add(lblAgeTag, 0, 1);
        gridPane.add(lblAge, 1, 1);
        
        //Create Previous and Next Buttons with their handlers
        Button btnPrevious = new Button("<");
        btnPrevious.setOnAction((t) -> {
            System.out.println(currentPosition.get());
            if(currentPosition.get() > 0)
            {                
                currentPosition.decrementAndGet();
                stage.setTitle(listOfPeople.get(currentPosition.get()).getName());
                lblName.setText(listOfPeople.get(currentPosition.get()).getName());
                lblAge.setText(Integer.toString(listOfPeople.get(currentPosition.get()).getAge())); 
            }
        });
        Button btnNext = new Button(">");
        btnNext.setOnAction((t) -> {
            System.out.println(currentPosition.get());
            if(currentPosition.get() < listOfPeople.size() - 1)
            {                
                currentPosition.incrementAndGet();
                stage.setTitle(listOfPeople.get(currentPosition.get()).getName());
                lblName.setText(listOfPeople.get(currentPosition.get()).getName());
                lblAge.setText(Integer.toString(listOfPeople.get(currentPosition.get()).getAge())); 
            }
        });
        HBox hBox = new HBox(btnPrevious, btnNext);
        hBox.setAlignment(Pos.CENTER);
        
        BorderPane root = new BorderPane();
        root.setCenter(gridPane);
        root.setBottom(hBox);
        
        var scene = new Scene(root, 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}

Person

/**
 *
 * @author sedrick (SedJ601)
 */
class Person 
{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" + "name=" + name + ", age=" + age + '}';
    }    
}
SedJ601
  • 12,173
  • 3
  • 41
  • 59
  • 2
    Thanks for your reply! Using Button.setOnAction instead of the EventHandler & handle() method like I was doing solves the main issue (that I could not access the ArrayList variables). Also good to know AtomicInteger now. There's some duplicate code (stage.setTitle, lblName.setText and lblAge.setText 3x in total in the start method), I'll change that, but your reply saved me hours, so thanks again! – Nesher Sep 11 '20 at 19:25