FXMisc / UndoFX

Undo manager for JavaFX
BSD 2-Clause "Simplified" License
98 stars 17 forks source link

Example for undoing changes on collections #18

Closed dlemmermann closed 7 years ago

dlemmermann commented 7 years ago

It would be great if you could also show an example on how to undo / redo changes on collections.

dlemmermann commented 7 years ago

Currently my change object for list changes looks like this, but I keep getting "IllegalArgumentException: Unexpected change received".

` public static abstract class MyChange {

    public abstract void redo();

    public abstract PathwayChange invert();

    public Optional<PathwayChange> mergeWith(PathwayChange other) {
        // don't merge changes by default
        return Optional.empty();
    }
}

// list changes (elements added or removed)

public static class ListChange extends MyChange {

    private ListChangeListener.Change change;
    private ObservableList list;
    private boolean added;
    private List elements;
    private int hashCode;

    public ListChange(ListChangeListener.Change change) {
        this.change = change;

        list = change.getList();

        change.next(); // not optimal, there could be several changes!
        if (change.wasAdded()) {
            added = true;
            elements = change.getAddedSubList();
        } else if (change.wasRemoved()) {
            added = false;
            elements = change.getRemoved();
        }

        hashCode = Objects.hash(change, list, elements, added);
    }

    // private constructor, only used internally be invert() method
    private ListChange(ListChangeListener.Change change, ObservableList list, List elements, boolean added) {
        this.change = change;
        this.list = list;
        this.elements = elements;
        this.added = added;
        this.hashCode = Objects.hash(change, list, elements, added);
    }

    @Override
    public void redo() {
        if (added) {
            list.addAll(elements);
        } else {
            list.removeAll(elements);
        }
    }

    @Override
    public ListChange invert() {
        return new ListChange(change, list, elements, !added);
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof ListChange) {
            ListChange that = (ListChange) other;
            return Objects.equals(this.change, that.change)
                    && Objects.equals(this.list, that.list)
                    && Objects.equals(this.elements, that.elements)
                    && Objects.equals(this.added, that.added);
        }

        return false;
    }

    @Override
    public int hashCode() {
        return hashCode;
    }
}

`

dlemmermann commented 7 years ago

Based on the error message I am currently assuming it has something to do with the equals() and hashCode() methods.

TomasMikula commented 7 years ago

It has to be the case that when you apply a change c, a change c1 arrives in the change stream such that c.equals(c1). This is probably not the case because of comparing the original ListChangeListener.Change objects. You probably don't need to store these objects at all.

dlemmermann commented 7 years ago

I made it work by only basing the hash code and equals methods on the added / removed elements list. Additionally I had to create a new element list to avoid concurrent modification exceptions.

` public static class ListChange extends PathwayChange {

    private ObservableList collection;
    private boolean added;
    private List elements;

    public ListChange(ListChangeListener.Change change) {
        collection = change.getList();

        change.next();
        if (change.wasAdded()) {
            added = true;
            elements = new ArrayList(change.getAddedSubList());
        } else if (change.wasRemoved()) {
            added = false;
            elements = new ArrayList(change.getRemoved());
        }
    }

    // private constructor, only used internally be invert() method
    private ListChange(ObservableList collection, List elements, boolean added) {
        this.collection = collection;
        this.elements = elements;
        this.added = added;
    }

    @Override
    public void redo() {
        if (added) {
            collection.addAll(elements);
        } else {
            collection.removeAll(elements);
        }
    }

    @Override
    public ListChange invert() {
        return new ListChange(collection, elements, !added);
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof ListChange) {
            ListChange that = (ListChange) other;
            return Objects.equals(this.elements, that.elements);
        }

        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(elements);
    }
}`
carstenkemper commented 6 years ago

Thank you. This works for me too, but I wonder how to undo/redo items in a tableview feeded by an observable list. I have an ObservableList<MyObject>where MyObject contains different variables of type BooleanProperty, StringProperty and DoubleProperty. How can I undo/redo changes of theses using "c.wasUpdated()" of the ListChangeListener? Is it possible or do I have to try a different approach?

rthangarajgit commented 5 years ago

could you share PathwayChange class implementation...please...