我设法通过覆盖ListView
来使updateItem(T item,boolean empty)
工作,但现在我需要在ListView
节点中显示与我的域对象数据双向链接的节点。使用您请求编辑模式的现有系统非常难以使用(例如,进入编辑模式会从TextField
移除焦点,因此您无法通过选择TextField
来输入焦点,这会强制我定义单元格或图形中的逻辑)。我正在尝试使用绑定来完成这项工作,但我需要知道何时应该添加绑定,删除它们,显示图形与否。
以下代码可以让您更好地了解我的需求,但它绝对不是好事或有效。我试图通过几种方式获得所需的结果,唯一的好结果是它确实与我的域及其逻辑正确链接。坏消息是每当我添加,删除或滚动该列表时,结果都是意外的,但绝对不能正常工作。
MyCell extends ListCell<MyModel> {
private SomethingThatExtendsNode view = new SomethingThatExtendsNode();
public MyCell(){
BooleanBinding remove = itemProperty().isNull()or(emptyProperty());
remove.addListener((observable, oldValue, newValue) -> {setGraphic(null)});
BooleanBinding exists = itemProperty().isNotNull().and(emptyProperty().not);
exists.addListener((observable, oldValue, newValue) -> {
if (newValue){
setGraphic(view);
MonadicObservableValue<MyModel> model = EasyBind.monadic(itemProperty());
view.getTextField1.textProperty().bindBidirectional(model.selectProperty(MyModel::text1));
//etc..
}
});
}
}
此外,这种设计是正确的方法吗?我希望我的观点只是让他们的属性绑定到域。业务逻辑主要通过更多绑定在域中。
编辑:尝试按照答案进行设置,但仍然行为不规律,也许问题必须在其他地方。 MCVE的Git在这里https://github.com/PopescuStefanRadu/JavaFX-listView-MCVE
此外,在文本字段
中提供输入时,此错误似乎时不时出现Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.scene.control.TextInputControl$TextProperty.fireValueChangedEvent(TextInputControl.java:1389)
at javafx.scene.control.TextInputControl$TextProperty.markInvalid(TextInputControl.java:1393)
at javafx.scene.control.TextInputControl$TextProperty.controlContentHasChanged(TextInputControl.java:1332)
at javafx.scene.control.TextInputControl$TextProperty.access$1600(TextInputControl.java:1300)
at javafx.scene.control.TextInputControl.lambda$new$162(TextInputControl.java:139)
at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:137)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.scene.control.TextField$TextFieldContent.insert(TextField.java:87)
at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:1204)
at javafx.scene.control.TextInputControl.updateContent(TextInputControl.java:556)
at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:548)
at com.sun.javafx.scene.control.skin.TextFieldSkin.replaceText(TextFieldSkin.java:576)
at com.sun.javafx.scene.control.behavior.TextFieldBehavior.replaceText(TextFieldBehavior.java:202)
at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.defaultKeyTyped(TextInputControlBehavior.java:238)
at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:139)
at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218)
at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127)
at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:248)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
at com.sun.glass.ui.View.notifyKey(View.java:966)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication.lambda$null$49(GtkApplication.java:139)
at java.lang.Thread.run(Thread.java:745)
答案 0 :(得分:0)
您永远不应该在单元格构造函数中添加任何绑定或侦听器,因为每次必须显示新项目时,列表单元格一旦创建就会被列表视图重用。在构造函数中绑定属性时,当列表视图更改它时,您将无法从旧项中删除它。当你滚动或更改列表时,这就是你有奇怪行为的原因。
在更新自定义列表单元格中的项目之前,必须删除双向绑定和侦听器。请检查以下代码:
@Override
public void start(Stage primaryStage) {
ObservableList<Model> myModels = FXCollections.observableArrayList(
IntStream.range(0, 100).mapToObj(i -> new Model("MyModel " + i)).collect(Collectors.toList())
);
ListView<Model> modelListView = new ListView<>();
modelListView.setCellFactory(c -> new MyListCell());
modelListView.setItems(myModels);
StackPane root = new StackPane();
root.getChildren().addAll(modelListView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Custom list cell");
primaryStage.setScene(scene);
primaryStage.show();
}
private class MyListCell extends ListCell<Model> {
private HBox content = new HBox();
private TextField textField = new TextField();
private Button actionButton = new Button("Action");
private final ChangeListener<String> textListener = (ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
checkIfDisableButton(newValue);
};
public MyListCell() {
content.getChildren().addAll(textField, actionButton);
content.setSpacing(10);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(content);
}
@Override
protected void updateItem(Model item, boolean empty) {
if (getItem() != null) { // get old item
//remove all bidirectional bindings and listeners
textField.textProperty().unbindBidirectional(getItem().nameProperty());
getItem().nameProperty().removeListener(textListener);
}
super.updateItem(item, empty);
//new item
if (item == null || empty) {
setText(null);
setGraphic(null);
} else {
setGraphic(content);
checkIfDisableButton(item.getName());
item.nameProperty().addListener(textListener);
textField.textProperty().bindBidirectional(item.nameProperty());
actionButton.setOnAction(e -> {
System.out.println(item.getName());
});
}
}
private void checkIfDisableButton(String value) {
actionButton.setDisable("MyModel 2".equals(value));
}
}
private class Model {
StringProperty name = new SimpleStringProperty();
public Model(String name) {
this.name.set(name);
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return this.name;
}
}
答案 1 :(得分:0)
问题解决了。
构建 ListView
以便图形反映基础数据,因此无需将其绑定到模型。每当模型发生变化时,UI都会更新。
因此,我必须确保模型数据的更改使ObservableList
了解更改。为此,我使用了一个提取器:
ObservableList<Model> myModels = FXCollections.observableArrayList(model -> new Observable[]{model.nameProperty()});
接下来我必须确保UI中的更改也会导致模型发生变化,因此我在cellFactory中使用了ChangeListener
。这也可以放在扩展ListCell的类的构造函数中。我试图使用绑定但它们似乎不起作用,不知道为什么,稍后会调查它。我想在每一个属性上使用比听众更冗长的东西,但它仍然有用。
这是代码:
@Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Model> myModels = FXCollections.observableArrayList(model -> new Observable[]{model.nameProperty()});
ListView<Model> modelListView = new ListView<>();
modelListView.setCellFactory(c -> {
MyListCell cell = new MyListCell();
//TODO is there a way to replace this listener with something less verbose?
cell.getTextField().textProperty().addListener((observable, oldValue, newValue) -> {
if (newValue!=null){
cell.getItem().setName(newValue);
}
});
return cell;
});
modelListView.setItems(myModels);
myModels.add(new Model());
Button addButton = new Button("Add");
addButton.setOnAction(event -> myModels.add(new Model()));
StackPane root = new StackPane();
root.getChildren().addAll(new HBox(20, modelListView, addButton));
primaryStage.setTitle("ListView with bidirectional binding");
primaryStage.setScene(new Scene(root, 600, 500));
primaryStage.show();
}
private class Model {
private StringProperty name = new SimpleStringProperty(this, "name", "");
private ReadOnlyStringWrapper computedName = new ReadOnlyStringWrapper(this, "computedName");
public Model() {
computedName.bind(Bindings.createStringBinding(() -> name.get().toUpperCase(), name));
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getComputedName() {
return computedName.get();
}
public ReadOnlyStringWrapper computedNameProperty() {
return computedName;
}
}
private class MyListCell extends ListCell<Model> {
private HBox content = new HBox(10);
private TextField textField = new TextField();
private Label label = new Label();
public MyListCell() {
content.getChildren().addAll(textField, label);
}
@Override
protected void updateItem(Model item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
setGraphic(content);
textField.textProperty().set(item.getName());
label.textProperty().set(item.getComputedName());
}
}
public TextField getTextField() {
return textField;
}
}