JavaFX 8, ListView with Checkboxes

前端 未结 3 1174
眼角桃花
眼角桃花 2021-02-20 02:52

I want to create a simple ListView. I have figured out I can use the method setCellFactory() but I don\'t understand how to use them correctly. So far I have:

         


        
3条回答
  •  谎友^
    谎友^ (楼主)
    2021-02-20 03:24

    If you have a ListView, then each item in the ListView is a String, and the CheckBoxListCell.forListView(...) method expects a Callback>.

    In the pre-Java 8 way of thinking of things, a Callback> is an interface that defines a single method,

    public ObservableValue call(String s) ;
    

    So you need something that implements that interface, and you pass in an object of that type.

    The documentation also tells you how that callback is used:

    A Callback that, given an object of type T (which is a value taken out of the ListView.items list), will return an ObservableValue that represents whether the given item is selected or not. This ObservableValue will be bound bidirectionally (meaning that the CheckBox in the cell will set/unset this property based on user interactions, and the CheckBox will reflect the state of the ObservableValue, if it changes externally).

    (Since you have a ListView, here T is String.) So, for each element in the list view (each element is a String), the callback is used to determine an ObservableValue which is bidirectionally bound to the state of the checkbox. I.e. if the checkbox is checked, that property is set to true, and if unchecked it is set to false. Conversely, if the property is set to true (or false) programmatically, the checkbox is checked (or unchecked).

    The typical use case here is that the type of item in the ListView would have a BooleanProperty as part of its state. So you would typically use this with some kind of custom class representing your data, as follows with the inner Item class:

    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 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>() {
                @Override
                public ObservableValue 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);
        }
    }
    

    If you genuinely have a ListView, it's not really clear what the property you are setting by clicking on the check box would be. But there's nothing to stop you creating one in the callback just for the purpose of binding to the check box's selected state:

    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 listView = new ListView<>();
            for (int i = 1; i <= 20 ; i++) {
                String item = "Item "+i ;
                listView.getItems().add(item);
            }
    
            listView.setCellFactory(CheckBoxListCell.forListView(new Callback>() {
                @Override
                public ObservableValue 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);
        }
    }
    

    Notice that in this case, the BooleanPropertys are potentially being created and discarded frequently. This probably isn't a problem in practice, but it does mean the first version, with the dedicated model class, may perform better.

    In Java 8, you can simplify the code. Because the Callback interface has only one abstract method (making it a Functional Interface), you can think of a Callback> as a function which takes a Item and generates an ObservableValue. So the cell factory in the first example could be written with a lambda expression:

        listView.setCellFactory(CheckBoxListCell.forListView(item -> item.onProperty()));
    

    or, even more succinctly using method references:

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

提交回复
热议问题