使用 JavaFX(ListView 或 TableView)绑定 java.util.Stack

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

我最近在使用 JavaFX,并希望通过将我的堆栈更新与 JavaFX 中的 ListView 或 TableView 绑定来实现观察者模式。但是,我不知道要对我的 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();
    }
}

此示例在您的堆栈实现周围添加了一个包装器 class,它提供了一个 ObservableList,它可以是:

  1. 放置在 ListView 中并且
  2. 响应绑定(请参阅示例应用中的弹出按钮禁用 属性 绑定)。

为了使其工作,必须在包装器 class 而不是底层 class.

上调用变异操作(例如 push/pop)

有更有效的实现方法(例如,不要子class堆栈,而是实现Deque接口并直接使用ObservableList作为存储,扩展ObservableListBase)。

然而,这就是我想出的,它仍然保留了您的基础 class,它可能很好或很容易适应您的目的。


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

基础堆栈实现与您问题中的 class 没有变化。

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

}

为堆栈提供可观察性。

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

测试应用程序。

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

潜在的替代或改进

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

是的,这是真的,买家要当心:-)

直接在支持的 observableList 上实现带有操作的 Deque 的替代建议解决方案可能是首选,但我现在不打算写它(要做得好需要更多的工作)。

这个答案中的解决方案使用了 FXCollections list wrapper,顺便说一下,它本身就是这个问题的另一个简单解决方案:

FXCollections.observableList(ComplexNumberStack.getInstance());

不过,它也有一些缺点:

  1. 不会观察到底层堆栈的更改(此答案中的解决方案也是如此)。
  2. 您需要更改列表以观察变化,并且列表不会有 push/pop ops(与此答案中的解决方案不同,它至少提供 push/pop ops 将被观察).

如果您对 JavaFX 框架实现包装器的工作原理感兴趣,可以查看 ObservableListWrapper.

的代码

如果你愿意,你可以复制一个版本的 ObservableListWrapper 到你自己的包中(你不想直接依赖 com.sun 代码),然后 subclass 它并调整它添加额外的 push/pop 操作(正如 kleopatra 在评论中所建议的那样)。