Skip to content
Advertisement

Bind java.util.Stack with JavaFX (ListView or TableView)

I’m recently using JavaFX and would like to implement the Observer pattern by binding my stack update, with a ListView or TableView from JavaFX. However, I don’t know what changes to make to my ComplexNumberStack class.

public class ComplexNumberStack extends Stack<ComplexNumber> {
    private static ComplexNumberStack instance = null;

    /** This method provide the unique instance of ComplexNumberStack. */
    public static ComplexNumberStack getInstance() {
        if (instance == null)
            instance = new ComplexNumberStack();

        return instance;
    }
    
    /**
     * This method provides a secure implementation of massive pop of operandNumber operands from the stack
     *
     * @param operandNumber specifies the number of operands to be taken from the stack
     * @return an iterator of complexNumber taken
     */
    public Iterator<ComplexNumber> getOperand(int operandNumber) {
        List<ComplexNumber> operands = new ArrayList<>();
        for (int i = 0; i < operandNumber; i++) {
            try {
                operands.add(pop());
            } catch (EmptyStackException e) {
                Collections.reverse(operands);
                operands.forEach(this::push);
                throw new InvalidParameterException("There aren't enough operands into the stack");
            }
        }

        return operands.iterator();
    }
}

Advertisement

Answer

This example adds a wrapper class around your stack implementation which provides an ObservableList that can be:

  1. Placed in a ListView AND
  2. Respond to bindings (see the pop button disable property binding in the example app).

For it to work, the mutation operations (e.g. push/pop) must be called on the wrapper class rather than the underlying class.

There are more efficient ways of implementing this (e.g. don’t subclass stack, instead implement the Deque interface and use an ObservableList directly as storage, extending ObservableListBase).

However, this is what I came up with that still kept your underlying class and it might be fine or easily adaptable for your purposes.

popup


public record ComplexNumber(double real, double imaginary) {}

The underlying stack implementation is unchanged from the class in your question.

import java.security.InvalidParameterException;
import java.util.*;

public class ComplexNumberStack extends Stack<ComplexNumber> {
    private static ComplexNumberStack instance = null;

    /**
     * This method provide an instance of a ComplexNumberStack.
     */
    public static ComplexNumberStack getInstance() {
        if (instance == null)
            instance = new ComplexNumberStack();
        return instance;
    }
    
    /**
     * This method provides a secure implementation of massive pop of operandNumber operands from the stack
     *
     * @param operandNumber specifies the number of operands to be taken from the stack
     * @return an iterator of complexNumber taken
     */
    public Iterator<ComplexNumber> getOperand(int operandNumber) {
        List<ComplexNumber> operands = new ArrayList<>();
        for (int i = 0; i < operandNumber; i++) {
            try {
                operands.add(pop());
            } catch (EmptyStackException e) {
                Collections.reverse(operands);
                operands.forEach(this::push);
                throw new InvalidParameterException("There aren't enough operands into the stack");
            }
        }
        return operands.iterator();
    }

}

Provides observability for the stack.

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.util.EmptyStackException;

public class ObservedComplexNumberStack {
    private final ObservableList<ComplexNumber> observableList;

    public ObservedComplexNumberStack(ComplexNumberStack complexNumberStack) {
        observableList = FXCollections.observableList(complexNumberStack);
    }

    public ComplexNumber pop() {
        if (observableList.size() == 0) {
            throw new EmptyStackException();
        }

        return observableList.remove(observableList.size() - 1);
    }

    public ComplexNumber push(ComplexNumber number) {
        observableList.add(number);

        return number;
    }

    public ObservableList<ComplexNumber> getObservableList() {
        return FXCollections.unmodifiableObservableList(observableList);
    }
}

Test application.

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.util.concurrent.ThreadLocalRandom;

public class StackApplication extends Application {

    @Override
    public void start(Stage stage) {
        ObservedComplexNumberStack stack = new ObservedComplexNumberStack(
                ComplexNumberStack.getInstance()
        );

        ListView<ComplexNumber> listView = new ListView<>(stack.getObservableList());
        listView.setPrefSize(80, 150);
        listView.setCellFactory(param -> new ListCell<>() {
            @Override
            protected void updateItem(ComplexNumber item, boolean empty) {
                super.updateItem(item, empty);

                if (empty || item == null) {
                    setText("");
                    return;
                }

                setText(String.format("%.2f + %.2fi", item.real(), item.imaginary()));
            }
        });

        Button push = new Button("Push");
        push.setOnAction(e -> {
            stack.push(randomNum());
            scrollToLastItem(listView);
        });
        Button pop = new Button("Pop");
        pop.setOnAction(e -> {
            stack.pop();
            scrollToLastItem(listView);
        });

        pop.disableProperty().bind(Bindings.isEmpty(listView.getItems()));

        HBox controls = new HBox(10, push, pop);
        VBox layout = new VBox(10, controls, listView);
        layout.setPadding(new Insets(10));

        Scene scene = new Scene(layout);
        stage.setScene(scene);
        stage.show();
    }

    private void scrollToLastItem(ListView<ComplexNumber> listView) {
        if (listView.getItems().size() > 0) {
            listView.scrollTo(listView.getItems().size() - 1);
        }
    }

    private ComplexNumber randomNum() {
        ThreadLocalRandom r = ThreadLocalRandom.current();

        return new ComplexNumber(r.nextDouble(9), r.nextDouble(9));
    }

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

Potential alteratives or improvements

hmm .. this looks a bit brittle – external code could change the stack without notifying the list (especially, since is a singleton and potential collaborators spread across the world

Yes, it’s true, its buyer beware 🙂

The alternate proposed solution of implementing Deque with operations directly on a backing observableList is probably preferred, but I’m not going to write that at this time (it would be quite a bit more work to do well).

The solution in this answer uses the FXCollections list wrapper, which, incidentally, on its own is another simple solution to this problem:

FXCollections.observableList(ComplexNumberStack.getInstance());

Though, it has some disadvantages:

  1. Changes to the underlying stack will not be observed (also true of the solution in this answer).
  2. You need to change the list to observe changes and the list won’t have push/pop ops (unlike the solution in this answer, which does at least provide push/pop ops which will be observed).

If you are interested in how the JavaFX framework implementation wrapper works, you can see the code for ObservableListWrapper.

If you wished to, you could copy a version of ObservableListWrapper to your own package (you don’t want to depend on com.sun code directly), then subclass it and adapt it to add your additional push/pop ops (as suggested by kleopatra in comments).

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement