> 技术文档 > TDocStd_Document 的事务和撤销/重做(Undo/Redo)机制_cpp document 事务

TDocStd_Document 的事务和撤销/重做(Undo/Redo)机制_cpp document 事务

我们来深入探讨一下 TDocStd_Document事务和撤销/重做(Undo/Redo)机制。这套机制是 Open CASCADE (OCCT) 应用框架的核心,保证了数据操作的原子性和可恢复性。其核心思想类似于数据库的事务和版本控制系统(如 Git)。每一次提交的操作都被记录成一个“变更集”,你可以应用这个“变更集”的反向操作来撤销它。


## 核心组件

这套机制主要由以下几个关键类协同工作:

  1. TDocStd_Document: 扮演着管理器 (Manager) 的角色。它持有 UndoRedo 的列表,并提供了 OpenCommandCommitCommandAbortCommandUndoRedo 等公共接口来协调整个过程。

  2. TDF_Data: 这是文档的核心数据容器。所有的几何形状、属性、元数据等都以标签(TDF_Label)和属性(TDF_Attribute)的形式存储在这里。事务机制只关心TDF_Data 的修改。

  3. TDF_Transaction: 这是一个临时的变更记录器。当你调用 OpenCommand 时,文档会创建一个 TDF_Transaction 并将其附加到 TDF_Data 上。从此刻起,任何对 TDF_Data 的修改(增、删、改标签或属性)都会被这个事务对象实时捕捉。

  4. TDF_Delta: 这是一个变更集 (Changeset)。当事务被提交(CommitCommand)时,TDF_Transaction 会将它记录的所有变更打包成一个不可变的 TDF_Delta 对象。这个对象包含了从事务开始到结束之间所有数据变化的完整信息。

  5. TDF_DeltaList: 这是一个 TDF_Delta 对象的列表(实际上是堆栈TDocStd_Document 内部维护着两个这样的列表:myUndosmyRedos


## 工作流程 ⚙️

下面我们通过一个完整的操作流程来理解这些组件是如何协作的。

1. 开启事务 (OpenCommand)
doc->OpenCommand();
  • 动作: TDocStd_Document 创建一个内部的 TDF_Transaction 对象 (myUndoTransaction)。
  • 目的: 告诉文档:“准备好,我要开始做一系列修改了,请开始记录。”
  • 状态: 此时,任何对 TDF_Data 的操作都会被 myUndoTransaction 记录下来。
2. 执行数据修改
// 假设这里执行了一些操作,比如创建一个形状并给它命名TDF_Label shapeLabel = ...;TNaming_Builder(shapeLabel).Generated(aBox);TDataStd_Name::Set(shapeLabel, \"MyBox\");
  • 动作: 这些代码修改了 TDF_Data,例如添加了新的标签和 TNaming_NamedShapeTDataStd_Name 属性。

  • 后台: myUndoTransaction 悄悄地记录了这些变更,比如:“在标签 X 添加了属性 Y”、“修改了属性 Z 的值”等。

3. 结束事务(两种可能)
路径 A:提交事务 (CommitCommand) ✅
doc->CommitCommand();
  1. TDocStd_Document 命令 myUndoTransaction 将其记录的所有变更打包成一个 TDF_Delta 对象。
  2. 这个新创建的 TDF_Delta压入 myUndos 堆栈的顶部。
  3. 清空 myRedos 堆栈。这是非常关键的一步,因为一个新的操作会使之前的“重做”历史失效(与大多数软件的逻辑一致)。
  4. myUndoTransaction 被销毁,事务结束。
路径 B:中止事务 (AbortCommand) ❌
doc->AbortCommand();
  1. TDocStd_Document 命令 myUndoTransaction 执行反向操作
  2. myUndoTransaction 会遍历它记录的所有变更,并一个一个地撤销它们,从而将 TDF_Data 恢复到 OpenCommand 被调用前的原始状态
  3. myUndosmyRedos 堆栈保持不变
  4. myUndoTransaction 被销毁,事务结束。
4. 撤销操作 (Undo) ↩️
doc->Undo();
  1. 检查 myUndos 堆栈是否为空。如果为空,则什么也不做。
  2. myUndos 堆栈顶部取出(Pop)最近的一个 TDF_Delta
  3. TDF_Data 应用这个 TDF_Delta 的反向操作,使数据恢复到这个变更集被应用之前的状态。
  4. 将这个 TDF_Delta 压入 myRedos 堆栈的顶部,以备将来重做。
5. 重做操作 (Redo) ↪️
doc->Redo();
  1. 检查 myRedos 堆栈是否为空。如果为空,则什么也不做。
  2. myRedos 堆栈顶部取出(Pop)最近的一个 TDF_Delta
  3. TDF_Data 正向应用这个 TDF_Delta,重新执行之前的操作。
  4. 将这个 TDF_Delta 压回 myUndos 堆栈的顶部。

## 图解流程

下面是一个简化的图示,展示了 TDF_DeltamyUndosmyRedos 堆栈之间的移动:

初始状态:myUndos: []myRedos: []--> OpenCommand()--> (修改 A)--> CommitCommand()状态 1:myUndos: [Delta_A]myRedos: []--> OpenCommand()--> (修改 B)--> CommitCommand()状态 2:myUndos: [Delta_B, Delta_A]myRedos: []--> Undo()状态 3:myUndos: [Delta_A]myRedos: [Delta_B]--> Undo()状态 4:myUndos: []myRedos: [Delta_A, Delta_B]--> Redo()状态 5 (同状态 3):myUndos: [Delta_A]myRedos: [Delta_B]--> OpenCommand()--> (修改 C)--> CommitCommand()状态 6 (Redo历史被清空):myUndos: [Delta_C, Delta_A]myRedos: []

## 总结

  • 原子性: OpenCommandCommitCommand AbortCommand 保证了一系列操作要么全部成功并记录,要么全部被撤销,使数据保持一致。

  • 基于变更集: 整个机制不是简单地保存整个文档的快照,而是高效地记录和应用“变更集” (TDF_Delta)。

  • 仅限 TDF 数据: 此机制只对 TDF_Data 内部的修改有效。任何外部副作用,如写入文件、更新UI、打印日志等,都不会被记录,也无法通过 UndoAbortCommand 回滚。

  • 堆栈模型: UndoRedo 的实现是经典的数据结构——双堆栈模型,一个用于撤销,一个用于重做。