JavaFX ListView 自定义单元格
JavaFX ListView custom cells
我已尝试为 ListView 创建自定义单元格,但没有出现...
有我的菜单控制器,我在其中使用 ListView 必须包含项目:
FXML(只是一个空白页来测试):
<AnchorPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.exalow.easymind.ui.factory.ProjectCell" />
public class MenuController extends Controller implements Initializable {
private final ListView<Project> projectListView = new ListView<>();
public MenuController(EasyMind api, Stage stage) {
super(api, stage);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<Project> observableList = FXCollections.observableArrayList();
observableList.addAll(api.getProjects());
projectListView.setItems(observableList);
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/fxml/ProjectCell.fxml"));
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
projectListView.setCellFactory(cellFactory -> loader.getController());
}
还有我的手机控制器
public class ProjectCell extends ListCell<Project> implements Initializable {
private Project project;
@FXML
private AnchorPane root;
@FXML
private ImageView imageView;
@FXML
private Label nameLabel;
@FXML
private Label descriptionLabel;
@FXML
private Label createDateLabel;
@Override
public void initialize(URL location, ResourceBundle resources) {
setGraphic(root);
}
@Override
protected void updateItem(Project item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
if (project.hasImage()) {
imageView.setImage(new Image(project.getImageUrl()));
} else imageView.setImage(new Image(project.getDefaultImage()));
nameLabel.setText(item.getName());
descriptionLabel.setText(item.getDescription());
createDateLabel.setText(item.getCreateDate().toString());
}
this.project = item;
}
}
我没有错误,但是当我打开应用程序时我的 ListView 中没有单元格。
可重现的例子:
已创建任何 ProjectCell...
public class ProjectCellController {
@FXML
private AnchorPane root;
@FXML
private ImageView imageView;
@FXML
private Label nameLabel;
@FXML
private Label descriptionLabel;
@FXML
private Label createDateLabel;
private Project project;
public void setProject(Project project) {
if (project.hasImage()) {
imageView.setImage(new Image(project.getImageUrl()));
} else imageView.setImage(new Image(project.getDefaultImage()));
nameLabel.setText(project.getName());
descriptionLabel.setText(project.getDescription());
createDateLabel.setText(project.getCreateDate().toString());
this.project = project;
}
}
public class ProjectCell extends ListCell<Project> {
private AnchorPane root;
private ProjectCellController controller;
public ProjectCell() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/fxml/ProjectCell.fxml"));
root = loader.load();
controller = loader.getController();
} catch (IOException exc) {
throw new RuntimeException(exc);
}
}
@Override
protected void updateItem(Project project, boolean empty) {
if (empty || project == null) {
setGraphic(null);
} else {
controller.setProject(project);
setGraphic(root);
}
}
}
和 FXML(一个基本的空白锚定窗格):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.exalow.easymind.ui.controllers.ProjectCellController" />
你的代码有很多问题。
- 您正在为每个单元分配相同的单元实例(因为您的单元工厂 returns 每次调用时都是相同的实例)。这违反了 UI 元素只能添加到场景图中一次的规则。
- 如果在空单元格上调用
updateItem()
,则将图形设置为空。在任何情况下,您都不会将图形设置回任何其他内容。因此,如果一个单元格 曾经 用作空单元格(这通常发生在 ListView
首次创建或显示时),那么它将一直没有内容,即使以后变成非空。
- 控制器通常不应是 UI 组件。此规则有一些例外情况(例如使用 custom component pattern),但这通常不是一个好主意,因为它会混淆控制器和视图。
所以这里的策略应该是将cell factory设置为新建cell实例的工厂。该单元格可以在构造函数中加载 FXML 文件(因此每个单元格加载 UI 的新实例,但每个单元格只加载一次)。在 updateItem()
方法中,如果单元格为空,则将图形设置为空,否则根据项目配置控制器并将图形设置为从 FXML 加载的 UI。
类似于:
public class ProjectCellController {
@FXML
private AnchorPane root;
@FXML
private ImageView imageView;
@FXML
private Label nameLabel;
@FXML
private Label descriptionLabel;
@FXML
private Label createDateLabel;
public void setProject(Project project) {
if (project.hasImage()) {
imageView.setImage(new Image(project.getImageUrl()));
} else imageView.setImage(new Image(project.getDefaultImage()));
nameLabel.setText(project.getName());
descriptionLabel.setText(project.getDescription());
createDateLabel.setText(project.getCreateDate().toString());
}
}
然后
public class ProjectCell extends ListCell<Project> {
private AnchorPane root ;
private ProjectCellController controller ;
public ProjectCell() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/fxml/ProjectCell.fxml"));
root = loader.load();
controller = loader.getController();
} catch (IOException exc) {
// this is basically fatal, so just bail here:
throw new RuntimeException(exc);
}
}
@Override
protected void updateItem(Project project, boolean empty) {
super.updateItem(project, empty);
if (empty || project == null) {
setGraphic(null);
} else {
controller.setProject(project);
setGraphic(root);
}
}
}
最后,相应地更改 FXML 文件的 fx:controller
属性,并设置控制器工厂:
projectListView.setCellFactory(listView -> new ProjectCell());
这是一个简化但完整的示例:
Project.java:
public class Project {
private final String name ;
public Project(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}
Main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.ListView?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MainController">
<center>
<ListView fx:id="list" />
</center>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</BorderPane>
MainController.java:
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
public class MainController {
@FXML
private ListView<Project> list ;
public void initialize() {
for (int i = 1 ; i <= 100 ; i++) {
list.getItems().add(new Project("Project "+i));
}
list.setCellFactory(lv -> new ProjectCell());
}
}
ProjectCell.java:
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.ListCell;
public class ProjectCell extends ListCell<Project> {
private Parent root ;
private ProjectCellController controller ;
public ProjectCell() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("ProjectCell.fxml"));
root = loader.load();
controller = loader.getController() ;
} catch (IOException exc) {
throw new RuntimeException(exc);
}
}
@Override
protected void updateItem(Project project, boolean empty) {
super.updateItem(project, empty);
if (empty || project==null) {
setGraphic(null);
} else {
controller.setProject(project);
setGraphic(root);
}
}
}
ProjectCell.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<VBox xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ProjectCellController">
<children>
<Label fx:id="nameLabel" />
</children>
</VBox>
ProjectCellController.java:
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class ProjectCellController {
@FXML
private Label nameLabel ;
public void setProject(Project project) {
nameLabel.setText(project.getName());
}
}
和主要应用程序 class:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class App extends Application {
@Override
public void start(Stage stage) throws IOException {
Scene scene = new Scene(FXMLLoader.load(getClass().getResource("Main.fxml")), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
我已尝试为 ListView 创建自定义单元格,但没有出现...
有我的菜单控制器,我在其中使用 ListView 必须包含项目:
FXML(只是一个空白页来测试):
<AnchorPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.exalow.easymind.ui.factory.ProjectCell" />
public class MenuController extends Controller implements Initializable {
private final ListView<Project> projectListView = new ListView<>();
public MenuController(EasyMind api, Stage stage) {
super(api, stage);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<Project> observableList = FXCollections.observableArrayList();
observableList.addAll(api.getProjects());
projectListView.setItems(observableList);
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/fxml/ProjectCell.fxml"));
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
projectListView.setCellFactory(cellFactory -> loader.getController());
}
还有我的手机控制器
public class ProjectCell extends ListCell<Project> implements Initializable {
private Project project;
@FXML
private AnchorPane root;
@FXML
private ImageView imageView;
@FXML
private Label nameLabel;
@FXML
private Label descriptionLabel;
@FXML
private Label createDateLabel;
@Override
public void initialize(URL location, ResourceBundle resources) {
setGraphic(root);
}
@Override
protected void updateItem(Project item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
if (project.hasImage()) {
imageView.setImage(new Image(project.getImageUrl()));
} else imageView.setImage(new Image(project.getDefaultImage()));
nameLabel.setText(item.getName());
descriptionLabel.setText(item.getDescription());
createDateLabel.setText(item.getCreateDate().toString());
}
this.project = item;
}
}
我没有错误,但是当我打开应用程序时我的 ListView 中没有单元格。
可重现的例子:
已创建任何 ProjectCell...
public class ProjectCellController {
@FXML
private AnchorPane root;
@FXML
private ImageView imageView;
@FXML
private Label nameLabel;
@FXML
private Label descriptionLabel;
@FXML
private Label createDateLabel;
private Project project;
public void setProject(Project project) {
if (project.hasImage()) {
imageView.setImage(new Image(project.getImageUrl()));
} else imageView.setImage(new Image(project.getDefaultImage()));
nameLabel.setText(project.getName());
descriptionLabel.setText(project.getDescription());
createDateLabel.setText(project.getCreateDate().toString());
this.project = project;
}
}
public class ProjectCell extends ListCell<Project> {
private AnchorPane root;
private ProjectCellController controller;
public ProjectCell() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/fxml/ProjectCell.fxml"));
root = loader.load();
controller = loader.getController();
} catch (IOException exc) {
throw new RuntimeException(exc);
}
}
@Override
protected void updateItem(Project project, boolean empty) {
if (empty || project == null) {
setGraphic(null);
} else {
controller.setProject(project);
setGraphic(root);
}
}
}
和 FXML(一个基本的空白锚定窗格):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.exalow.easymind.ui.controllers.ProjectCellController" />
你的代码有很多问题。
- 您正在为每个单元分配相同的单元实例(因为您的单元工厂 returns 每次调用时都是相同的实例)。这违反了 UI 元素只能添加到场景图中一次的规则。
- 如果在空单元格上调用
updateItem()
,则将图形设置为空。在任何情况下,您都不会将图形设置回任何其他内容。因此,如果一个单元格 曾经 用作空单元格(这通常发生在ListView
首次创建或显示时),那么它将一直没有内容,即使以后变成非空。 - 控制器通常不应是 UI 组件。此规则有一些例外情况(例如使用 custom component pattern),但这通常不是一个好主意,因为它会混淆控制器和视图。
所以这里的策略应该是将cell factory设置为新建cell实例的工厂。该单元格可以在构造函数中加载 FXML 文件(因此每个单元格加载 UI 的新实例,但每个单元格只加载一次)。在 updateItem()
方法中,如果单元格为空,则将图形设置为空,否则根据项目配置控制器并将图形设置为从 FXML 加载的 UI。
类似于:
public class ProjectCellController {
@FXML
private AnchorPane root;
@FXML
private ImageView imageView;
@FXML
private Label nameLabel;
@FXML
private Label descriptionLabel;
@FXML
private Label createDateLabel;
public void setProject(Project project) {
if (project.hasImage()) {
imageView.setImage(new Image(project.getImageUrl()));
} else imageView.setImage(new Image(project.getDefaultImage()));
nameLabel.setText(project.getName());
descriptionLabel.setText(project.getDescription());
createDateLabel.setText(project.getCreateDate().toString());
}
}
然后
public class ProjectCell extends ListCell<Project> {
private AnchorPane root ;
private ProjectCellController controller ;
public ProjectCell() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/fxml/ProjectCell.fxml"));
root = loader.load();
controller = loader.getController();
} catch (IOException exc) {
// this is basically fatal, so just bail here:
throw new RuntimeException(exc);
}
}
@Override
protected void updateItem(Project project, boolean empty) {
super.updateItem(project, empty);
if (empty || project == null) {
setGraphic(null);
} else {
controller.setProject(project);
setGraphic(root);
}
}
}
最后,相应地更改 FXML 文件的 fx:controller
属性,并设置控制器工厂:
projectListView.setCellFactory(listView -> new ProjectCell());
这是一个简化但完整的示例:
Project.java:
public class Project {
private final String name ;
public Project(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}
Main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.ListView?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MainController">
<center>
<ListView fx:id="list" />
</center>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</BorderPane>
MainController.java:
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
public class MainController {
@FXML
private ListView<Project> list ;
public void initialize() {
for (int i = 1 ; i <= 100 ; i++) {
list.getItems().add(new Project("Project "+i));
}
list.setCellFactory(lv -> new ProjectCell());
}
}
ProjectCell.java:
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.ListCell;
public class ProjectCell extends ListCell<Project> {
private Parent root ;
private ProjectCellController controller ;
public ProjectCell() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("ProjectCell.fxml"));
root = loader.load();
controller = loader.getController() ;
} catch (IOException exc) {
throw new RuntimeException(exc);
}
}
@Override
protected void updateItem(Project project, boolean empty) {
super.updateItem(project, empty);
if (empty || project==null) {
setGraphic(null);
} else {
controller.setProject(project);
setGraphic(root);
}
}
}
ProjectCell.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<VBox xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ProjectCellController">
<children>
<Label fx:id="nameLabel" />
</children>
</VBox>
ProjectCellController.java:
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class ProjectCellController {
@FXML
private Label nameLabel ;
public void setProject(Project project) {
nameLabel.setText(project.getName());
}
}
和主要应用程序 class:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class App extends Application {
@Override
public void start(Stage stage) throws IOException {
Scene scene = new Scene(FXMLLoader.load(getClass().getResource("Main.fxml")), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}