首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >单元格构造函数中图形和itemProperty之间的JavaFX ListView单元格双向绑定

单元格构造函数中图形和itemProperty之间的JavaFX ListView单元格双向绑定
EN

Stack Overflow用户
提问于 2017-01-24 02:42:03
回答 2查看 1.1K关注 0票数 1

我通过覆盖updateItem(T item,boolean empty)成功地创建了有效的ListView,但是现在我需要在ListView中显示一个与我的域对象数据双向链接的节点。使用你请求编辑模式的现有系统真的很难使用(例如,进入编辑模式会从TextField中移除焦点,所以你不能通过选择TextField来进入焦点,它迫使我在单元格或图形中定义逻辑)。我正在尝试使用绑定,但我需要知道何时应该添加绑定、删除绑定、显示图形或不显示。

下面的代码可以让你更好地了解我想要什么,但它肯定不好或不起作用。我试着用几种方法得到想要的结果,唯一好的结果是它确实与我的域和它的逻辑正确链接。坏消息是,每当我添加、删除或滚动列表时,结果都是意想不到的,但肯定不能正常工作。

代码语言:javascript
复制
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

此外,当在文本字段中提供输入时,此错误似乎会时不时地出现

代码语言:javascript
复制
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)
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-01-28 01:14:05

问题解决了。

构建ListView是为了让图形反映底层数据,因此不需要将其绑定到模型。每当模型中发生更改时,UI都会更新。

因此,我必须确保我的模型中的数据更改使ObservableList能够意识到这些更改。为此,我使用了一个提取器:

代码语言:javascript
复制
ObservableList<Model> myModels = FXCollections.observableArrayList(model -> new Observable[]{model.nameProperty()});

接下来,我必须确保UI中的更改也会导致模型中的更改,因此我在cellFactory中使用了ChangeListener。这也可以放在扩展ListCell的类的构造函数中。我尝试过使用绑定,但它们似乎不起作用,不知道为什么,稍后我将研究它。我想在每个属性上使用比侦听器更简单的东西,但尽管如此,它还是可以工作的。

代码如下:

代码语言:javascript
复制
@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;
    }

}
票数 0
EN

Stack Overflow用户

发布于 2017-01-24 05:07:02

您不应该在cell构造函数中添加任何绑定或侦听器,因为列表单元格一旦创建,每次必须显示新项时列表视图都会重用它。当您在构造函数中绑定属性时,当列表视图更改它时,您将无法将其从旧项中移除。这就是为什么当你滚动或更改列表时,你会有奇怪的行为。

在更新自定义列表单元格中的项之前,必须删除双向绑定和侦听器。检查以下代码:

代码语言:javascript
复制
@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;
    }

}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41813388

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档