0

I have a tableview with some input controls inside its tablecells which are bind to a model's properties. The problem is that with any change the user mades in textfields, they lose the focus when doing the binding.

tblcNombre.setCellValueFactory(new PropertyValueFactory<Responsable, String>("nombre"));
tblcNombre.setCellFactory(col -> {
    TableCell<Responsable, String> cell = new TableCell<Responsable, String>(){
        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            if(empty) {
                setText("");
                setGraphic(null);
            }else {
                TextField tf = new TextField();
                tf.textProperty().bindBidirectional(getTableRow().getItem().nombreProperty());
                tf.setText(item);
                setGraphic(tf);
            }
        }
    };
    return cell;
});

Any idea on how to solve it? Thank you!

Edit: I think it happens becasue when user changes textfield text, the cell is repainted, so updateItem is called again, and it creates a new textfield with a new binding. But I can't find a solution for it

Nessarose
  • 25
  • 5
  • See: [Why should I avoid using PropertyValueFactory in JavaFX?](https://stackoverflow.com/questions/72437983/why-should-i-avoid-using-propertyvaluefactory-in-javafx). (not related to your binding issue, just a preferred style for table use in general). – jewelsea Jul 28 '23 at 07:47
  • Don’t create nodes in updateItem, create them in the cell constructor and update the value they display in updateItem. – jewelsea Jul 28 '23 at 07:50
  • Consider using a [TextFieldTableCell](https://openjfx.io/javadoc/17/javafx.controls/javafx/scene/control/cell/TextFieldTableCell.html), instead of your binding approach, otherwise follow the editing example in the [JavaFX tutorial](https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm), example 13-10 and 13-11. If you do continue to try to bind (probably not a good idea in a table cell), don’t forget to unbind when the binding should no longer apply. – jewelsea Jul 28 '23 at 07:57
  • jewelsea, good point, thank you very much. Can you give me an example for doing the binding? Now, I'm creating the textfield on the constructor, but I don't know how to access the model (Responsable class in this case) to bind the values. Using TextFieldTableCell would be and option if there were only textfield controls, but i have other types of fields in the rest of cells. – Nessarose Jul 28 '23 at 08:15
  • “Can you give me an example for doing the binding?”-> I don’t think binding is really recommended in this case so I don’t want to give an example of it. – jewelsea Jul 28 '23 at 08:25
  • *“Using TextFieldTableCell would be and option if there were only textfield controls, but i have other types of fields in the rest of cells.”* -> that’s ok it is a TextFieldTableCell, not a table row, you can have other types in other columns. Provide a [mcve] so people can exactly understand your situation. – jewelsea Jul 28 '23 at 08:28
  • And how I should bind a collection? I have a Task class with a list of Persons (Responsable class). I'm creating the TableView with a Tablerow for each Responsable. This TableRow must be editable with some textfields, some datepickers...and the value of this fields must be saved in the Responsable model. In my mind, each row must be binded to its Responsable model fields. But if there is another proper way to do it I would like to know. Thank you very much – Nessarose Jul 28 '23 at 08:32
  • That is why to get help it is good to provide a [mcve], people can then understand exactly what you have fond and are trying to accomplish, then provide a correct or improved solution in an answer if they wish, without it, there is often much guesswork and misunderstanding in my experience. – jewelsea Jul 28 '23 at 08:37
  • 1
    Tahnk you, I think is better to open another question to ask about the correct way to bind the collection. I think the solution of my first problem is to create the textfield in the constructor of the tablecell – Nessarose Jul 28 '23 at 08:41
  • Editable tables are *really* tricky. Consider if it is really the best approach for your UI. Sometimes it is, but often a separate editing pane for selected rows is better (like the [Makery tutorial](https://code.makery.ch/library/javafx-tutorial/)). – jewelsea Jul 28 '23 at 08:41
  • *” I think is better to open another question to ask about the correct way to bind the collection”* -> yes, it is always best to ask one question in each question. – jewelsea Jul 28 '23 at 08:49
  • On thinking about it, the binding solution you are attempting might be better for some cases than the editing mode used in the official examples (which is quite complex). A difference is that with your approach, the cell will be always editable, always showing a text field. In the modal edit approach, a label is displayed that is click to edit and converts to a textfield upon commencing editing and reverts back to a label when editing is done. – jewelsea Jul 28 '23 at 08:56
  • *”I think the solution of my first problem is to create the textfield in the constructor of the tablecell”* -> If you get that to work well for you, you can add the code for your solution as a self-answer if you wish. Don’t forget to unbind and rebind the textfield when you are notified of the item changing in the updateItem call. – jewelsea Jul 28 '23 at 08:58
  • *”how I should bind a collection?”* -> The [Bindings class has methods for binding collections](https://openjfx.io/javadoc/17/javafx.base/javafx/beans/binding/Bindings.html#bindContent(java.util.List,javafx.collections.ObservableList)). I don’t know if you really need to do that in this case, but the functionality is there. For lists both single and bidirectional binding and unbinding is supported. – jewelsea Jul 28 '23 at 08:59
  • I will take a look at bindContent method of Binding Collections, it seems a good option. Thanks again – Nessarose Jul 28 '23 at 10:07

1 Answers1

0

So, following the comments of jewelsea I finally got into this:

tblcEmail.setCellValueFactory(data -> data.getValue().emailProperty());
tblcEmail.setCellFactory(col -> {
    TableCell<Responsable, String> cell = new TableCell<Responsable, String>(){
        TextField tf;
        {
            tf = new TextField();
            tf.focusedProperty().addListener(new ChangeListener<Boolean>(){
                @Override
                public void changed(ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue, Boolean newPropertyValue){
                    if (newPropertyValue){
                        getTableRow().getItem().emailProperty().unbind();
                    }else{
                        //Lost focus.
                        //Bind the data
                        getTableRow().getItem().emailProperty().bind(tf.textProperty());
                    }
                }
            });
        }
        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            setText("");
            if(empty) {
                setGraphic(null);
            }else {
                tf.setText(item);
                setGraphic(tf);
            }
        }
    };
    return cell;
});

I'm doing the binding in the focus property of the textfield because updateItem() is called for every line, and it would bind on the same textfield all properties.

Nessarose
  • 25
  • 5