简体   繁体   中英

JavaFX TableView Undo functionality

I have a table, and I create the columns in the following way:

@FXML
TableView<Row> tableView = new TableView<>();

private void createColumns(int numberOfColumns, Row firstRow, boolean errorsDisplayed) {
        for (int i = 0; i < numberOfColumns; i++) {
            int colNum = i;
            TableColumn<Row, String> column = new TableColumn<>(firstRow.getCell(i).toString());
            column.setCellValueFactory(param -> {
                int index = param.getTableView().getColumns().indexOf(param.getTableColumn());
                return new SimpleStringProperty(param.getValue().getLastCellNum() > index
                        ? param.getValue().getCell(index).toString() : null);
            });
            if (!errorsDisplayed || (colNum != numberOfColumns - 1 && colNum != numberOfColumns - 2)) {
                column.setCellFactory(TextFieldTableCell.forTableColumn());
                column.setOnEditCommit(param -> param.getTableView().getItems().get(param.getTablePosition()
                        .getRow()).getCell(colNum).setCellValue(param.getNewValue()));
            }
            tableView.getColumns().add(column);
        }
    }

Is there a way I could store different states of the table in order to restore them when the undo button being pressed?

I create an app that can hopefully help. It is probably full of traps and pitfalls so don't attempt to use the code as it. I got the Undo/Redo ideas from here . Two Stack are used to implement the ideas. I got the code for the TableView from here . Changes are committed on focus lost.

Main

/*
    Altered code from the following!
    1. https://docs.oracle.com/javafx/2/ui_controls/table-view.htm
    2. https://www.geeksforgeeks.org/implement-undo-and-redo-features-of-a-text-editor/
*/

import com.mycompany.javafxsimpletest.MyAction.MyActionType;
import javafx.application.Application;
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.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
 
public class App extends Application { 
    final private UndoRedo undoRedo = new UndoRedo();   
    final private TableView<Person> table = new TableView();
    final private HBox hb = new HBox();
    
    private ObservableList<Person> data;    
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        data = FXCollections.observableArrayList(
                new Person(GenerateUniqueId.getUniqueId(), "Jacob", "Smith", "jacob.smith@example.com"),
                new Person(GenerateUniqueId.getUniqueId(), "Isabella", "Johnson", "isabella.johnson@example.com"),
                new Person(GenerateUniqueId.getUniqueId(), "Ethan", "Williams", "ethan.williams@example.com"),
                new Person(GenerateUniqueId.getUniqueId(), "Emma", "Jones", "emma.jones@example.com"),
                new Person(GenerateUniqueId.getUniqueId(), "Michael", "Brown", "michael.brown@example.com"));
        
        //Since I added these Persons via code and not manually, I added that action here!
        data.forEach((newPerson) -> {
            undoRedo.addAction(new MyAction(MyActionType.ADD, null, newPerson.copy()));
            System.out.println("Add Undo Action - \n\tActionType: " + MyActionType.ADD + "\n\tOld Person: " + null + "\n\tnew Person: " + newPerson);
        });
        
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(550);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
        Callback<TableColumn, TableCell> cellFactory = (TableColumn p) -> new EditingCell();
 
        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
            new PropertyValueFactory<Person, String>("firstName"));
        firstNameCol.setCellFactory(cellFactory);
        firstNameCol.setOnEditCommit(
            new EventHandler<CellEditEvent<Person, String>>() {
                @Override
                public void handle(CellEditEvent<Person, String> t) {
                    Person oldPerson = t.getTableView().getItems().get(t.getTablePosition().getRow());
                    Person newPerson = oldPerson.copy();
                    newPerson.setFirstName(t.getNewValue());
                                      
                    undoRedo.addAction(new MyAction(MyActionType.EDIT, oldPerson, newPerson));
                    System.out.println("Add Undo Action - \n\tActionType: " + MyActionType.EDIT + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
                }
             }
        );
 
 
        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
            new PropertyValueFactory<Person, String>("lastName"));
        lastNameCol.setCellFactory(cellFactory);
        lastNameCol.setOnEditCommit(
            new EventHandler<CellEditEvent<Person, String>>() {
                @Override
                public void handle(CellEditEvent<Person, String> t) {
                    Person oldPerson = t.getTableView().getItems().get(t.getTablePosition().getRow());
                    Person newPerson = oldPerson.copy();
                    newPerson.setLastName(t.getNewValue());
                                      
                    undoRedo.addAction(new MyAction(MyActionType.EDIT, oldPerson, newPerson));
                    System.out.println("Add Undo Action - \n\tActionType: " + MyActionType.EDIT + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
                }
            }
        );
 
        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
            new PropertyValueFactory<Person, String>("email"));
        emailCol.setCellFactory(cellFactory);
        emailCol.setOnEditCommit(
            new EventHandler<CellEditEvent<Person, String>>() {
                @Override
                public void handle(CellEditEvent<Person, String> t) {
                    Person oldPerson = t.getTableView().getItems().get(t.getTablePosition().getRow());
                    Person newPerson = oldPerson.copy();
                    newPerson.setEmail(t.getNewValue());
                                      
                    undoRedo.addAction(new MyAction(MyActionType.EDIT, oldPerson, newPerson));
                    System.out.println("Add Undo Action - \n\tActionType: " + MyActionType.EDIT + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
                }
            }
        );
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");
 
        final Button addButton = new Button("Add");
        addButton.setOnAction((ActionEvent e) -> {
            Person newPerson = new Person(GenerateUniqueId.getUniqueId(),addFirstName.getText(), addLastName.getText(), addEmail.getText());
            data.add(newPerson);
            addFirstName.clear();
            addLastName.clear();
            addEmail.clear();
            undoRedo.addAction(new MyAction(MyActionType.ADD, null, newPerson.copy()));
            System.out.println("Add Undo Action - ActionType: " + MyActionType.ADD + "\tPerson: " + newPerson);
        });
         
        Button btnUndo = new Button("<");
        btnUndo.setOnAction((t) -> {
            if(!undoRedo.isUndoEmpty())
            {
                MyAction myUndoAction = undoRedo.getUndo();
                Person oldPerson = (Person)myUndoAction.getOldAction();
                Person newPerson = (Person)myUndoAction.getNewAction();
                System.out.println("Add Undo Action - \n\tActionType: " + myUndoAction.getActionType() + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
                if(myUndoAction.getActionType() == MyActionType.ADD)
                {
                    data.remove(data.indexOf(data.stream().filter((z) -> z.getId() == newPerson.getId()).findFirst().get()));
                }
                else if(myUndoAction.getActionType() == MyActionType.EDIT)
                {
                    data.set(data.indexOf(data.stream().filter((z) -> z.getId() == oldPerson.getId()).findFirst().get()), oldPerson);
                }           
            }
        });
        
        Button btnRedo = new Button(">");
        btnRedo.setOnAction((t) -> {
            if(!undoRedo.isRedoEmpty())
            {
                MyAction myRedoAction = undoRedo.getRedo();
                Person oldPerson = (Person)myRedoAction.getOldAction();
                Person newPerson = (Person)myRedoAction.getNewAction();
                System.out.println("Add Undo Action - \n\tActionType: " +  myRedoAction.getActionType() + "\n\tOld Person: " + oldPerson + "\n\tnew Person: " + newPerson);
                
                if(myRedoAction.getActionType() == MyActionType.ADD)
                {
                    data.add(newPerson.getId(), newPerson);
                }
                else if(myRedoAction.getActionType() == MyActionType.EDIT)
                {
                    data.set(data.indexOf(data.stream().filter((z) -> z.getId() == newPerson.getId()).findFirst().get()), newPerson);
                }   
            }
        });
                
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton, btnUndo, btnRedo);
        hb.setSpacing(3);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, hb);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
  
    class EditingCell extends TableCell<Person, String> {
 
        private TextField textField;
 
        public EditingCell() {
        }
 
        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                textField.selectAll();
            }
        }
 
        @Override
        public void cancelEdit() {
            super.cancelEdit();
 
            setText((String) getItem());
            setGraphic(null);
        }
 
        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
 
            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                if (isEditing()) {
                    if (textField != null) {
                        textField.setText(getString());
                    }
                    setText(null);
                    setGraphic(textField);
                } else {
                    setText(getString());
                    setGraphic(null);
                }
            }
        }
 
        private void createTextField() {
            textField = new TextField(getString());
            textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()* 2);
            textField.focusedProperty().addListener(new ChangeListener<Boolean>(){
                @Override
                public void changed(ObservableValue<? extends Boolean> arg0, 
                    Boolean arg1, Boolean arg2) {
                        if (!arg2) {
                            commitEdit(textField.getText());
                        }
                }
            });
        }
 
        private String getString() {
            return getItem() == null ? "" : getItem().toString();
        }
    }
}

Person

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Person {        
    private final IntegerProperty id;
    private final StringProperty firstName;
    private final StringProperty lastName;
    private final StringProperty email;

    public Person(int id, String fName, String lName, String email) {
        this.id = new SimpleIntegerProperty(id);
        this.firstName = new SimpleStringProperty(fName);
        this.lastName = new SimpleStringProperty(lName);
        this.email = new SimpleStringProperty(email);
    }

    public String getFirstName() {
        return firstName.get();
    }

    public void setFirstName(String fName) {
        firstName.set(fName);
    }

    public String getLastName() {
        return lastName.get();
    }

    public void setLastName(String fName) {
        lastName.set(fName);
    }

    public String getEmail() {
        return email.get();
    }

    public void setEmail(String fName) {
        email.set(fName);
    }

    public int getId()
    {
        return id.get();
    }

    @Override public String toString()
    {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("id: ").append(this.id.get())
                .append("\tName:").append(this.firstName.get()).append(" ").append(this.lastName.get())
                .append("\temail: ").append(this.email.get());

        return stringBuilder.toString();
    }

    public Person copy()
    {
        Person copyPerson = new Person(this.id.get(), this.firstName.get(), this.lastName.get(), this.email.get());

        return copyPerson;
    }       
}

MyAction

/**
 *
 * @author blj0011(sedj601)
 * @param <T>
 */
public class MyAction<T>
{
    public enum MyActionType {
        ADD,
        DELETE,
        EDIT
    }
    private final T oldAction;
    private final T newAction;
    private final MyActionType myActionType;
    
    public MyAction(MyActionType actionType, T oldAction, T newAction) {   
        this.oldAction = oldAction;
        this.newAction = newAction;
        this.myActionType = actionType;
    }
    
    public T getOldAction()
    {
        return this.oldAction;
    }
    
    public T getNewAction()
    {
        return this.newAction;
    }
    
    public MyActionType getActionType()
    {
        return this.myActionType;
    }
}

UndoRedo

import java.util.Stack;

/**
 *
 * author 2. https://www.geeksforgeeks.org/implement-undo-and-redo-features-of-a-text-editor/
 * 
 */

public class UndoRedo {
    private final Stack<MyAction> undo = new Stack();
    private final Stack<MyAction> redo = new Stack();
       
    public void addAction(MyAction myAction)
    {
        undo.push(myAction);
    }
    
    // Function to perform
    // "UNDO" operation
    public MyAction getUndo()
    {
        MyAction myAction = undo.peek();
        undo.pop();        
        redo .push(myAction);
        
        return myAction;
    }
  
    // Function to perform
    // "REDO" operation
    public MyAction getRedo()
    {
        MyAction myAction = redo.peek();
        redo.pop();
        undo.push(myAction);
        
        return myAction;
    }
    
    //Check if stack is empty before attempting to do use getUndo()!
    public boolean isUndoEmpty()
    {
        return undo.empty();
    }
    
     //Check if stack is empty before attempting to do use getRedo()!
    public boolean isRedoEmpty()
    {
        return redo.empty();
    }
}

**GenerateUniqueId

/**
 *
 * @author blj0011(sedj601)
 */
public class GenerateUniqueId {
    static AtomicInteger uniqueIdGenerator = new AtomicInteger();
    
    public static int getUniqueId()
    {
        return uniqueIdGenerator.getAndIncrement();
    }
}

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM