4

dotnet 文档应用的撤销重做设计

 3 years ago
source link: https://lindexi.gitee.io/post/dotnet-%E6%96%87%E6%A1%A3%E5%BA%94%E7%94%A8%E7%9A%84%E6%92%A4%E9%94%80%E9%87%8D%E5%81%9A%E8%AE%BE%E8%AE%A1.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
dotnet 文档应用的撤销重做设计

文档应用是指如 Word 或 PPT 等的提供给用户进行内容创作的工具,而撤销重做其实也被称为撤销恢复功能。本文来告诉大家撤销重做这个模块的设计路线,从简单的复杂

大部分的应用软件都可以采用敏捷开发,不断进行迭代。应用的每个小功能都在不断迭代中,成为模块或者某个团队产品。撤销重做功能也是可以从一个小功能,迭代成为一个文档的核心模块

在软件开始开发的时候,很少会有人能了解这个软件产品的未来,如果此时就给很多小功能模块投入大量的资源,那大部分都会是浪费的。本文记录的功能的迭代也仅仅只是在我当前团队里面,跟随产品逐步修改的,不一定适合你当前的团队

本文以下的撤销重做和撤销恢复说的是相同的功能。但是本质上这个词是我当前团队用错的,如在Word里面的重做,也就是标题上左上角的按钮,其实指的是当前的输入再次输入,而恢复只有在用户点击撤销之后,才会看到恢复按钮

默认在 WPF 或 UWP 等应用的文本框或者富文本框里面都有自带的撤销恢复机制,只要自己不作,那么就依靠这个功能就能玩很长时间了

当更多的需要自己开始自定义各个控件的时候,此时就很难用上框架提供的撤销重做,需要自己定义一套撤销重做机制。从需求层面上讲,撤销就是撤回到上一个步骤,而重做或者说恢复其实就是在恢复撤销的步骤。可以看到越在后面添加的操作,在撤销的时候越快进行撤销。而越早撤销的操作,在重做的时候就越早重做。刚好,这就是数据结构的栈的定义,先进入的数据后拿出,后进入的数据先拿出

撤销重做的数据结构层面使用栈是最合适的,在使用了 栈 之后,撤销重做模块就有了一个概念叫 撤销重做栈

在软件开发里面,很多开发的开始是在定义数据结构或者说在设计类,但按照本文的编写方法,如果一开始就来开设计类,我预计将会十分无聊

在定义好了 撤销重做栈 之后,咱将会遇到一个问题,那就是这个 撤销重做栈 的代码应该如何写?在 dotnet 里面 Stack 可以表示栈这个数据结构,而 Stack 推荐是使用泛形的 Stack<T> 那问题是这个 T 应该是什么

假定咱的文档应用可以输入的内容,不单是文本,加入咱还可以输入图片,而图片还可以被拖动修改坐标,图片可以被修改颜色等,可以看到咱的应用可以输入的内容有很多不同的业务定义。因此咱需要有一个足够通用类型用来定义撤销重做操作

最基础的撤销重做操作其实只有两个动作,一个是就是被撤销,另一个就是被重做恢复,可以定义的类型如下

	interface IOperation
	{
		void Undo();

		void Redo();
	}

为什么我上面会推荐定义为接口而不是抽象类?原因是在 C# 里面是单继承的,如果是抽象的类,将会让某些业务的代码不好编写。有些业务的代码已经需要继承某个类了,而如果此时这个类需要插入到 撤销重做栈 将会发现不能再继承一个抽象类。另外,从撤销重做的业务上,也不需要使用抽象类,只需要有撤销和重做两个方法就可以

在应用程序可以根据业务定义多个撤销重做栈的内容,例如说做一个和 PPT 差很多的软件,有编辑和播放两个不同的界面,这两个界面的撤销重做相互独立,那么在这两个模式里面就可以定义两个不同的 撤销重做栈 对象

在撤销重做栈这个类型里面,最简单的版本是只有两个 Stack 一个是撤销另一个是恢复。在用户输入操作的时候,将操作放入到撤销的栈。在用户重做恢复时,从撤销的栈弹出操作,放入到重做恢复的栈里

随着业务的迭代,其实纯撤销重做栈会有一些通用的撤销恢复的功能还需要额外开发

提供当前合入多个不同的业务的操作做一个的业务,例如我有图片编辑模块,这个模块的编辑每一步默认都会作为一个操作加入到撤销重做栈,而我还有另一个是文本编辑模块,每一个文本编辑步骤就是一个操作。在我进入特殊的模式,例如是插入某个复杂元素,如公式,允许在公式里面编辑文本和图片。此时在插入公式过程中,编辑文本和图片每一步都可以撤销,而在插入公式完成之后,撤销的是整个公式。也就是说允许在底层撤销重做提供的一个功能,这个功能相当于一个开关,在打开的时候,插入的所有操作在开关关闭的时候将会合并成为一个操作的功能。在撤销重做模块提供这个功能可以方便业务端解耦,而这个功能就需要定义的操作类型里面有组合操作才能实现

咱上文说的操作,是指继承了 IOperation 接口的类。基本上每一个操作都是独立的,单独的。而组合操作是特殊的,在组合操作里面将会包含其他的多个操作,将会在撤销恢复时按照顺序进行撤销恢复

在实现了撤销重做的功能,每个业务都需要有 IOperation 来表示业务的用户输入,而刚好如果有漫游同步的功能,这个功能也是需要用户的输入。如果一开始在软件开发就判断有漫游同步的功能,那么将漫游同步和撤销重做同步是一个很好的设计

每一个用户的输入都可以被抽象为一个 IOperation 同时也是一个同步的对象,刚好两个功能可以作为一个功能实现,开发的工作量可以更少。如果有这样的需求,那么对于 IOperation 的设计上,就需要开发者设置为基于数据,不能基于对象的动作

另外,即使没有漫游同步的功能,其实文档保存也可以复用撤销重做提供的功能。在文档保存的时候,很多文档软件都有自动保存的功能,如 VS 软件。在文档内容很多,保存一次需要大量的时候时,就需要用到增量的功能,那么如何实现增量?如果文档可以划分为不同的元素,根据元素是否加入撤销重做就可以了解元素或页面等有没进行编辑,从而决定是否保存


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-%E6%96%87%E6%A1%A3%E5%BA%94%E7%94%A8%E7%9A%84%E6%92%A4%E9%94%80%E9%87%8D%E5%81%9A%E8%AE%BE%E8%AE%A1.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者前往 CSDN 关注我的主页

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号
lindexi%2F201985113622445

欢迎进入 Eleven 老师组建的 .NET 社区
lindexi%2F20209121930471745.jpg

以上广告全是友情推广,无盈利


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK