javalistviewjavafxcellrenderer

JavaFX custom listView


I wanted to ask the best way to make a ListView with custom objects in JavaFX, I want a list that each item looks like this:

enter image description here

I searched and found that most people do it with the cell factory method. Is ther any other way? For example with a custome fxml?

Here's my fmxl archive

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

<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="557.0" prefWidth="1012.0" style="-fx-background-color: #0288D1;" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="desktop_tasker.FXMLTaskerController">
   <children>
      <SplitPane dividerPositions="0.5" layoutX="-8.0" layoutY="35.0" prefHeight="529.0" prefWidth="1027.0" style="-fx-background-color: #EEEEEE;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="28.0">
        <items>
          <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
               <children>
                  <ListView fx:id="list_todo" onEditStart="#handleButtonAction" prefHeight="527.0" prefWidth="502.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
               </children></AnchorPane>
          <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="527.0" prefWidth="640.0">
               <children>
                  <ListView fx:id="list_done" onEditStart="#handleButtonAction" prefHeight="527.0" prefWidth="502.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
               </children></AnchorPane>
        </items>
      </SplitPane>
      <MenuBar prefHeight="30.0" prefWidth="1012.0">
        <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>
   </children>
</AnchorPane>

And here's my Object Task:

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by Usuario on 26/10/2017.
 */

public class Task implements Comparable<Task> {
    private String title;
    private String attendant;
    private String comment;
    private String description;
    private int priority;
    private String creationDate;
    private boolean state;
    private boolean visible;

    public Task(String title, String attendant, String comment, String description, int priority) {
        this.title = title;
        this.attendant = attendant;
        this.comment = comment;
        this.description = description;
        this.priority = priority;
        this.creationDate = setCreationDate();
        this.state = false;
        this.visible = true;
    }

    public Task(String title, String attendant, String comment, String description, int priority, String date, boolean state, boolean visible) {
        this.title = title;
        this.attendant = attendant;
        this.comment = comment;
        this.description = description;
        this.priority = priority;
        this.creationDate = date;
        this.state = state;
        this.visible = visible;
    }

    private static String setCreationDate() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date date = new Date();
        return sdf.format(date);
    }


    public boolean isVisible() {
        return visible;
    }

    public String getTitle() {
        return title;
    }

    public String getAttendant() {
        return attendant;
    }

    public String getComment() {
        return comment;
    }

    public String getDescription() {
        return description;
    }

    public int getPriority() {
        return priority;
    }

    public String getCreationDate() {
        return creationDate;
    }

    public boolean isState() {
        return state;
    }



    @Override
    public String toString() {
        return "Task{" +
                "title='" + title + '\'' +
                ", attendant='" + attendant + '\'' +
                ", comment='" + comment + '\'' +
                ", description='" + description + '\'' +
                ", priority=" + priority +
                ", creationDate='" + creationDate + '\'' +
                ", state=" + state +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Task task = (Task) o;

        if (priority != task.priority) return false;
        if (state != task.state) return false;
        if (title != null ? !title.equals(task.title) : task.title != null) return false;
        if (attendant != null ? !attendant.equals(task.attendant) : task.attendant != null)
            return false;
        if (comment != null ? !comment.equals(task.comment) : task.comment != null) return false;
        if (description != null ? !description.equals(task.description) : task.description != null)
            return false;
        return creationDate != null ? creationDate.equals(task.creationDate) : task.creationDate == null;
    }

    @Override
    public int hashCode() {
        int result = title != null ? title.hashCode() : 0;
        result = 31 * result + (attendant != null ? attendant.hashCode() : 0);
        result = 31 * result + (comment != null ? comment.hashCode() : 0);
        result = 31 * result + (description != null ? description.hashCode() : 0);
        result = 31 * result + priority;
        result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0);
        result = 31 * result + (state ? 1 : 0);
        return result;
    }


    @Override
    public int compareTo( Task task) {
        int comparePrior = this.getPriority() - (task.getPriority());
        return comparePrior == 0 ? this.getTitle().compareTo(task.getTitle()) : comparePrior;
    }

}

I want the items to look like individual containers, but I didn't find any way to do it. Any recommendations on what to use? And what's the best way to do it?


Solution

  • This is an example of using FXML to create custom ListCell

    First, a controller is created to inherit ListCell. In the constructor of this controller we load the FXML statement on its view.

    public class TaskCell extends ListCell<Task> {
    
        @FXML
        private Label titleLabel;
    
        @FXML
        private Label commentLabel;
    
        @FXML
        private Label descriptionLabel;
    
        public TaskCell() {
            loadFXML();
        }
    
        private void loadFXML() {
            try {
                FXMLLoader loader = new FXMLLoader(getClass().getResource("task_cell.fxml"));
                loader.setController(this);
                loader.setRoot(this);
                loader.load();
            }
            catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        protected void updateItem(Task item, boolean empty) {
            super.updateItem(item, empty);
    
            if(empty || item == null) {
                setText(null);
                setContentDisplay(ContentDisplay.TEXT_ONLY);
            }
            else {
                titleLabel.setText(item.getTitle());
                commentLabel.setText(item.getComment());
                descriptionLabel.setText(item.getDescription());
    
                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            }
        }
    }
    

    This is the view - task_cell.fxml

    <fx:root type="javafx.scene.control.ListCell" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml">
        <graphic>
            <VBox>
                <children>
                    <Label fx:id="titleLabel" />
                    <VBox>
                        <children>
                            <Label fx:id="commentLabel" />
                            <Label fx:id="descriptionLabel"/>
                        </children>
                    </VBox>
                </children>
            </VBox>
        </graphic>
    </fx:root>
    

    Then you need to create a cell factory that can be used to create cells.

    public class TaskCellFactory implements Callback<ListView<Task>, ListCell<Task>> {
    
        @Override
        public ListCell<Task> call(ListView<Task> param) {
            return new TaskCell();
        }
    }
    

    Once we have the factory, we can manually submit it to ListView using the ListView#setCellFactory()

    public class Controller {
    
        @FXML
        private ListView<Task> listView;
    
        @FXML
        private void initialize() {
            listView.setCellFactory(new TaskCellFactory());
        }
    }
    

    оr we can describe it in the FXML statement of the view that contains the ListView control

    <GridPane fx:controller="sample.Controller" xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
        <ListView fx:id="listView" GridPane.columnIndex="0" GridPane.rowIndex="0">
            <cellFactory>
                <TaskCellFactory />
            </cellFactory>
        </ListView>
    </GridPane>
    

    In the second version, the intellij idea marks the wrong line with <TaskCellFactory /> but this is clearly an error in the xml parser.