The IOleUndoManager interface enables containers to implement multi-level undo and redo operations for actions that occur within contained controls.
The control must create an undo unit with the IOleUndoUnit interface or a parent undo unit with the IOleParentUndoUnit interface derived from IOleUndoUnit. Both of these interfaces perform the undo action and the parent undo unit additionally can contain nested undo units.
The undo manager provides a centralized undo and redo service. It manages parent undo units and simple undo units on the undo and redo stacks. Whether an object is UI-active or not, it can deposit undo units on these stacks by calling methods in the undo manager.
The centralized undo manager then has the data necessary to support the undo and redo user interface for the host application and can discard undo information gradually as the stack becomes full.
The undo manager is implemented as a service and objects obtain a pointer to IOleUndoManger from the IServiceProvider interface. It is usually implemented by the container. The service manages two stacks, the undo stack and the redo stack, each of which contains undo units generated by embedded objects or by the container application itself.
Undo units are typically generated in response to actions taken by the end user. An object does not generate undo actions for programmatic events. In fact, programmatic events should clear the undo stack since the programmatic event can possibly invalidate assumptions made by the undo units on the stack.
When the object's state changes, it creates an undo unit encapsulating all the information needed to undo that change. The object calls methods in the undo manager to place its undo units on the stack. Then, when the end user selects an Undo operation, the undo manager takes the top undo unit off the stack, invokes its action by calling its IOleUndoUnit::Do method, and then releases it. When an end user selects a Redo operation, the undo manager takes the top redo unit off the stack, invokes its action by calling its IOleUndoUnit::Do method, and then releases it.
The undo manager has three states: the base state, the undo state, and the redo state. It begins in the base state. To perform an action from the undo stack, it puts itself into the undo state, calls IOleUndoUnit::Do on the undo unit, and goes back to the base state. To perform an action from the redo stack, it puts itself into the redo state, calls IOleUndoUnit::Do on the undo unit, and goes back to the base state.
If the undo manager receives a new undo unit while in the base state, it places the unit on the undo stack and discards the entire redo stack. While it is in the undo state, it puts incoming units on the redo stack. While it is in the redo state, it places them on the undo stack without flushing the redo stack.
The undo manager also allows objects to discard the undo or redo stack starting from any object in either stack.
The host application determines the scope of an undo manager. For example, in one application, the scope might be at the document level; a separate undo manager is maintained for each document; and undo is managed independently for each document. However, another application maintain one undo manager, and therefore one undo scope, for the entire application.
Having an undo operation fail and leaving the document in an unstable state is something the undo manager, undo units, and the application itself all have to work together to avoid. As a result, there are certain requirements that undo units, the undo manager, and the application or component using undo must conform to.
To perform an undo, the undo manager calls IOleUndoUnit::Do on one or more undo units which can, in turn, contain more units. If a unit somewhere in the hierarchy fails, the error will eventually reach the undo manager, which is responsible for making an attempt to roll back the state of the document to what it was before the call to the last top-level unit. The undo manager performs the rollback by calling IOleUndoUnit::Do on the unit that was added to the redo stack during the undo attempt. If the rollback also fails, then the undo manager is forced to abandon everything and return to the application. The undo manager indicates whether or not the rollback succeeded, and the application can take different actions based on this, such as reinitializing components so they're in a known state.
All the steps in adding an undo unit to the stack should be performed atomically. That is, all steps must succeed or none of them should succeed.
The host application that provides the undo manager decides what action to take when undo fails. At the very least, it should inform the user of the failure. The host application will be told by the undo manager whether or not the undo succeeded and whether or not the attempted rollback succeeded. In case both the undo and rollback failed, the host application can present the user with several options, including immediately shutting down the application.
Simple undo units must not change the state of any object if they return failure. This includes the state of the redo stack or undo stack if performing a redo. They are also required to put a corresponding unit on the redo or undo stack if they succeed. The application should be stable before and after the unit is called.
Parent undo units have the same requirements as simple units, with one exception. If one or more children succeeded prior to another child's failure, the parent unit must commit its corresponding unit on the redo stack and return the failure to its parent. If no children succeeded, the parent unit should commit its redo unit only if it has made a state change that needs to be rolled back. For example, suppose a parent unit contains three simple units. The first two succeed and added units to the redo stack, but the third one failed. At this point, the parent unit commits its redo unit and returns the failure.
As a side effect, the parent unit should never make state changes that depend on the success of their children. Doing this will cause the rollback behavior to break. If a parent unit makes state changes, it should do them before calling any children. Then, if the state change fails, it should not commit its redo unit, it should not call any children, and it should return the failure to its parent.
The undo manager has one primary requirement for error handling: to attempt rollback when an undo or redo fails.
Objects that do not support multi-level undo can cause serious problems for a global undo service. Since the object cannot be relied on to properly update the undo manager, any units submitted by other objects are also suspect, because their units may rely on the state of the non-compliant object. Attempting to undo a compliant object's units may not be successful, because the state in the non-compliant object will not match.
To detect objects that do not support multi-level undo, check for the OLEMISC_SUPPORTSMULTILEVELUNDO value. An object that can participate in the global undo service sets this value.
When an object without this value is added to a user-visible undo context, the safest thing to do is disable the undo user interface for this context. Alternatively, a dialog could be presented to the user, asking them whether to attempt to provide partial undo support, working around the non-compliance of the new object.
In addition, non-compliant objects may be added to nested containers. In this case, the nested container needs to notify the undo manager that undo can no longer be safely supported by calling IOleUndoManager::Enable(FALSE).
Implement this interface to provide centralized undo services to the objects in a container.
Call the methods in this interface to participate in global undo services.
IUnknown Methods | Description |
---|---|
QueryInterface | Returns a pointer to a specified interface. |
AddRef | Increments the reference count. |
Release | Decrements the reference count. |
IOleUndoManager Methods | Description |
---|---|
Open | Opens a new parent undo unit, which becomes part of its containing unit's undo stack. |
Close | Closes the specified parent undo unit. |
Add | Adds a simple undo unit to the collection. |
GetOpenParentState | Returns state information about the innermost open parent undo unit. |
DiscardFrom | Instructs the undo manager to discard the specified undo unit and all undo units below it on the undo or redo stack. |
UndoTo | Instructs the undo manager to perform actions back through the undo stack, down to and including the specified undo unit. |
RedoTo | Instructs the undo manager to invoke undo actions back through the redo stack, down to and including the specified undo unit. |
EnumUndoable | Creates an enumerator object that the caller can use to iterate through a series of top-level undo units from the undo stack. |
EnumRedoable | Creates an enumerator object that the caller can use to iterate through a series of top-level undo units from the redo stack. |
GetLastUndoDescription | Returns the description for the top-level undo unit that is on top of the undo stack. |
GetLastRedoDescription | Returns the description for the top-level undo unit that is on top of the redo stack. |
Enable | Enables or disables the undo manager. |
Windows NT: Use version 4.0 or later. New for OC96.
Windows: Use Windows 95 or later. New for OC96.
Windows CE: Unsupported.
Header: Declared in ocidl.h.
IOleParentUndoUnit, IOleUndoUnit