设计一个通用的撤销/重做框架是一项挑战,因为它需要解决至少两个核心问题:一是定义用户操作的范围,二是确保用户执行的每一个动作都可以撤销。例如,将用户输入的每个字符都设为可撤销的,可能会影响用户体验,但这至少是一个开始。理想的框架应该允许代码聚合变化。
总体目标是有序地观察系统中所有对象的变化,而这些对象的数量可能是未知的。例如,对于任何给定的用户操作,可能创建和/或修改了50个对象,将所有这些变化明确地汇总成一个撤销操作对于用户来说可能是繁琐的,甚至在现实世界中是不可能的。
为了支持任何ViewModel或集合的撤销/重做,有以下组件:
这是一个加强版的ObservableCollection,它会自动处理UI线程的上下文切换,无缝跟踪所有操作并使其可撤销,并且可以在任何时候开启或关闭。它像UI需要的那样正确地、同步地触发CollectionChanged和PropertyChanged事件,并且可以很好地处理并发。
这是一个超充能版的Dictionary,它同样可以正确地、同步地触发CollectionChanged和PropertyChanged事件,并且可以很好地处理并发。如果设置为在UI线程上触发,它也会自动处理UI线程的上下文切换。
这是一个高性能的List<>,可以很好地处理并发。它有许多内置的线程安全的帮助方法,如RemoveAndGetIndex、ReplaceAll等,也可以作为Stack<>或Queue<>使用。
用于记录所有操作以便于以后撤销。它可以用作应用程序范围操作的单例,或者作为局部操作(如对话框中的操作)的实例。
管理Accumulator的撤销和重做栈。自动记录任何撤销操作的重做操作,反之亦然。
自动跟踪其属性的所有变化,并将其引用的Accumulator(无论是单例还是局部实例)中的变化记录下来。通过使用自定义的Get<>/Set<>方法来跟踪任何属性的变化。
这是一个实用类,可以轻松地创建一个Accumulator并在Dispose时将其推送到AccumulatorManager。在其using块中的所有变化都会被汇总成一个撤销操作。
要使用这个框架,需要创建一个继承自TrackableViewModel的ViewModel,并给它一些属性。对于需要的任何集合,使用TrackableCollection。当想要跟踪变化时,创建范围,或者设置Globals.ScopeEachChange为true。
这个框架在过去的4年多时间里被设计、提炼和完善,现在是时候发布了,以便任何想要在他们的应用程序中添加这个功能的人都能从中受益。
与任何处理高性能并发活动的框架一样,存在创建线程锁的可能性,因此请继续阅读以了解如何避免这种情况。