使用 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,它可以是:
- 放置在 ListView 中并且
- 响应绑定(请参阅示例应用中的弹出按钮禁用 属性 绑定)。
为了使其工作,必须在包装器 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());
不过,它也有一些缺点:
- 不会观察到底层堆栈的更改(此答案中的解决方案也是如此)。
- 您需要更改列表以观察变化,并且列表不会有 push/pop ops(与此答案中的解决方案不同,它至少提供 push/pop ops 将被观察).
如果您对 JavaFX 框架实现包装器的工作原理感兴趣,可以查看 ObservableListWrapper.
的代码
如果你愿意,你可以复制一个版本的 ObservableListWrapper 到你自己的包中(你不想直接依赖 com.sun 代码),然后 subclass 它并调整它添加额外的 push/pop 操作(正如 kleopatra 在评论中所建议的那样)。
我最近在使用 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,它可以是:
- 放置在 ListView 中并且
- 响应绑定(请参阅示例应用中的弹出按钮禁用 属性 绑定)。
为了使其工作,必须在包装器 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());
不过,它也有一些缺点:
- 不会观察到底层堆栈的更改(此答案中的解决方案也是如此)。
- 您需要更改列表以观察变化,并且列表不会有 push/pop ops(与此答案中的解决方案不同,它至少提供 push/pop ops 将被观察).
如果您对 JavaFX 框架实现包装器的工作原理感兴趣,可以查看 ObservableListWrapper.
的代码如果你愿意,你可以复制一个版本的 ObservableListWrapper 到你自己的包中(你不想直接依赖 com.sun 代码),然后 subclass 它并调整它添加额外的 push/pop 操作(正如 kleopatra 在评论中所建议的那样)。