Using more than one choicebox to filter listview in JavaFX

白昼怎懂夜的黑 提交于 2021-02-19 05:35:20

问题


I am trying to create a filter function for my listview using multiple choiceboxes and I have no idea how to do it since i'm quite new to JavaFX.

I did some research and I hear using a filteredList is required but most of the examples online revolve around only using a textfield.

So this is my controller class

@FXML
private ChoiceBox<String> genre;
@FXML
private ChoiceBox<String> branch;
@FXML
private ChoiceBox<String> status;
@FXML
private ChoiceBox<String> company;
@FXML
private ListView<Movie> listView;

private ObservableList<Movie> movieList = FXCollections.observableArrayList();
private FilteredList<Movie> filteredData = new FilteredList<>(movieList, s -> true);

public Controller()  {
        vehicleList.addAll(
                new Movie("Horror" ,"IT", ,"Branch1", "Released", "Warner Bros"),
                new Movie("Action","John Wick 3" ,"Branch2", "Coming Soon", "Summit Entertainment")
        );

@Override
public void initialize(URL location, ResourceBundle resources) {
//I am planning to implement the filter here in the initialize method
        listView.setItems(filteredData);
        listView.setCellFactory(movieListView -> new MovieListViewCell());
}

This is the MovieListViewCell class

 @FXML
    private Label genre;

    @FXML
    private Label status;

    @FXML
    private GridPane gridPane;

    private FXMLLoader mLLoader;

    @Override
    protected void updateItem(Movie movie, boolean empty) {
        super.updateItem(vehicle, empty);

        if(empty || vehicle == null) {

            setText(null);
            setGraphic(null);

        } else {
            if (mLLoader == null) {
                mLLoader = new FXMLLoader(getClass().getResource("/application/ListCell.fxml"));
                mLLoader.setController(this);

                try {
                    mLLoader.load();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

            genre.setText(String.valueOf(vehicle.getMovie_Genre()));
            status.setText(String.valueOf(vehicle.getMovie_Status()));
            setText(null);
            setGraphic(gridPane);
        }

    }

This my main method where i run the whole UI

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("/application/MainPage.fxml"));
            Scene scene = new Scene(root,800,650);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

This is my FXML main Layout MainPage.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.String?>
<?import javafx.collections.FXCollections?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
   <center>
      <AnchorPane prefHeight="374.0" prefWidth="262.0" BorderPane.alignment="CENTER">
         <children>
            <ListView fx:id="listView" layoutX="106.0" layoutY="93.0" prefHeight="374.4" prefWidth="400.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
         </children>
      </AnchorPane>
   </center>
   <top>
      <MenuBar BorderPane.alignment="CENTER">
        <menus>
          <Menu mnemonicParsing="false" text="File">
            <items>
              <MenuItem mnemonicParsing="false" text="Close" />
            </items>
          </Menu>
          <Menu mnemonicParsing="false" text="Edit">
            <items>
              <MenuItem mnemonicParsing="false" text="Delete" />
            </items>
          </Menu>
          <Menu mnemonicParsing="false" text="Help">
            <items>
              <MenuItem mnemonicParsing="false" text="About" />
            </items>
          </Menu>
        </menus>
      </MenuBar>
   </top>
   <left>
      <VBox alignment="CENTER" prefHeight="368.0" prefWidth="149.0" BorderPane.alignment="CENTER">
         <children>
            <TextField fx:id="filterField" />
            <ChoiceBox fx:id="type" prefWidth="150.0">
                 <items>
                    <FXCollections fx:factory="observableArrayList">
                        <String fx:value="Horror" />
                        <String fx:value="Action" />
                    </FXCollections>
                 </items>
               <VBox.margin>
                  <Insets bottom="20.0" left="10.0" right="10.0" />
               </VBox.margin>
            </ChoiceBox>
            <ChoiceBox fx:id="branch" prefWidth="150.0">
                <items>
                    <FXCollections fx:factory="observableArrayList">
                        <String fx:value="branch1" />
                        <String fx:value="branch2" />
                        <String fx:value="branch3" />
                    </FXCollections>
                 </items>
               <VBox.margin>
                  <Insets bottom="20.0" left="10.0" right="10.0" />
               </VBox.margin>
            </ChoiceBox>
            <ChoiceBox fx:id="company" prefWidth="150.0">
                <items>
                    <FXCollections fx:factory="observableArrayList">
                        <String fx:value="Warner Bros" />
                        <String fx:value="Summit Entertainment" />
                    </FXCollections>
                 </items>
               <VBox.margin>
                  <Insets bottom="20.0" left="10.0" right="10.0" />
               </VBox.margin>
            </ChoiceBox>
            <ChoiceBox fx:id="status" prefWidth="150.0">
                <items>
                    <FXCollections fx:factory="observableArrayList">
                        <String fx:value="Released" />
                        <String fx:value="Coming Soon" />
                    </FXCollections>
                 </items>
               <VBox.margin>
                  <Insets left="10.0" right="10.0" />
               </VBox.margin>
            </ChoiceBox>
         </children>
      </VBox>
   </left>
</BorderPane>

This is ListCell.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>

<GridPane fx:id="gridPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="206.0" prefWidth="534.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
    <columnConstraints>
      <ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" maxWidth="350.0" minWidth="0.0" prefWidth="284.0" />
        <ColumnConstraints hgrow="SOMETIMES" maxWidth="509.0" minWidth="0.0" prefWidth="56.0" />
        <ColumnConstraints hgrow="SOMETIMES" maxWidth="543.0" minWidth="10.0" prefWidth="55.0" />
      <ColumnConstraints hgrow="SOMETIMES" maxWidth="543.0" minWidth="10.0" prefWidth="119.0" />
    </columnConstraints>
    <rowConstraints>
        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    </rowConstraints>
    <children>
      <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0">
         <children>
            <ImageView fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true">
               <image>
                  <Image url="@../../../../Desktop/Test.jpg" />
               </image>
            </ImageView>
         </children>
      </VBox>
      <VBox alignment="CENTER" prefHeight="212.0" prefWidth="95.0" GridPane.columnIndex="1">
         <children>
            <Label text="Genre:" />
            <Label text="Status:" />
         </children>
      </VBox>
      <VBox alignment="CENTER" prefHeight="183.0" prefWidth="80.0" GridPane.columnIndex="2">
         <children>
            <Label fx:id="genre" text="Label" />
            <Label fx:id="status" text="Label" />
         </children>
      </VBox>
      <VBox alignment="CENTER" prefHeight="225.0" prefWidth="132.0" GridPane.columnIndex="3">
         <children>
            <Button mnemonicParsing="false" text="Button" />
         </children>
      </VBox>
    </children>
</GridPane>

Hope someone can give me any solution for this. Thanks


回答1:


Using a FilteredList is the correct approach if you want to filter the items in memory. If you look at the documentation, you'll see that FilteredList has a predicate property. This property holds, unsurprisingly, a Predicate. The Predicate interface is a functional interface (which means it can be the target of a lambda expression or method reference) whose abstract method accepts a generic argument of type T and returns true or false based on arbitrary logic. When you set the predicate property of a FilteredList, it will use the Predicate to determine whether or not elements in the source ObservableList should be visible through the FilteredList view. As items are added and removed from the source ObservableList the FilteredList will automatically update.

Unfortunately, if you update any state the Predicate is based on (e.g. what choices are chosen in ChoiceBoxes) it will not automatically reapply the Predicate on all the elements of the source ObservableList. In other words, the FilteredList will not automatically update just because the Predicate's internal state has changed. This means every time you update the filter state you need to create a new Predicate and set the predicate property of the FilteredList. This is where creating and using a binding will be helpful. (Note: While Callable is part of the java.util.concurrent package, there is no concurrency happening here.)

To create the binding, we'll use Bindings.createObjectBinding(Callable,Observable...). That method accepts a Callable (another functional interface) and an array of Observable objects. The array of Observables are known as the dependencies of the created ObjectBinding and, when any of them are invalidated, causes the ObjectBinding to recompute its value based on the given Callable. In other words, the Callable is invoked every time one of the Observables are invalidated.

FilteredList<Movie> filteredList = movieList.filtered(null); // a null Predicate means "always true"

// moved to own variable for clarity (usually inlined with the method call)
Observable[] dependencies = {genre.valueProperty(), branch.valueProperty(), status.valueProperty(), company.valueProperty()};

ObjectBinding<Predicate<Movie>> binding = Bindings.createObjectBinding(() -> {
    Predicate<Movie> predicate = movie -> {
        // test "movie" based on the values of your ChoiceBoxes
    };
    return predicate;
}, dependencies);

filteredList.predicateProperty().bind(binding);

If you notice, the dependencies are the value properties of each of your ChoiceBoxes. Properties (i.e. instances of ReadOnlyProperty and Property) are implementations of Observable and are invalidated when their value has possibly changed. This means whenever the user changes their choice, the value property is invalidated, and the Predicate of the FilteredList is changed. When the Predicate changes the list is "re-filtered".




回答2:


So After thinking about it, I can use the simple version to demo and it should fit right in with your more complicated version. The key is creating a listener for each ChoiceBox. When the ChoiceBox selection change, update the FilteredList predicate.

Code you need

cbBranch.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) ->{
     System.out.println("Branch: " + newValue);
    filteredData.setPredicate((t) -> {

        switch(cbGenre.getValue())
        {
            case "All":
                switch(newValue)
                {
                    case "All":
                        return true;
                    default:
                        return newValue.equals(t.getBranch());                                
                }

            default:
                return newValue.equals(t.getBranch()) && cbGenre.getValue().equals(t.getGenre());
        }
    });
});

