JavaFX 8 Custom ListView Cells it's Evil


Question

I have custom ListView cell i have HBox with two controls:

  1. Label with name field
  2. It's must generated from context of object

For example i set ListView items array of Class Fields, if field type Boolean i create CheckBox, if String i create TextField and etc.

Problem is: i get Field object only in method updateItem() - and only there i can create my controls.

In JavaFX 7 all works fine, but on Java 8 - i dont see my controls.

Is exists correct way to resolve my case?

UPDATE 1

Here complete example for replicate problem:

package javafx8listviewexample;

import java.lang.reflect.Field;
import java.net.URL;
import java.util.Date;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.DatePicker;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.util.Callback;

/**
 *
 * @author dmitrynelepov
 */
public class FXMLDocumentController implements Initializable {

    static class TestClassForListView {

        public String fieldString;
        public Date fieldDate;
    }

    static class MyListCell extends ListCell<Field> {

        /**
         * As in tutorial
         *
         * @param t
         * @param bln
         */
        @Override
        protected void updateItem(Field t, boolean bln) {
            super.updateItem(t, bln);
            if (t != null) {
                if (t.getType().equals(String.class)) {
                    System.out.println("Draw string");
                    setGraphic(new TextField(t.getName()));
                } else if (t.getType().equals(Date.class)) {
                    System.out.println("Draw Date");
                    setGraphic(new DatePicker());

                }
            }
        }

    }

    @FXML
    private ListView<Field> listView;

    @FXML
    private void handleButtonAction(ActionEvent event) {
        this.listView.getItems().clear();
        this.listView.setItems(
                FXCollections.observableArrayList(
                        TestClassForListView.class.getFields()
                )
        );
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        this.listView.setCellFactory(new Callback<ListView<Field>, ListCell<Field>>() {

            @Override
            public ListCell<Field> call(ListView<Field> p) {
                return new MyListCell();
            }
        });

    }

}

Here screenshot of program JDK 7

enter image description here

as u can see i TextField can be focused and edited.

But if i run this example with JDK 8 i got

enter image description here

as here u can see TextField cant be focused and edited.

1
8
11/20/2013 7:32:43 AM

Accepted Answer

Looks like you want a ControlsFX PropertySheet:

propertysheet


A similar implementation, is in the answer to JavaFX 2 TableView : different cell factory depending on the data inside the cell. Even though the answer to that question is based on TableView, both ListView and TableView virtualized controls, so the implementation concept is somewhat similar to using a ListView with an HBox as outlined in your question.

tableproperty


Update based on sample code from question.

I still think that a ControlsFX PropertySheet appears to be the most appropriate solution for what you are trying to accomplish. Using a Virtualized Control like a ListView for such a task simply makes things more complicated than they need to be.

A full solution for a ListView based property editor is a pretty complex thing and outside the scope of what can be reasonably provided in a StackOverflow answer.

The sample code you provide in your question has some issues. The ListView is a virtualized control so you should not create new graphic nodes to place inside the ListView all the time that update is being called. What happens is that a TextField gets focus, then update is called on the ListView and you create a new TextField and by default the new TextField does not have focus.

I think the ListView itself has some implementation issues for your particular case and is calling update too many times. Regardless of this, you should write your code so that you can appropriately handle update being called multiple times on a single cell and. if you do so, it won't matter if ListView calls your method more times than it needs to.

Here is some sample code which may help you make more progress. I don't believe the sample code is a complete solution to your issue, it is certainly not provided as a comprehensive property editor for Java objects, but perhaps it may give you some inspiration to help improve and implement your code (assuming you decide to continue attempting to solve this problem in this manner). If you continue using ListView, you may want to look into the editing routines for ListView (similar to what is defined for editing table cells in the Oracle JavaFX tutorials).

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;

import java.lang.reflect.Field;
import java.time.LocalDate;

/**
 * @author dmitrynelepov
 * Modified by Jewelsea
 */
public class EvilHasSurvived extends Application {

    static class TestClassForListView {
        public String fieldString;
        public LocalDate fieldDate;

        @Override
        public String toString() {
            return "TestClassForListView{" +
                    "fieldString='" + fieldString + '\'' +
                    ", fieldDate=" + fieldDate +
                    '}';
        }
    }

    static class MyListCell extends ListCell<Field> {
        private TextField textField;
        private DatePicker datePicker;
        private Object editedObject;
        private ChangeListener<Boolean> editCommitHandler;

        public MyListCell(Object editedObject) {
            this.editedObject = editedObject;
            setContentDisplay(ContentDisplay.RIGHT);
        }

        @Override
        protected void updateItem(Field t, boolean bln) {
            super.updateItem(t, bln);

            if (datePicker != null) {
                datePicker.focusedProperty().removeListener(editCommitHandler);
            }
            if (textField != null) {
                textField.focusedProperty().removeListener(editCommitHandler);
            }

            if (t == null) {
                setText(null);
                setGraphic(null);
                return;
            }

            if (t.getType().equals(String.class)) {
                if (textField == null) {
                    textField = new TextField();
                }

                editCommitHandler = (observable, oldValue, newValue) -> {
                    try {
                        t.set(editedObject, textField.getText());
                        System.out.println(editedObject + " for " + textField + " value " + textField.getText());
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                };
                textField.focusedProperty().addListener(editCommitHandler);

                setText(t.getName());
                setGraphic(textField);
            } else if (t.getType().equals(LocalDate.class)) {
                if (datePicker == null) {
                    datePicker = new DatePicker();
                }

                editCommitHandler = (observable, oldValue, newValue) -> {
                    try {
                        t.set(editedObject, datePicker.getValue());
                        System.out.println(editedObject + " for " + datePicker + " value " + datePicker.getValue());
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                };
                datePicker.focusedProperty().addListener(editCommitHandler);

                setText(t.getName());
                setGraphic(datePicker);
            }
        }
    }

    @Override
    public void start(Stage stage) throws Exception {
        ListView<Field> listView = new ListView<>();
        listView.setItems(
            FXCollections.observableArrayList(
                TestClassForListView.class.getFields()
            )
        );
        TestClassForListView testObject = new TestClassForListView();
        listView.setCellFactory(p -> new MyListCell(testObject));

        stage.setScene(new Scene(listView));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
10
5/23/2017 11:45:52 AM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon