65.9K
CodeProject 正在变化。 阅读更多。
Home

实现撤销/重做 - DocVars 方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.39/5 (11投票s)

2002年12月16日

6分钟阅读

viewsIcon

71301

downloadIcon

2351

本教程解释了一种在 MFC 应用程序中实现撤销/重做功能的通用方法。该技术允许您在编辑会话内部和之间保存和循环撤销/重做状态。

Demo Application

引言

我很高兴能向 Code Project 提交一篇教程,因为我从这里慷慨而有用的资源中受益匪浅。

本教程旨在解释我是如何为自己的应用程序开发撤销/重做功能的。它对我来说运行得很好;也许这里的其他人会觉得它有用。我将其称为“DocVars”方法。

DocVars 方法的基本理念阐述如下:

  • 打包 - 将文档所有可撤销和可重做的变量放入一个单独的类(例如 CDocVars)。

  • 将 DocVars 作为一个完整的包进行处理和修改 - 如果文档当前 DocVars 中的一个变量需要更改,则会创建一个新的 DocVars 实例来反映该更改。

  • 服务 – 随着文档的修改,管理一个 CDocVars 列表。本教程使用 CUpdateMgr 来完成此工作。管理职责包括:

    • 从文档接收新 DocVars 的更新并将其添加到列表中。

    • 响应文档的撤销和重做请求,将列出的 DocVars 发送给文档。

  • 保管职责 – 一个单一的类必须承担支持当前和活动 DocVars 的责任。本教程使用文档作为 DocVars 的保管者。在这种情况下,文档是应用程序可撤销/可重做变量的中央枢纽。所有重要变量都由文档维护,要访问它们,所有其他类都必须查阅文档。

下面的对话应该有助于解释系统的工作原理。

CView:

嘿,CDocument!你有什么?

CDocument:

那都取决于你想要它做什么。

CView:

我需要修改你的一个变量。

CDocument:

好的,我要把我的当前 DocVars 包的副本发送给你。进行你需要的任何更改。完成后,把它还给我。

CView:

整个包?!天哪!我只想做一个微不足道的改动。你不能只把我要的那个变量发给我吗?

CDocument:

老兄!是 包-装。明白吗?

CView:

好吧,但这看起来是做事情的笨方法。

CDocument:

别抱怨了,赶紧回去工作。

CView:

好的,我已经修改了我的 DocVars 副本。你想要吗?

CDocument:

是的,把它还给我。

CView:

现在会发生什么?

CDocument:

我创建一个独特的副本。如果 DocVars 有指针变量,我必须为新指针分配内存,然后用你刚发给我的 DocVars 中的参数填充它们。之后,我将其发送给 CUpdateMgr。他将其添加到列表中,完成后将一份副本发回给我。

CView:

你开玩笑吧!!多么错综复杂的官僚机构!!你和车管所一样糟糕。

CDocument:

请闭嘴,让我做我的工作。

CDocument:

你好,CUpdateMgr?我有一个新的 DocVars 包给你。请更新你的列表。

CUpdateMgr:

收到了。我正在把它添加到列表的末尾。完成了。现在我正在把你的一份副本发回去。

CDocument:

收到了。谢谢。你还给我的包现在是我的当前 DocVars。

当天晚些时候...

CMainFrame:

嘿,CDocument!某个用鼠标的怪胎刚刚点击了“撤销”按钮。

CDocument:

谢谢你的消息。我会告诉 CUpdateMgr

CDocument:

嘿,CUpdateMgr!我刚刚收到一个撤销请求。

CUpdateMgr:

好的。让我在 DocVars 列表中向后滚动一个位置。找到了。好的,我正在把你旧的 DocVars 发送给你。

CDocument:

收到了,现在它是我的当前 DocVars。

那天晚得多的时间...

CView:

嘿,CDocument!你有什么?

CDocument:

那都取决于。你需要什么?

CView:

我只需要检查你一个 DocVars 变量的值。

CDocument:

好的,我将把一个指向我当前 DocVars 的指针发送给你。

CView:

太棒了!

CDocument:

但是你不要动那些变量。禁止修改!听见了吗,小子?!

CView:

为什么不?

CDocument:

因为你会完全破坏我和 CUpdateMgr 在这里做得好好的事情,而且你会毁掉整个撤销/重做功能。

CView:

收到!禁止修改。只是看看。

使用代码

CDocument 或不是

虽然示例应用程序使用 MFC 文档/视图设置,但文档/视图设置不是必需的。诀窍是将一个单一的类设置为保管者。它不需要是 CDocument。例如,一个基于对话框的应用程序可以使用主对话框类作为 DocVars 的保管者。

序列化

示例应用程序使用 MFC 序列化。为了实现这一点,CDocVars 和所有基于类的 DocVars 变量都必须支持序列化。由于 CObject 支持序列化,我将 CDocVars 派生自 CObject。如果您需要基于自定义类的 DocVars 变量,请考虑将它们派生自 CObject。请记住,当您从 CObject 派生一个新类时,您将需要为它编写一个复制构造函数。此外,您将需要准备一个赋值运算符 (=),并且可能根据您的需要准备其他运算符。请参阅示例应用程序源代码中的 DocVars.h,了解我是如何实现的。

模板

CUpdateMgr 使用了一个自定义列表类 CtObjectList。这个列表类是一个从 CList 派生的模板类。对我来说,模板有点神秘。但有一点是肯定的,它们非常方便。在这个例子中,我必须准备一个 (=) 赋值运算符,我只重写了 Serialize() 函数。请注意,CtObjectList 期望处理可序列化类型。在本例中,它处理从可序列化 CObject 类派生的 CDocVars 类型。

无限撤销

CUndo_Redo_DemoDoc 类中的 Serialize() 重写函数被设置为可以保留所有撤销状态直到文档创建。也就是说,无论您有多少个编辑会话,您仍然可以撤销到最开始。这个功能的缺点是您可能会得到相当大的文件。如果这是一个问题,我建议您限制 UpdateMgr 列表中 DocVars 的数量,并将其设置为用户可配置的值。或者您可以简单地通过注释掉 Undo_Redo_DemoDoc.cpp 中的 #define INFINITE_UNDO 语句来禁用此会话间功能。

指针

别忘了,如果您在 DocVars 中使用指针变量,您必须将每个 DocVars 中的指针与内存中的唯一空间关联起来。否则,DocVars 列表中所有 DocVars 指针变量都将指向相同的内存地址——这有点违背了目的。我将把如何做到最好留给您自己去解决。

DocVarsGetByValue()

此函数主要用于文档传递其当前 DocVars 副本。这样调用函数就可以修改其 DocVars 副本,而不会直接影响文档的当前 DocVars。

DocVarsGetPointer()

当您需要查找文档当前 DocVars 中变量的值时,请调用此函数。持有此指针时,请勿修改 DocVars 变量。否则,将导致全面混乱。

示例 MSVS 项目

我以 Microsoft Visual C++ .NET 解决方案的形式提供示例源代码。MSVC++ 6 的用户可能会发现创建一个新的 SDI/DocView 工作区然后简单地从示例中添加文件会更容易。

© . All rights reserved.