【问题标题】:Undo Redo in WPF/C# in an already functional application在 WPF/C# 中撤消重做已经可以运行的应用程序
【发布时间】:2011-05-19 07:02:24
【问题描述】:

我已经对如何实现这个问题的标题进行了一些研究。我正在开发的应用程序已经开发了几年左右(虽然进展缓慢,你们都知道它在现实世界中的情况)。现在我需要加入 Undo/Redo 多级功能。现在说“你应该在开始之前考虑过这个”有点晚了......好吧,我们确实考虑过 - 我们对此没有采取任何行动,现在它就在这里。通过搜索 SO(和外部链接),我可以看到两种最常见的方法似乎是......

Command Pattern

Memento Pattern

命令模式看起来会做很多工作,我只能想象它也会在这个过程中抛出数千个错误,所以我不太喜欢那个。

Memento 模式实际上很像我对此的想法。我在想是否有某种方法可以快速拍摄当前内存中的对象模型的快照,然后我可以将它存储在某个地方(也许也在内存中,也许在文件中)。这似乎是一个好主意,我能看到的唯一问题是它如何与我们已经编写的内容集成。您会看到我们的应用程序,它在一个大面板(可能有数百个)中绘制图像,然后允许用户通过 UI 或通过自定义构建的属性网格来操作它们。整个应用程序与一个大的观察者模式相连。第二个任何变化,事件被触发,所有需要更新的东西都会发生。这很好,但我不禁想到,如果用户在属性网格上的 texfield 中输入文本,在 UI 赶上之前会有一点延迟(似乎每次用户按下一个键,都会添加一个新快照到撤消列表)。所以我的问题是......

  • 您是否知道任何可能有效的 Memento 模式替代方案。
  • 您认为 Memento 模式是否适合这里,或者它会使应用程序变慢太多。
  • 如果要采用 Memento 模式,那么制作对象模型快照的最有效方法是什么(我在考虑对其进行序列化之类的)
  • 快照应该存储在内存中还是可以将它们放入文件中?

如果你已经走到这一步,感谢你阅读。您的任何意见都将是宝贵的,非常感谢。

【问题讨论】:

    标签: c# .net wpf design-patterns undo-redo


    【解决方案1】:

    嗯,这是我对这个问题的看法。

    1- 您需要多级撤消/重做功能。所以您需要存储用户执行的操作,这些操作可以存储在堆栈中。

    2-您的第二个问题是如何识别我认为通过 Memento 模式进行的操作已更改的内容,这是一个相当大的挑战。 Memento 就是在你的记忆中撕裂初始对象状态。

    要么,您需要存储操作更改的内容,以便您可以使用此信息撤消操作。

    命令模式是为撤消/重做功能而设计的,我会说它已经晚了,但它值得实现已经使用了几年并且适用于大多数应用程序的设计。

    【讨论】:

    • +1 当您说撤消/重做时,您会自动说命令模式。不管是容易实施还是难以实施。
    • 目前的应用程序完全与观察者联系起来。让应用程序创建一个“纪念品”就像编写函数来创建它一样简单,然后将看守者订阅到所需的观察者。我不喜欢 Memento,但我倾向于它。否则我会很高兴地被说服。
    【解决方案2】:

    如果性能允许,您可以在每次操作之前序列化您的域。如果对象本身不大,几百个对象也不算多。

    由于您的对象图可能很重要(即使用继承、循环等),因此集成的 XmlSerializer 和 JsonSerializer 是毫无疑问的。 Json.net 支持这些,但对某些类型(本地日期时间、数字等)进行了一些有损转换,所以它也很糟糕。

    我认为 protobuf 序列化程序需要某种形式的 DTD(.proto 文件)或使用将名称映射到数字的属性来修饰所有属性,因此它可能不是最佳的。

    BinaryFormatter 可以序列化大多数东西,你只需要用 [Serializable] 属性装饰所有类。但是我自己没有使用它,所以可能存在我不知道的陷阱。可能与单例或事件有关。

    【讨论】:

    • 我希望会是这样。使用 Memento 模式并将对象模型保存为某种序列化状态。你知道做这种事情的快速方法吗?
    • BinaryFormatter 是我的第一个猜测。
    【解决方案3】:

    撤消/重做的关键是

    • 了解需要保存和恢复的状态
    • 知道何时需要保存状态

    事后添加撤消/重做总是一件痛苦的事情 - (我知道这个评论现在对你没有用,但最好在开始之前将支持设计到应用程序框架中,因为它可以帮助人们在整个开发过程中使用撤消友好模式)。

    可能最简单的方法是基于纪念品的方法:

    • 找到构成“文档”的所有数据。你能以某种方式统一这些数据,使其形成一个连贯的整体吗?通常,如果您可以将文档结构序列化为文件,那么您需要的逻辑在序列化系统中,这样就可以进入。直接使用它的缺点通常是您通常必须序列化所有内容,以便撤消将是巨大而缓慢的。如果可能,重构代码,以便 (a) 在整个应用程序中使用一个通用的序列化接口(因此可以使用通用调用保存/恢复数据的任何部分),以及 (b) 每个子系统都被封装因此对数据的修改必须通过一个通用接口(而不是很多人直接修改成员变量,他们都应该调用对象提供的 API 来请求它对自身进行更改)和 (c) 每个子部分数据保留“版本号”。每次进行更改时(通过 (b) 中的界面),它应该增加该版本号。这种方法意味着您现在可以扫描整个文档并使用版本号来查找自上次查看后发生更改的部分,然后序列化最小数量以保存和恢复更改的状态。

      • 提供一种可以记录单个撤消步骤的机制。这意味着允许多个系统对数据结构进行更改,然后当所有内容都更新后,触发撤消记录。确定何时执行此操作可能很棘手,但通常可以通过在您的 UI 完成处理每个输入事件时扫描您的文档以查找消息循环中的更改(见上文)来完成。

    除此之外,我建议采用基于命令的方法,因为它除了撤消/重做之外还有很多好处。

    【讨论】:

    • 非常感谢您的意见。我很感激。
    【解决方案4】:

    您可能会发现受监控的撤消框架很有用。 http://muf.codeplex.com/

    它使用类似于 memento 模式的东西,通过在更改发生时监视更改并允许您将委托放在撤消堆栈上,以撤消/重做更改。

    我考虑了一种可以序列化/反序列化文档的方法,但担心开销。相反,我按属性基础监视属性上的模型(或视图模型)的变化。然后,根据需要,我使用 MUF 库来“批量”相关更改,以便它们作为一个更改单元撤消/重做。

    您的 UI 设置可以对底层模型的变化做出反应,这一点很好。听起来您可以在那里注入撤消/重做逻辑,并且更改会冒泡到 UI。

    我认为您不会看到太多延迟或性能下降。我有一个类似的应用程序,我们根据模型中的数据绘制了一个图表。到目前为止,我们在这方面取得了不错的成绩。

    您可以在http://muf.codeplex.com/ 的 codeplex 站点上找到更多信息和文档。该库也可通过 NuGet 获得,支持 .NET 3.5、4.0、SL4 和 WP7。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-09
      • 2015-03-30
      相关资源
      最近更新 更多