cbGenre.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue)->{
    System.out.println("Genre: " + newValue);
    filteredData.setPredicate((t) -> {
        switch(cbBranch.getValue())
        {
            case "All":
                switch(newValue)
                {
                    case "All":
                        return true;
                    default:
                        return newValue.equals(t.getGenre());
                }
            default:
                return newValue.equals(t.getGenre()) && cbGenre.getValue().equals(t.getBranch());
        }
    });
});

Full Example

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
 *
 * @author Sedrick
 */
public class JavaFXApplication38 extends Application {

    @Override
    public void start(Stage primaryStage) {
        ChoiceBox<String> cbGenre = new ChoiceBox();
        cbGenre.getItems().addAll("All", "Horror", "Action");
        cbGenre.setValue("All");
        ChoiceBox<String> cbBranch = new ChoiceBox();
        cbBranch.getItems().addAll("All", "Branch1", "Branch2");
        cbBranch.setValue("All");

        ObservableList<Movie> movieList = FXCollections.observableArrayList();
        movieList.add(new Movie("Horror", "IT", "Branch1", "Released", "Warner Bros"));
        movieList.add(new Movie("Action","John Wick 3" ,"Branch2", "Coming Soon", "Summit Entertainment"));
        FilteredList<Movie> filteredData = new FilteredList<>(movieList, s -> true);

        ListView<Movie> listView = new ListView<>(filteredData);
        listView.setCellFactory((ListView<Movie> param) -> {
            ListCell<Movie> cell = new ListCell<Movie>() {                
                @Override
                protected void updateItem(Movie item, boolean empty) {
                    super.updateItem(item, empty);
                    if (item != null) {
                        setText(item.getTitle());
                    } else {
                        setText("");
                    }
                }
            };

            return cell;
        });

        cbBranch.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) ->{
             System.out.println("Branch: " + newValue);
            filteredData.setPredicate((t) -> {

                switch(cbGenre.getValue())
                {
                    case "All":
                        switch(newValue)
                        {
                            case "All":
                                return true;
                            default:
                                return newValue.equals(t.getBranch());                                
                        }

                    default:
                        return newValue.equals(t.getBranch()) && cbGenre.getValue().equals(t.getGenre());
                }
            });
        });
        cbGenre.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue)->{
            System.out.println("Genre: " + newValue);
            filteredData.setPredicate((t) -> {
                switch(cbBranch.getValue())
                {
                    case "All":
                        switch(newValue)
                        {
                            case "All":
                                return true;
                            default:
                                return newValue.equals(t.getGenre());
                        }
                    default:
                        return newValue.equals(t.getGenre()) && cbGenre.getValue().equals(t.getBranch());
                }
            });
        });

        HBox root = new HBox(new VBox(cbBranch, cbGenre), listView);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}

