JavaFX 8,带有复选框的ListView

11

我想创建一个简单的ListView。我已经想到可以使用 setCellFactory() 方法,但我不知道如何正确使用它们。目前为止,我有:

myListView.setCellFactory(CheckBoxListCell.forListView(property));

“property”是一个叫做回调函数(Callback)的东西,我想回调函数可能与双向绑定有关。因此我创建了一个

property = new CallBack<String, ObservableValue<Boolean>>();

我的编译器告诉我,如果我创建一个新的回调函数,就需要重写方法调用。

但是我现在卡住了。我应该如何处理那个方法调用?我可以实现它,但是应该返回什么或者将其用于什么?我想在任何列表项上单击复选框并在控制台中显示“hi”。

3个回答

33
如果您有一个ListView<String>,那么ListView中的每个项目都是一个String,而CheckBoxListCell.forListView(...)方法需要一个Callback<String,ObservableValue<Boolean>>
在Java 8之前,Callback<String,ObservableValue<Boolean>>是定义单个方法的接口。
public ObservableValue<Boolean> call(String s) ;

因此,您需要一个实现该接口的东西,并传入该类型的对象。

文档还告诉您如何使用该回调:

给定类型为 T 的对象(这是从 ListView.items 列表中取出的值),将返回表示是否选择给定项目的 ObservableValue。该 ObservableValue 将被双向绑定(这意味着单元格中的复选框将根据用户交互设置/取消设置此属性,如果该属性在外部更改,则复选框将反映 ObservableValue 的状态)。

(由于您有一个 ListView<String>,因此此处的 TString。)因此,对于列表视图中的每个元素(每个元素都是 String),将使用回调来确定与复选框状态双向绑定的 ObservableValue<Boolean>。即,如果选中复选框,则该属性设置为 true,如果未选中,则设置为 false。反过来,如果以编程方式将该属性设置为 true(或 false),则复选框将被选中(或未选中)。

此处的典型用例是,ListView 中的项目类型将具有 BooleanProperty 作为其状态的一部分。因此,通常会使用自定义类来表示数据,如下所示的内部 Item 类:

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class ListViewWithCheckBox extends Application {

    @Override
    public void start(Stage primaryStage) {
        ListView<Item> listView = new ListView<>();
        for (int i=1; i<=20; i++) {
            Item item = new Item("Item "+i, false);

            // observe item's on property and display message if it changes:
            item.onProperty().addListener((obs, wasOn, isNowOn) -> {
                System.out.println(item.getName() + " changed on state from "+wasOn+" to "+isNowOn);
            });

            listView.getItems().add(item);
        }

        listView.setCellFactory(CheckBoxListCell.forListView(new Callback<Item, ObservableValue<Boolean>>() {
            @Override
            public ObservableValue<Boolean> call(Item item) {
                return item.onProperty();
            }
        }));

        BorderPane root = new BorderPane(listView);
        Scene scene = new Scene(root, 250, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static class Item {
        private final StringProperty name = new SimpleStringProperty();
        private final BooleanProperty on = new SimpleBooleanProperty();

        public Item(String name, boolean on) {
            setName(name);
            setOn(on);
        }

        public final StringProperty nameProperty() {
            return this.name;
        }

        public final String getName() {
            return this.nameProperty().get();
        }

        public final void setName(final String name) {
            this.nameProperty().set(name);
        }

        public final BooleanProperty onProperty() {
            return this.on;
        }

        public final boolean isOn() {
            return this.onProperty().get();
        }

        public final void setOn(final boolean on) {
            this.onProperty().set(on);
        }

        @Override
        public String toString() {
            return getName();
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}

如果你真正拥有一个 ListView<String>,那么通过点击复选框设置的属性并不是很清晰。但你完全可以在回调函数中创建一个仅用于绑定复选框选择状态的属性:

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class ListViewWithStringAndCheckBox extends Application {

    @Override
    public void start(Stage primaryStage) {
        ListView<String> listView = new ListView<>();
        for (int i = 1; i <= 20 ; i++) {
            String item = "Item "+i ;
            listView.getItems().add(item);
        }

        listView.setCellFactory(CheckBoxListCell.forListView(new Callback<String, ObservableValue<Boolean>>() {
            @Override
            public ObservableValue<Boolean> call(String item) {
                BooleanProperty observable = new SimpleBooleanProperty();
                observable.addListener((obs, wasSelected, isNowSelected) -> 
                    System.out.println("Check box for "+item+" changed from "+wasSelected+" to "+isNowSelected)
                );
                return observable ;
            }
        }));

        BorderPane root = new BorderPane(listView);
        Scene scene = new Scene(root, 250, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

请注意,在这种情况下,BooleanProperty 可能会频繁地被创建和丢弃。实际上这可能不是问题,但这意味着第一个版本(有专门的模型类)可能执行得更好。
在Java 8中,您可以简化代码。由于 Callback 接口只有一个抽象方法(使其成为一个函数式接口),您可以将 Callback<Item, ObservableValue<Boolean>> 看作一个函数,它接受一个 Item 并生成一个 ObservableValue<Boolean>。因此,第一个示例中的单元格工厂可以使用lambda表达式编写:
    listView.setCellFactory(CheckBoxListCell.forListView(item -> item.onProperty()));

或者,更加简洁地使用方法引用

    listView.setCellFactory(CheckBoxListCell.forListView(Item::onProperty));

哇,我真的得说这个帮助不仅好而且太棒了! 我理解你写的所有内容,它实际上以任何方式都解决了我的问题。非常感谢你,詹姆斯! 我会尽快投赞成票(因为我是新手,还缺少15个声望)。 - jan

3
    listView.setCellFactory(CheckBoxListCell.forListView(new Callback<String, ObservableValue<Boolean>>() {
        @Override
        public ObservableValue<Boolean> call(String item) {
            BooleanProperty observable = new SimpleBooleanProperty();
            observable.addListener((obs, wasSelected, isNowSelected) -> 
                System.out.println("Check box for "+item+" changed from "+wasSelected+" to "+isNowSelected)
            );
            return observable ;
        }
    }));

谢谢!这帮助我解决了我的问题。

如果我限制了列表的高度,这就有一个缺陷。而且我有数百个列表项。我使用复选框从顶部选择一些项目,当我向下滚动到底部然后向上滚动时,顶部选择的项目会被刷新并取消选择。 - Asif Ashraf

0

感谢之前的回答。 我错过了setCellValueFactory不需要的信息,但是在setCellFactory中也应该完成值的分配。这是我的方法(很多都是从以前的解决方案复制的)。

public TreeTableColumn<RowContainer, Boolean> treetblcolHide;
...
    treetblcolHide.setCellFactory(CheckBoxTreeTableCell.<RowContainer, Boolean>forTreeTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
        @Override
        public ObservableValue<Boolean> call(final Integer param) {
            final RowContainer rowitem = treetblcolHide.getTreeTableView().getTreeItem(param).getValue();
            BooleanProperty observable = new SimpleBooleanProperty();
            observable.addListener(new ChangeListener<Boolean>() {
                                       @Override
                                       public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                                           rowitem.setHideMenuItem(newValue.toString());
                                       }
                                   }
            );
            observable.setValue(Boolean.parseBoolean(rowitem.getHideMenuItem()));
            return observable ;
        }
    }));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接