-2

I am trying to add a certain behavior to the TableView, so it is more user friendly. Though I got a problem getting one of those things to work. What I am trying to do is adding a new Item to the TableView (I use a custom BackingList), and automatically start editing that new Item. Here is an example snippit on how I try to accomplish this.

public void onNewItem(Object newItem)
{
    // I use the index of this cell, to make sure it is inserted at this
    // index in the TableView instead of at the end.
    this.BackingList.add(this.getIndex(), newItem);
    // Start editing the previous cell, assuming the index of this cell is already updated.
    this.getTableView().edit(this.getIndex(), this.getTableColumn());
}

So I add the item to the backingList, which should fire an UpdateEvent inside the TableView. Though against my expectations that update is happening way after this method is exited. This means I cannot start editing that specific cell (since the item doesn't exist yet) at this point in time.

So the question is, is there a way to 'force' an update, so I can start editing the specific cell? Or are there any other workarounds for this problem?

If there are any suggestion/idea's/solutions, please let me know. Thanks in advance!


Edit 01:

I tried some things to accomplish this, and one method that is working is the following. Though the downside is, that it breaks the EventSystem somehow. So it is not usefull.(In case if you are wondering, this.startEdit() isn't working either, so I have no options left unfortunatly.)

public void onNewItem(Object newItem)
{
    this.BackingList.add(this.getIndex(), newItem);
    this.getTableRow().updateIndex(this.getIndex());//TODO fishy fix!
    this.getTableView().edit(this.getIndex(), this.getTableColumn());
}

EDIT 02:

On request Im adding this example so people can see what is happening. I added two ways to add ellements. one is via a contextMenu, which works perfectly. second is the button at the bottom of the list.

    package testpacket;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;



public class EditStateTest extends Application
{
    private static ObservableList<SimpleStringProperty> exampleList = FXCollections.observableArrayList();
    //Placeholder for the button
    private static SimpleStringProperty PlaceHolder = new SimpleStringProperty();

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        // basic ui setup
        AnchorPane parent = new AnchorPane();
        Scene scene = new Scene(parent);
        primaryStage.setScene(scene);

        //fill backinglist with data
        for(int i = 0 ; i < 20; i++)
            exampleList.add(new SimpleStringProperty("Hello Test"));
        exampleList.add(PlaceHolder);

        //create a basic tableView
        TableView<SimpleStringProperty> listView = new TableView<SimpleStringProperty>();
        listView.setEditable(true);

        TableColumn<SimpleStringProperty, String> column = new TableColumn<SimpleStringProperty, String>();
        column.setCellFactory(E -> new TableCellTest<SimpleStringProperty, String>());
        column.setCellValueFactory(E -> E.getValue());
        column.setEditable(true);

        // set listViews' backing list
        listView.setItems(exampleList);


        listView.getColumns().clear();
        listView.getColumns().add(column);
        parent.getChildren().add(listView);

        parent.setOnKeyReleased(E -> System.out.println("KeyRelease Captuered: Parent"));


        primaryStage.show();
    }

    // basic editable cell example
    public static class TableCellTest<S, T> extends TableCell<S, T>
    {
        // The editing textField.
        protected static TextField textField = new TextField();;
        protected Button addButton;
        protected ContextMenu menu;


        public TableCellTest()
        {
            this.setOnContextMenuRequested(E -> {
                if(this.getTableView().editingCellProperty().get() == null)
                    this.menu.show(this, E.getScreenX(), E.getScreenY());
            });
            this.menu = new ContextMenu();

            MenuItem createNew = new MenuItem("create New");
            createNew.setOnAction(E -> {
                this.onNewItem(this.getIndex());
            });
            this.menu.getItems().add(createNew);

            addButton = new Button("Add");
            addButton.setOnAction(E -> this.onNewItem(exampleList.size() - 1));
            addButton.prefWidthProperty().bind(this.widthProperty());
        }

        public void onNewItem(int index)
        {
            EditStateTest.exampleList.add(index, new SimpleStringProperty("New Item"));
            this.getTableView().edit(index, this.getTableColumn());
            textField.requestFocus();
        }

        @Override
        public void startEdit()
        {
            if (!isEditable()
                    || (this.getTableView() != null && !this.getTableView().isEditable())
                    || (this.getTableColumn() != null && !this.getTableColumn().isEditable()))
                return;

            super.startEdit();

            if(textField == null)
                this.createTextField();


            textField.setText((String)this.getItem());
            this.setGraphic(textField);
            textField.selectAll();
            this.setText(null);
        }

        @Override
        public void cancelEdit()
        {
            if (!this.isEditing())
                return;

            super.cancelEdit();

            this.setText((String)this.getItem());
            this.setGraphic(null);
        }

        @Override
        protected void updateItem(T item, boolean empty)
        {
            super.updateItem(item, empty);

            // Checks if visuals need an update.
            if(this.getIndex() == EditStateTest.exampleList.size() - 1)
            {
                this.setText("");
                this.setGraphic(addButton);
            }
            else if(empty || item == null)
            {
                this.setText(null);
                this.setGraphic(null);
            }
            else
            {
                // These checks are needed to make sure this cell is the specific cell that is in editing mode.
                // Technically this#isEditing() can be left out, as it is not accurate enough at this point.
                if(this.isEditing() && this.getTableView().getEditingCell() != null 
                        && this.getTableView().getEditingCell().getRow() == this.getIndex())
                {
                    //change to TextField
                    this.setText(null);
                    this.setGraphic(textField);
                }
                else
                {
                    //change to actual value
                    this.setText((String)this.getItem());
                    this.setGraphic(null);
                }
            }
        }

        @SuppressWarnings("unchecked")
        public void createTextField()
        {
            // A keyEvent for the textField. which is called when there is no keyEvent set to this cellObject.
            textField.addEventHandler(KeyEvent.KEY_RELEASED, E -> {
                if(this.getTableView().getEditingCell().getRow() == this.getIndex())
                    if(E.getCode() == KeyCode.ENTER)
                    {
                        this.setItem((T) textField.getText());
                        this.commitEdit(this.getItem());
                    }
                    else if(E.getCode() == KeyCode.ESCAPE)
                        this.cancelEdit();
            });
        }
    }
}

The idea is that there is a new item added (which works perfectly) and that it will automatically start editing the newly added Item. When using the contextMenu it works perfectly, though when using the button it works only in some cases. (about every 3-5 new Items it works once)

I hope that there is some way to solve this wierd problem.

On a side note, when making the button static (which I prefer) the button somehow disapears every few new Items. And it seems like it is in the same patern as I mentioned above.

n247s
  • 1,898
  • 1
  • 12
  • 30
  • Well try this.getTableView().edit() in Platform.runLater.. – Uluk Biy Jan 30 '16 at 05:39
  • @Uluk Biy I tried that one indeed. Though unfortunatly, it isnt enough delay if I may call it that way. Also playing with threads at this point is the last thing I want to do, since it is asking for troubles sooner or later. Though thanks for the suggestion! – n247s Jan 30 '16 at 07:26
  • 1
    I don't understand why you are using `this.getIndex()-1` when you call edit, but anyway... I tried this in the event handler for a context menu item handler with the context menu set on the table cell, and it worked just fine. Can you create a [MCVE] so that there is enough context to see why it is not working for you? – James_D Jan 30 '16 at 21:52
  • @James_D I dont understand that either :P, I wasnt thinking when I typed that, since its the cells' content that updates, not the cell itself. Anyway, I tried using `startEdit()` for example, and the method I described above, and it is not the index that is not working, though the cell updates after I set it to editing mode, meaning it exit right out of it afterwards. So I changed the title for that one. also I do the MCVE, all the time, though this time, I cant solve it myself. – n247s Feb 01 '16 at 18:51
  • I don't know what "I do the MCVE though this time I can't do it myself" means. Your code works for me when I include it in a complete application. If it's not working for you, make a simple, complete application that others can execute, and include it in your question so people can help you figure out what you're doing wrong. No-one can possibly diagnose your error from incoherent code snippets with no context. – James_D Feb 01 '16 at 19:04
  • @James_D Sorry for the late answer, I had some stuff to take care off first. Anyway I posted a working example. I hope it helps in anyway. – n247s Feb 04 '16 at 21:07
  • Please [edit] the question with the MCVE, do not post it as an answer (it's not an answer). – James_D Feb 04 '16 at 21:10
  • OK, not trying to be patronizing here, and I'm really sorry if it comes across that way, but you are trying to do way more than your level of expertise allows at this stage. This is a pretty complex framework, and editing `TableView`s is a very tricky, and quite buggy, dark corner of it. Customizing behavior in JavaFX is not well supported. You are still at the stage where you don't understand basic concepts (like `static`, which is really up there in the early stages of learning Java). You need way more experience before you attempt something like this. – James_D Feb 04 '16 at 21:20
  • @James_D thats a first timer, I had some comments saying it is better to answer the question with clarifications, because you get lost easily within all the edits. – n247s Feb 05 '16 at 06:14
  • @James_D Anyway, you are saying Im not experienced enough to do something like this, since I dont understand static variables. That is a verry bold conclusion from the little information in my opinion. Im asking a serious question since the behavior of the javafx's TableView is far from what I expected. So please try to help instead of advising me to abort this idea, since your idea about my level of expertise is totaly wrong. If you are 100% sure about the opposite, could you at least explain where you base it on?(since I have no clue where this is comming from) – n247s Feb 05 '16 at 06:15

1 Answers1

0

I have been digging quite a lot into this problem right now, and the main problem why this was rather confusing is because the eventSystem is messing up when using the 'normal' way of starting an edit on a certain cell.

This problem (e.g. editing state updating) was visually caused by a flaw inside the onUpdateItem method. Basically the placeHolder for the button had a null value, causing the button to disapear. Unexplainable is that the button still appeared sporadically. This should do the trick for the onUpdateItem() method.

if(empty || item == null)
{
    if(this.getIndex() == EditStateTest.exampleList.size() - 1)
    {
        this.setText("");
        this.setGraphic(addButton);
    }
    else
    {
        this.setText(null);
        this.setGraphic(null);
    }
}
else
{
    // These checks are needed to make sure this cell is the specific cell that is in editing mode.
    // Technically this#isEditing() can be left out, as it is not accurate enough at this point.
    if(this.getTableView().getEditingCell() != null 
                        && this.getTableView().getEditingCell().getRow() == this.getIndex())
    {
        //change to TextField
        this.setText(null);
        this.setGraphic(textField);
    }
    else
    {
        //change to actual value
        this.setText((String)this.getItem());
        this.setGraphic(null);
    }
}

Anyway, since the method in question is updated, the editing state is entered flawelessly. But there are still problems with the EventSystem behind the TableView unfortunatly. Meaning that the example (even with the changes above) is not functional. But as the problem is out of the scope of this question, I consider this problem solved. If you are interested in the solution for the EventSystem, this Question is dedicated to that problem.

Everyone who helped me out with this problem, many thanks!

n247s
  • 1,898
  • 1
  • 12
  • 30