Update! Code now has a better Predicate to handle more ChoiceBoxes.

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
 *
 * @author Sedrick
 */
public class JavaFXApplication38 extends Application {

    @Override
    public void start(Stage primaryStage) {
        ChoiceBox<String> cbGenre = new ChoiceBox();
        cbGenre.getItems().addAll("All", "Horror", "Action");
        cbGenre.setValue("All");
        ChoiceBox<String> cbBranch = new ChoiceBox();
        cbBranch.getItems().addAll("All", "Branch1", "Branch2");
        cbBranch.setValue("All");
        ChoiceBox<String> cbRelease = new ChoiceBox();
        cbRelease.getItems().addAll("All", "Released", "Coming Soon");
        cbRelease.setValue("All");
        ChoiceBox<String> cbParentCompany = new ChoiceBox();
        cbParentCompany.getItems().addAll("All", "Warner Bros", "Summit Entertainment");
        cbParentCompany.setValue("All");
        ChoiceBox<String> cbTitle = new ChoiceBox();
        cbTitle.getItems().addAll("All", "IT", "John Wick 3");
        cbTitle.setValue("All");


        ObservableList<Movie> movieList = FXCollections.observableArrayList();
        movieList.add(new Movie("Horror", "IT", "Branch1", "Released", "Warner Bros"));
        movieList.add(new Movie("Action","John Wick 3" ,"Branch2", "Coming Soon", "Summit Entertainment"));
        FilteredList<Movie> filteredData = new FilteredList<>(movieList, s -> true);

        ListView<Movie> listView = new ListView<>(filteredData);
        listView.setCellFactory((ListView<Movie> param) -> {
            ListCell<Movie> cell = new ListCell<Movie>() {                
                @Override
                protected void updateItem(Movie item, boolean empty) {
                    super.updateItem(item, empty);
                    if (item != null) {
                        setText(item.getTitle());
                    } else {
                        setText("");
                    }
                }
            };

            return cell;
        });
        cbRelease.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) ->{
             System.out.println("Released: " + newValue);
            filteredData.setPredicate((t) -> {               
                return (cbBranch.getValue().equals("All") ? true : t.getBranch().equals(cbBranch.getValue())) && 
                       (cbGenre.getValue().equals("All") ? true : t.getGenre().equals(cbGenre.getValue())) && 
                       (cbParentCompany.getValue().equals("All") ? true : t.getParentCompany().equals(cbParentCompany.getValue())) &&
                       (cbTitle.getValue().equals("All") ? true : t.getTitle().equals(cbTitle.getValue())) && 
                       (cbRelease.getValue().equals("All") ? true : t.getRelease().equals(cbRelease.getValue()));
            });
        });
        cbBranch.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) ->{
             System.out.println("Branch: " + newValue);

            filteredData.setPredicate((t) -> {
                return (cbBranch.getValue().equals("All") ? true : t.getBranch().equals(newValue)) && 
                       (cbGenre.getValue().equals("All") ? true : t.getGenre().equals(cbGenre.getValue())) && 
                       (cbParentCompany.getValue().equals("All") ? true : t.getParentCompany().equals(cbParentCompany.getValue())) &&
                       (cbTitle.getValue().equals("All") ? true : t.getTitle().equals(cbTitle.getValue())) && 
                       (cbRelease.getValue().equals("All") ? true : t.getRelease().equals(cbRelease.getValue()));
            });
        });
        cbGenre.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue)->{
            System.out.println("Genre: " + newValue);
            filteredData.setPredicate((t) -> {
                return (cbBranch.getValue().equals("All") ? true : t.getBranch().equals(cbBranch.getValue())) && 
                       (cbGenre.getValue().equals("All") ? true : t.getGenre().equals(cbGenre.getValue())) && 
                       (cbParentCompany.getValue().equals("All") ? true : t.getParentCompany().equals(cbParentCompany.getValue())) &&
                       (cbTitle.getValue().equals("All") ? true : t.getTitle().equals(cbTitle.getValue())) && 
                       (cbRelease.getValue().equals("All") ? true : t.getRelease().equals(cbRelease.getValue()));
            });
        });

        cbParentCompany.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue)->{
            System.out.println("parent company: " + newValue);
            filteredData.setPredicate((t) -> {
                return (cbBranch.getValue().equals("All") ? true : t.getBranch().equals(cbBranch.getValue())) && 
                       (cbGenre.getValue().equals("All") ? true : t.getGenre().equals(cbGenre.getValue())) && 
                       (cbParentCompany.getValue().equals("All") ? true : t.getParentCompany().equals(cbParentCompany.getValue())) &&
                       (cbTitle.getValue().equals("All") ? true : t.getTitle().equals(cbTitle.getValue())) && 
                       (cbRelease.getValue().equals("All") ? true : t.getRelease().equals(cbRelease.getValue()));
            });
        });

        cbTitle.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue)->{
            System.out.println("title: " + newValue);
            filteredData.setPredicate((t) -> {
                return (cbBranch.getValue().equals("All") ? true : t.getBranch().equals(cbBranch.getValue())) && 
                       (cbGenre.getValue().equals("All") ? true : t.getGenre().equals(cbGenre.getValue())) && 
                       (cbParentCompany.getValue().equals("All") ? true : t.getParentCompany().equals(cbParentCompany.getValue())) &&
                       (cbTitle.getValue().equals("All") ? true : t.getTitle().equals(cbTitle.getValue())) && 
                       (cbRelease.getValue().equals("All") ? true : t.getRelease().equals(cbRelease.getValue()));
            });
        });

        HBox root = new HBox(new VBox(cbBranch, cbGenre, cbRelease, cbParentCompany, cbTitle), listView);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}


来源:https://stackoverflow.com/questions/56248446/using-more-than-one-choicebox-to-filter-listview-in-javafx

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!