问题
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 ChoiceBox
es) 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 Observable
s 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 Observable
s 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 ChoiceBox
es. 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