需要撤消状态的WPF撤消/重做命令

anauzrmj  于 2022-11-18  发布在  其他
关注(0)|答案(1)|浏览(203)

上下文:

我正在尝试使用Command模式和WPF来实现undo/redo。
我将命令作为ViewModel的属性来操作数据。我在视图中绑定到这些命令。(对于上下文,我主要使用这些命令操作对象的集合。该集合用作DataGrid的ItemsSource)
可以撤消的命令实现此接口:

public interface IUndoableCommand : ICommand
{
    public void Undo();
}

我维护了这些UndoableCommands的堆栈。当UndoableCommand被执行时,它被压入堆栈。当我想撤销时,我从堆栈中弹出这些命令中的一个,并执行它的Undo()方法。

问题:

为许多命令实现Undo()方法并不需要记住任何状态。
例如,如果我想从列表中删除一个带有UndoableCommand的项,我可以将删除的项存储在命令对象本身中。

public class DeleteItemCommand : IUndoableCommand
{
    private object _deletedItem;

    public void Execute()
    {
        // Remove the item from the collection and set to _deletedItem
    }
    public void Undo()
    {
       // Insert the deletedItem back
    }
}

这样做的问题是,我绑定到的命令如下:

private IUndoableCommand _DeleteCommand;
public IUndoableCommand DeleteCommand
{
    get
    {
        if(_DeleteCommand == null)
        {
            _DeleteCommand = new DeleteItemCommand();
        }
        return _DeleteCommand;
    }
}

新的DeleteItemCommand示例并不是每次需要调用命令时都创建的。它是同一个对象。以这种方式存储已删除的项是行不通的。
我四处查看了一下,但我只能找到在WPF DataBinding上下文之外用Command模式实现撤销的例子,或者用不需要状态的Undo()方法实现撤销的例子。我想这是一个常见的场景。我的想法是不是都错了?这个问题有没有一致同意的解决方案?
我想做的事情是:
1.创建命令的深层副本并将其压入撤消堆栈。
1.让View绑定的命令创建我实际要执行的命令的示例。

llycmphe

llycmphe1#

我处理Undo的方法是将UI层与底层的Undo机制分离。仅仅因为“command pattern”和“ICommand”都有工作“command”并不意味着它们需要或应该是同一个对象层次结构的一部分。
因此,您的DeleteFromList-命令可能如下所示:

public class DeleteFromList<T> : IMyUndoObject{
    private T item;
    private List<T> list;
    public DeleteFromList<T>(T item, List<T> list) => (item, list) = (item, list);
    public void Undo() => list.Add(item);
}

和wpf命令(如

public class DeleteLastFooCommand : ICommand{
    private List<Foo> foos;
    private MyUndoStack undoStack;
    public DeleteFooCommand(List<Foo> foos, MyUndoStack undoStack) => (foos, undoStack) = (foos, undoStack);
    public void Execute(){
        var removed = foos.Last();
        foos.RemoveAt(foos.Count - 1);
        undoStack.Push(new DeleteFromList<Foo>(removed, foos));
}

要真正撤消一个操作,您需要一个单独的wpf命令,它会请求撤消堆栈弹出最后一个撤消对象,并运行它的撤消方法(如果这样的对象可用)。
这将把状态从wpf命令中移到一个单独的对象中,从而巧妙地避免了这个问题。
然而,我也会考虑只保存应用程序的完整状态。每种方法都有其优点,但您可能希望能够保存状态。通常,此状态只有几千字节,因此您可能可以维护数百个撤消状态,而无需担心内存问题。

相关问题