Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
306 views
in Technique[技术] by (71.8m points)

tableview - UITableView - Better Editing through Binding?

Linked to question: JavaFX 2: Save edit in TableCell

There seems to be allot of plumbing required for to establish an editable tableview - namely trapping all the events for each textField (gained/lost focus, tabbing away from the textField, commiting edits from the textField to the underlying data model), and overriding several methods in the the TableCell.

The default behavior to establish editing - doubleclicking in a cell - doesn't seem familiar to me or users of a standard table control. I just want to click into the cell and start typing, for the most part.

Are there any fully implemented examples out there? Please add yours, or comments for designing such a creature.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Instead of responding to several events at the TableCell and TableColumn level to initiate editing of a cell, and successfully update the cell's underlying data - instead we provide a custom cell factory and override theupdateItem() method in the cell and 'bind' the textProperty of the textField directly to the property inside our data model for the TableView (in this case a StringProperty). I have added other aesthetics to make the textField inside the cell seem seamless and respond to hover and focused states.

All the magic happens in updateItem() method. You have to keep track of the textField and what it is bound to - the TableView API 'recycles' TableCells to reduce memory consumption:

    @Override
    protected void updateItem(String item, boolean empty) {
      super.updateItem(item, empty);        
      if(!empty) {
        // Show the Text Field
        this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

        // Retrieve the actual String Property that should be bound to the TextField
        // If the TextField is currently bound to a different StringProperty
        // Unbind the old property and rebind to the new one
        ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
        SimpleStringProperty sp = (SimpleStringProperty)ov;

        if(this.boundToCurrently==null) {
            this.boundToCurrently = sp;
            this.textField.textProperty().bindBidirectional(sp);
        }
        else {
            if(this.boundToCurrently != sp) {
              this.textField.textProperty().unbindBidirectional(this.boundToCurrently);
              this.boundToCurrently = sp;
              this.textField.textProperty().bindBidirectional(this.boundToCurrently);
            }
        }
        System.out.println("item=" + item + " ObservableValue<String>=" + ov.getValue());
        //this.textField.setText(item);  // No longer need this!!!
      }
      else {
        this.setContentDisplay(ContentDisplay.TEXT_ONLY);
      }
    }

Here is a full example of a table with 4 columns, all bound to the underlying textField. As soon as you type in the textField, the underlying data model in the Observable list is updated:

package tablevieweditingwithbinding;

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;

/**
 *
 * @author jKaufmann
 */
public class TableViewEditingWithBinding extends Application {
  public static class TableData {
    private SimpleStringProperty firstName, lastName, phone, email;
    private ObjectProperty<SimpleStringProperty> firstNameObject;

    public TableData(String firstName, String lastName, String phone, String email) {
        this.firstName = new SimpleStringProperty(firstName);
        this.firstNameObject = new SimpleObjectProperty(firstNameObject);
        this.lastName = new SimpleStringProperty(lastName);
        this.phone = new SimpleStringProperty(phone);
        this.email = new SimpleStringProperty(email);
    }

    public String getEmail() {
        return email.get();
    }
    public void setEmail(String email) {
        this.email.set(email);
    }
    public SimpleStringProperty emailProperty() { return email; } 

    public String getFirstName() {
        return firstName.get();
    }
    public SimpleStringProperty getFirstNameObject() {
        return firstNameObject.get();
    }
    public void setFirstNameObject(SimpleStringProperty firstNameObject) {
        this.firstNameObject.set(firstNameObject);
    }
    public ObjectProperty<SimpleStringProperty> firstNameObjectProperty() { return firstNameObject; }

    public void setFirstName(String firstName) {
        this.firstName.set(firstName);
    }
    public SimpleStringProperty firstNameProperty() { 
        return firstName; 
    }
    public String getLastName() {
        return lastName.get();
    }
    public void setLastName(String lastName) {
        this.lastName.set(lastName);
    }
    public SimpleStringProperty lastNameProperty() { return lastName; }

    public String getPhone() {
        return phone.get();
    }
    public void setPhone(String phone) {
        this.phone.set(phone);
    }
    public SimpleStringProperty phoneProperty() { return phone; }

  }
  public static class TextFieldCellFactory  
     implements Callback<TableColumn<TableData,String>,TableCell<TableData,String>> {

    @Override
    public TableCell<TableData, String> call(TableColumn<TableData, String> param) {
        TextFieldCell textFieldCell = new TextFieldCell();
        return textFieldCell;
    }

    public static class TextFieldCell extends TableCell<TableData,String> {
        private TextField textField;
        private StringProperty boundToCurrently = null;

        public TextFieldCell() {
          String strCss;
          // Padding in Text field cell is not wanted - we want the Textfield itself to "be"
          // The cell.  Though, this is aesthetic only.  to each his own.  comment out
          // to revert back.  
          strCss = "-fx-padding: 0;";


          this.setStyle(strCss);

          textField = new TextField();

          // 
          // Default style pulled from caspian.css. Used to play around with the inset background colors
          // ---trying to produce a text box without borders
          strCss = "" +
                    //"-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;" +
                    "-fx-background-color: -fx-control-inner-background;" +
                    //"-fx-background-insets: 0, 1, 2;" +
                    "-fx-background-insets: 0;" +
                    //"-fx-background-radius: 3, 2, 2;" +
                    "-fx-background-radius: 0;" +
                    "-fx-padding: 3 5 3 5;" +   /*Play with this value to center the text depending on cell height??*/
                    //"-fx-padding: 0 0 0 0;" +
                    "-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);" +
                    "-fx-cursor: text;" +
                    "";
          // Focused and hover states should be set in the CSS.  This is just a test
          // to see what happens when we set the style in code
          textField.focusedProperty().addListener(new ChangeListener<Boolean>() {

            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                TextField tf = (TextField)getGraphic();
                String strStyleGotFocus = "-fx-background-color: purple, -fx-text-box-border, -fx-control-inner-background;" +
                            "-fx-background-insets: -0.4, 1, 2;" +
                            "-fx-background-radius: 3.4, 2, 2;";
                String strStyleLostFocus = //"-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;" +
                                   "-fx-background-color: -fx-control-inner-background;" +
                                 //"-fx-background-insets: 0, 1, 2;" +
                                   "-fx-background-insets: 0;" +
                                 //"-fx-background-radius: 3, 2, 2;" +
                                   "-fx-background-radius: 0;" +
                                   "-fx-padding: 3 5 3 5;" +   /**/
                                   //"-fx-padding: 0 0 0 0;" +
                                   "-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);" +
                                   "-fx-cursor: text;" +
                                   "";
                if(newValue.booleanValue())
                  tf.setStyle(strStyleGotFocus);
                else
                  tf.setStyle(strStyleLostFocus);             
            }
          });
          textField.hoverProperty().addListener(new ChangeListener<Boolean>() {

            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                TextField tf = (TextField)getGraphic();
                String strStyleGotHover = "-fx-background-color: derive(purple,90%), -fx-text-box-border, derive(-fx-control-inner-background, 10%);" +
                            "-fx-background-insets: 1, 2.8, 3.8;" +
                            "-fx-background-radius: 3.4, 2, 2;";
                String strStyleLostHover = //"-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;" +
                                   "-fx-background-color: -fx-control-inner-background;" +
                                 //"-fx-background-insets: 0, 1, 2;" +
                                   "-fx-background-insets: 0;" +
                                 //"-fx-background-radius: 3, 2, 2;" +
                                   "-fx-background-radius: 0;" +
                                   "-fx-padding: 3 5 3 5;" +   /**/
                                   //"-fx-padding: 0 0 0 0;" +
                                   "-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);" +
                                   "-fx-cursor: text;" +
                                   "";
                String strStyleHasFocus = "-fx-background-color: purple, -fx-text-box-border, -fx-control-inner-background;" +
                            "-fx-background-insets: -0.4, 1, 2;" +
                            "-fx-background-radius: 3.4, 2, 2;";
                if(newValue.booleanValue()) {
                  tf.setStyle(strStyleGotHover);
                }
                else {
                  if(!tf.focusedProperty().get()) {
                    tf.setStyle(strStyleLostHover);
                  }
                  else {
                    tf.setStyle(strStyleHasFocus);
                  }
                }

            }
          });
          textField.setStyle(strCss);
          this.setGraphic(textField);
        }

        @Override
        protected void updateItem(String item, boolean empty) {
          super.updateItem(item, empty);        
          if(!empty) {
            // Show the Text Field
            this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

            // Retrieve the actual String Property that should be bound to the TextField
            // If the TextField is currently bound to a different StringProperty
            // Unbind the old property and rebind to the new one
            ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
            SimpleStringProperty sp = (SimpleStringProperty)ov;

            if(this.boundToCurrently==null) {
                this.boundToCurrently = sp;
                this.textField.textProperty().bindBidirectional(sp);
            }
            else {
                if(this.boundToCurrently 

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...