After messing around with Netbeans and Scenebuilder for a while I'm stuck at a problem I can't quite understand. I use a custom cellfactory to bind a doubleclick event to the cells in my tableview. But when I set the cellfactory and a cellValueFactory only the custom cellFactory has an effect.

I'm trying to populate a tableview with data from a number of objects and bind a double click event to the cells of the first column. Populating is not the problem, I just used

idNumber.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("idNumber"));
status.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("status"));

I then googled around to figure out how to bind a doubleclick event to the cells of the table and found javafx, TableView: detect a doubleclick on a cell amongst others... I defined a custom cellFactory like this:

Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>> cellFactory =
    new Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>>() {
        @Override
        public TableCell call(TableColumn p) {
            TableCell cell = new TableCell<LiveStock, String>() {};

            cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent event) {
                    if (event.getClickCount() == 2) {
                        System.out.println("double clicked!");
                        TableCell c = (TableCell) event.getSource();
                        System.out.println("Livestock ID: " + c.getId());
                    }
                }
            });

            return cell;
        }

I removed the update and toString methods just to see if they where the reason I ran in to problems.

So I tried

idNumber.setCellFactory(cellFactory);
idNumber.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("idNumber"));

This results in my cells beeing empty, but having the double click binding

any ideas?

My LiveStock class looks like this:

package projekt1.fx;

import javafx.beans.property.SimpleStringProperty;

public class LiveStock {
    private final int idNumber;
    private final SimpleStringProperty ownerID;
    private SimpleStringProperty status;
    private double lat;
    private double longd;


    public LiveStock(int idNumber, String ownerID) {
        this.idNumber = idNumber;
        this.ownerID = new SimpleStringProperty(ownerID);
        this.status = new SimpleStringProperty("ok");
    }

    public int getIdNumber() {
        return this.idNumber;
    }

//    public void setIdNumber(int number) {
//        this.idNumber = number;
//    }

    public String getOwnerID(){
        return ownerID.get();
    }

    public void setOwnerID(String id){
        ownerID.set(id);
    }

    public String getStatus(){
        return status.get();
    }

    public void setStatus(String st){
        status.set(st);
    } 
}

The cellfactory now looks like this:

    Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>> cellFactory =
        new Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>>() {
            @Override
            public TableCell call(TableColumn p) {
                TableCell cell = new TableCell<LiveStock, String>() {
                    @Override
                    public void updateItem(String item, boolean empty) {
                        super.updateItem(item, empty);
//                        setText("HELLO WORLD!");
                        setText(empty ? null : getString());
                    }
                };

                cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
                    @Override
                    public void handle(MouseEvent event) {
                        if (event.getClickCount() == 2) {
                            System.out.println("double clicked!");
                            TableCell c = (TableCell) event.getSource();
                            System.out.println("Livestock ID: " + c.getId());
                            togglePopup(null);
                        }
                    }
                });

                return cell;
            }
        };
有帮助吗?

解决方案

Documentation of Cell API says:

Because by far the most common use case for cells is to show text to a user, this use case is specially optimized for within Cell. This is done by Cell extending from Labeled. This means that subclasses of Cell need only set the text property, rather than create a separate Label and set that within the Cell. ...

The current source code of Cell constructor sets the text to null:

public Cell() {
    setText(null);
    ...
}

The subclass IndexedCell and sub-subclass TableCell, both of them don't set the text of Labeled.
The text is set by default cell factory of TableColumn in source code.

public static final Callback<TableColumn<?,?>, TableCell<?,?>> DEFAULT_CELL_FACTORY = new Callback<TableColumn<?,?>, TableCell<?,?>>() {
    @Override public TableCell<?,?> call(TableColumn<?,?> param) {
        return new TableCell() {
            @Override protected void updateItem(Object item, boolean empty) {
                if (item == getItem()) return;

                super.updateItem(item, empty);

                if (item == null) {
                    super.setText(null);
                    super.setGraphic(null);
                } else if (item instanceof Node) {
                    super.setText(null);
                    super.setGraphic((Node)item);
                } else {
                    super.setText(item.toString());
                    super.setGraphic(null);
                }
            }
        };
    }
};

However by defining your own cell factory that creates new TableCell but does not set the text in its overriden updateItem() method, will be resulting an empty (=null) column cell text. So yes the reason of the problem was removing updateItem method, which calls setText(...) internally.

EDIT:
Specify the generic types explicitly for TableColumns as,

TableColumn<LiveStock, Integer> idNumber = new TableColumn<LiveStock, Integer>("ID No");

This will avoid type mismatches or wrong type castings.
Then the cell factory callback for your use case will be

Callback<TableColumn<LiveStock, Integer>, TableCell<LiveStock, Integer>> cellFactory =
        new Callback<TableColumn<LiveStock, Integer>, TableCell<LiveStock, Integer>>() {
    public TableCell<LiveStock, Integer> call(TableColumn<LiveStock, Integer> p) {
        TableCell<LiveStock, Integer> cell = new TableCell<LiveStock, Integer>() {

            @Override
            public void updateItem(Integer item, boolean empty) {
                super.updateItem(item, empty);
                setText((item == null || empty) ? null : item.toString());
                setGraphic(null);
            }
        };

        cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                if (event.getClickCount() > 1) {
                    System.out.println("double clicked!");
                    TableCell c = (TableCell) event.getSource();
                    System.out.println("Cell text: " + c.getText());
                }
            }
        });
        return cell;
    }
};

What is changed?
The type of idNumber in LiveStock is int. By defining new TableColumn<LiveStock, Integer> we say this is a column from LiveStock row for its attribute idNumber which has a type int, but the generic types must be a reference type, it cannot be TableCell<LiveStock, int> so we define TableCell<LiveStock, Integer>. The thumb of rule: row item class's attribute type should match the second generic type parameter of TableColumn and due to this the parameter of TableCell also.

getString method is defined in the referenced answer link mentioned by you. But it is just a user defined method, not mandatory to use it.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top