在已正常运行的应用程序中在 WPF/C# 中撤消重做

发布于 2024-10-05 21:05:10 字数 1052 浏览 0 评论 0原文

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

命令模式

备忘录模式

命令模式看起来会是一项繁重的工作,我只能想象它也会在这个过程中引发数千个错误,所以我不太喜欢那个。

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

  • 你知道 Memento 模式有什么好的替代方案吗?
  • 您认为 Memento 模式适合这里吗?或者它会降低应用程序的运行速度吗?
  • 如果纪念品模式是可行的方法,那么制作对象模型快照的最有效方法是什么(我正在考虑将其序列化或其他什么)
  • 快照应该存储在内存中还是可以将它们放入文件中?

如果您已经读到这里,非常感谢您的阅读。您提供的任何意见都将非常有价值并且非常感激。

I have done some research already as to how I can achieve the title of this question. The app I am working on has been under development for a couple of years or so (slow progress though, you all know how it is in the real world). It is now a requirement for me to put in Undo/Redo multiple level functionality. It's a bit late to say "you should have thought about this before you started" ... well, we did think about it - and we did nothing about it and now here it is. From searching around SO (and external links) I can see that the two most common methods appear to be ...

Command Pattern

Memento Pattern

The command pattern looks like it would be a hell of a lot of work, I can only imagine it throwing up thousands of bugs in the process too so I don't really fancy that one.

The Memento pattern is actually a lot like what I had in my head for this. I was thinking if there was some way to quickly take a snapshot of the object model currently in memory, then I would be able to store it somewhere (maybe also in memory, maybe in a file). It seems like a great idea, the only problem I can see for this, is how it will integrate with what we have already written. You see the app as we have it draws images in a big panel (potentially hundreds) and then allows the user to manipulate them either via the UI or via a custom built properties grid. The entire app is linked up with a big observer pattern. The second anything changes, events are fired and everything that needs to update does. This is nice but I cant help thinking that if a user is entering text into a texfield on the properties grid there will be a bit of delay before the UI catches up (seems as everytime the user presses a key, a new snapshot will be added to the undo list). So my question to you is ....

  • Do you know of any good alternatives to the Memento pattern that might work.
  • Do you think the Memento pattern will fit in here or will it slow the app down too much.
  • If the Memento pattern is the way to go, what is the most efficient way to make a snapshot of the object model (i was thinking serialising it or something)
  • Should the snapshots be stored in memory or is it possible to put them into files?

If you have got this far, thankyou kindly for reading. Any input you have will be valuable and very much appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

云雾 2024-10-12 21:05:10

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

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

2-你的第二个问题如何识别操作改变了什么,我认为通过纪念品模式,这是一个相当大的挑战。 Memento 就是要破坏你记忆中的初始对象状态。

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

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

Well , Here is my thought on this problem.

1- You need multi level undo/redo functionality. so you need to store user actions performed which can be stored in a stack.

2- Your second problem how to identify what has been changed by a operation i think through Memento pattern , it is quite a challenge. Memento is all about toring initial object state in your memory.

either , you need to store what is changed by a operation so that you can use this information to undo the opertions.

Command pattern is designed for the Undo/Redo functionality and i would say that its late but its worth while to implement the design which is being used for several years and works for most of the applications.

蓝礼 2024-10-12 21:05:10

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

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

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

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

If performance allows it you could serialize your domain before each action. A few hundred objects is not much if the objects aren't big themselves.

Since your object graph is probably non trivial (i.e. uses inheritance, cycles,...) the integrated XmlSerializer and JsonSerializers are out of question. Json.net supports these, but does some lossy conversions on some types (local DateTimes, numbers,...) so it's bad too.

I think the protobuf serializers need either some form of DTD(.proto file) or decoration of all properties with attributes mapping their name to a number, so it might not be optimal.

BinaryFormatter can serialize most stuff, you just need to decorate all classes with the [Serializable] attribute. But I haven't used it myself, so there might be pitfalls I'm not aware of. Perhaps related to Singletons or events.

送舟行 2024-10-12 21:05:10

撤消/重做的关键是

  • 知道您需要保存和恢复什么状态
  • ,知道何时需要保存状态

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

最简单的方法可能是基于备忘录的方法:

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

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

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

The critical things for undo/redo are

  • knowing what state you need to save and restore
  • knowing when you need to save the state

Adding undo/redo after the fact is always a painful thing to do - (I know this comment is of no use to you now, but it's always best to design support into the application framework before you start, as it helps people use undo-friendly patterns throughout development).

Possibly the simplest approach will be a memento-based one:

  • Locate all the data that makes up your "document". Can you unify this data in some way so that it forms a coherent whole? Usually if you can serialise your document structure to a file, the logic you need is in the serialisation system, so that gives you a way in. The down side to using this directly is usually that you will usually have to serialise everything so your undo will be huge and slow. If possible, refactor code so that (a) there is a common serialisation interface used throughout the application (so any and every part of your data can be saved/restored using a generic call), and (b) every sub-system is encapsulated so that modifications to the data have to go through a common interface (rather than lots of people modifying member variables directly, they should all call an API provided by the object to request that it makes changes to itself) and (c) every sub-portion of the data keeps a "version number". Every time an alteration is made (through the interface in (b)) it should increment that version number. This approach means you can now scan your entire document and use the version numbers to find just the parts of it that have changed since you last looked, and then serialise the minimal amount to save and restore the changed state.

    • Provide a mechanism whereby a single undo step can be recorded. This means allowing multple systems to make changes to the data structure, and then when everything has been updated, triggering an undo recording. Working out when to do this may be tricky, but it can usually be accomplished by scanning your document for changes (see above) in your message loop, when your UI has finished processing each input event.

Beyond that, I'd advise going for a command based approach, because there are many benefits to it besides undo/redo.

白云悠悠 2024-10-12 21:05:10

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

它使用类似于备忘录模式的东西,通过监视更改它们发生并允许您将委托放在撤消堆栈上,这将反转/重做更改。

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

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

我认为您不会看到太多的延迟或性能下降。我有一个类似的应用程序,其中有一个我们根据模型中的数据呈现的图表。到目前为止,我们已经取得了良好的成果。

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

You may find the Monitored Undo Framework to be useful. http://muf.codeplex.com/

It uses something similar to the memento pattern, by monitoring for changes as they happen and allows you to put delegates on the undo stack that will reverse / redo the change.

I considered an approach that would serialize / deserialize the document but was concerned about the overhead. Instead, I monitor for changes in the model (or view model) on a property by property bases. Then, as needed, I use the MUF library to "batch" related changes so that they undo / redo as a unit of change.

The fact that you have your UI setup to react to changes in the underlying model is good. It sounds like you could inject the undo / redo logic there and the changes would bubble up to the UI.

I don't think that you'd see much lag or performance degradation. I have a similar application, with a diagram that we render based on the data in the model. We've had good results with this so far.

You can find more info and documentation on the codeplex site at http://muf.codeplex.com/. The library is also available via NuGet, with support for .NET 3.5, 4.0, SL4 and WP7.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文