我通过覆盖updateItem(T item,boolean empty)成功地创建了有效的ListView,但是现在我需要在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 here 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)发布于 2017-01-28 01:14:05
问题解决了。
构建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;
}
}发布于 2017-01-24 05:07:02
您不应该在cell构造函数中添加任何绑定或侦听器,因为列表单元格一旦创建,每次必须显示新项时列表视图都会重用它。当您在构造函数中绑定属性时,当列表视图更改它时,您将无法将其从旧项中移除。这就是为什么当你滚动或更改列表时,你会有奇怪的行为。
在更新自定义列表单元格中的项之前,必须删除双向绑定和侦听器。检查以下代码:
@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;
}
}https://stackoverflow.com/questions/41813388
复制相似问题