Code:
/ DotNET / DotNET / 8.0 / untmp / whidbey / REDBITS / ndp / fx / src / Designer / Host / UndoEngine.cs / 1 / UndoEngine.cs
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
namespace System.ComponentModel.Design {
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Design;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Globalization;
///
///
/// The UndoEngine is a class that can be instantiated to support automatic
/// undo. Normally, to support undo, a developer must create individual
/// undo units that consist of an undo action and a redo action. This is
/// fragile because each and every action the user performs must be wrapped
/// in an undo unit. Worse, if a user action is not wrapped in an undo unit
/// its absence on the undo stack will break undo because each individual
/// unit always assumes that the previous unit's state is maintained. The
/// UndoEngine, on the other hand, listens to change events and can create
/// undo and redo actions automatically. All that is necessary to implement
/// undo is to add these actions to an undo/redo stack and instantiate this
/// class.
///
public abstract class UndoEngine : IDisposable {
private static TraceSwitch traceUndo = new TraceSwitch("UndoEngine", "Trace UndoRedo");
private IServiceProvider _provider;
private Stack _unitStack; // the stack of active (non-committed) units.
private UndoUnit _executingUnit; // the unit currently executing an undo.
private IDesignerHost _host;
private ComponentSerializationService _serializationService;
private EventHandler _undoingEvent;
private EventHandler _undoneEvent;
private IComponentChangeService _componentChangeService;
private Dictionary> _refToRemovedComponent;
private bool _enabled;
///
///
/// Creates a new UndoEngine. UndoEngine requires a service
/// provider for access to various services. The following
/// services must be available or else UndoEngine will
/// throw an exception:
///
/// IDesignerHost
/// IComponentChangeService
/// IDesignerSerializationService
///
///
protected UndoEngine(IServiceProvider provider) {
if (provider == null) {
throw new ArgumentNullException("provider");
}
_provider = provider;
_unitStack = new Stack();
_enabled = true;
// Validate that all required services are available. Because undo
// is a passive activity we must know up front if it is going to work
// or not.
//
_host = GetRequiredService(typeof(IDesignerHost)) as IDesignerHost;
_componentChangeService = GetRequiredService(typeof(IComponentChangeService)) as IComponentChangeService;
_serializationService = GetRequiredService(typeof(ComponentSerializationService)) as ComponentSerializationService;
// We need to listen to a slew of events to determine undo state.
//
_host.TransactionOpening += new EventHandler(this.OnTransactionOpening);
_host.TransactionClosed += new DesignerTransactionCloseEventHandler(this.OnTransactionClosed);
_componentChangeService.ComponentAdding += new ComponentEventHandler(this.OnComponentAdding);
_componentChangeService.ComponentChanging += new ComponentChangingEventHandler(this.OnComponentChanging);
_componentChangeService.ComponentRemoving += new ComponentEventHandler(this.OnComponentRemoving);
_componentChangeService.ComponentAdded += new ComponentEventHandler(this.OnComponentAdded);
_componentChangeService.ComponentChanged += new ComponentChangedEventHandler(this.OnComponentChanged);
_componentChangeService.ComponentRemoved += new ComponentEventHandler(this.OnComponentRemoved);
_componentChangeService.ComponentRename += new ComponentRenameEventHandler(this.OnComponentRename);
}
///
/// Retrieves the current unit from the stack.
///
private UndoUnit CurrentUnit {
get {
if (_unitStack.Count > 0) {
return (UndoUnit)_unitStack.Peek();
}
return null;
}
}
///
///
/// This property indicates if an undo is in progress.
///
public bool UndoInProgress {
get {
return _executingUnit != null;
}
}
///
///
/// This property returns true if the Undo engine is currently enabled. When enabled,
/// the undo engine tracks changes made to the designer. When disabled, changes are
/// ignored. If the UndoEngine is set to disabled while in the middle of processing
/// change notifications from the designer, the undo engine will only ignore additional
/// changes. That is, it will finish recording the changes that are in process and only
/// ignore additional changes.
///
/// Caution should be used when disabling undo. If undo is disabled it is easy to
/// make a change that would cause other undo actions to become invalid. For
/// example, if myButton.Text was changed, and then myButton was renamed while
/// undo was disabled, attempting to undo the text change would fail because there
/// is no longer a control called myButton. Generally, you should never make changes
/// to components with undo disabled unless you are certain to put the components
/// back the way they were before undo was disabled. An example of this would be
/// to replace one instance of "Button" with another, say "SuperButton", fixing up
/// all the property values as you go. The result is a new component, but because
/// it has the same component name and property values, undo state will still be
/// consistent.
///
public bool Enabled {
get {
return _enabled;
}
set {
_enabled = value;
}
}
///
///
/// This event is raised immediately before an undo action is performed.
///
public event EventHandler Undoing {
add {
_undoingEvent += value;
}
remove {
_undoingEvent -= value;
}
}
///
///
/// This event is raised immediately after an undo action is performed. It
/// will always be raised even if an exception is thrown.
///
public event EventHandler Undone {
add {
_undoneEvent += value;
}
remove {
_undoneEvent -= value;
}
}
///
///
/// Adds the given undo unit into the undo stack. UndoEngine
/// does not maintain its own undo stack, so you must implement
/// this method yourself.
///
protected abstract void AddUndoUnit(UndoUnit unit);
///
/// This method will check to see if the current undo unit needs to be
/// popped from the stack. If it does, it will pop it and add it
/// to the undo stack. There must be at least one unit on the stack
/// to call this method.
///
/// When calling CheckPopUnit you must supply a reason for the call.
/// There are three reasons:
///
/// Normal
/// ======
/// Call with Normal if you are not calling in response to a closing
/// transaction. For normal pop reasons, the unit will be popped
/// if there is no current transaction. If the unit is not empty it
/// will be added to the undo engine. If there is a transaction in
/// progress, this method will do nothing.
///
/// TransactionCommit
/// =================
/// Call with TransactionCommit if you are calling in response to a
/// transaction closing, and if that transaction is marked as being
/// committed. CheckPopUnit will pop the unit off of the stack and
/// add it to the undo engine if it is not empty.
///
/// TransactionCancel
/// =================
/// Call with TransactionCancel if you are calling in response to a
/// transaction closing, and if that transaction is marked as being
/// cancelled. CheckPopUnit will pop the unit off of the stack. If
/// the unit is not empty Undo will be called on the unit to roll back
/// the transaction work. The unit will never be added to the undo
/// engine.
///
private void CheckPopUnit(PopUnitReason reason) {
// The logic in here is subtle. This code handles both committing
// and cancelling of nested transactions. Here's a summary of how
// it works:
//
// 1. Each time a transaction is opened, a new unit is pushed onto
// the unit stack.
//
// 2. When a change occurs, the change event checks to see if there
// is a currently executing unit. It also checks to see if the
// current unit stack is empty. If there is no executing unit
// (meaning that nothing is performing an undo right now), and
// if the unit stack is empty, the change event will create a
// new undo unit and push it on the stack.
//
// 3. The change event always runs through all undo units in the
// undo stack and calls the corresponding change method. In
// the normal case of a single transaction or no transaction,
// this will operate on just one unit. In the case of nested
// transactions there are two possibilities:
//
// a) We are adding undo information to a nested transaction.
// We want to add the undo information to all levels
// of nested transactions. Why? Because as a nested
// transaction is closed, it is either committed or
// cancelled. If committed, and if the transaction
// is not the top-most transaction, the transaction
// is actually just thrown away because its data is
// redundantly stored in the next transaction on the
// stack.
//
// b) We are adding undo information to a nested transaction,
// but that undo information is being created because
// an undo unit is being "undone". Remember that for
// nested transactions each undo unit higher on the stack
// has all the data that the lower units have. When
// a lower unit is undone, it is popped from the stack
// and all of the changes it makes are recorded on the
// higher level units. This combines the "do" and "undo"
// data into the higher level unit, which in effect
// subtracts the undone data from the higher level unit.
//
// 4. When a unit is undone it stores itself in a member variable
// called _executingUnit. All change events examine this variable
// and if it is set they do not create a new unit in response to
// a change. Instead, they just run through all the existing
// units. This builds the undo history for a transaction that
// is being rolled back.
//
if (reason != PopUnitReason.Normal || !_host.InTransaction) {
Trace("Popping unit {0}. Reason: {1}", _unitStack.Peek(), reason);
UndoUnit unit = (UndoUnit)_unitStack.Pop();
if (!unit.IsEmpty) {
unit.Close();
if (reason == PopUnitReason.TransactionCancel) {
unit.Undo();
if (_unitStack.Count == 0) {
DiscardUndoUnit(unit);
}
}
else {
if (_unitStack.Count == 0) {
AddUndoUnit(unit);
}
}
}
else {
if (_unitStack.Count == 0) {
DiscardUndoUnit(unit);
}
}
}
}
///
///
/// This virtual method creates a new instance of an
/// UndoUnit class. The default implementation just returns
/// a new instance of UndoUnit. Those providing their
/// own UndoEngine can derive from UndoUnit to customize
/// the actions it performs. This is also a handy way
/// to connect UndoEngine into an existing undo stack.
///
/// If the primary parameter is set to true, the undo unit will
/// eventually be passed to either the AddUndoUnit or DiscardUndoUnit
/// methods. If the primary parameter is false, the undo unit is
/// part of a nested transaction and will never be passed to
/// AddUndoUnit or DiscardUndoUnit; only the encompasing unit
/// will be passed, because the undo engine will either include
/// or exclude the contents of the nested unit when it is closed.
///
protected virtual UndoUnit CreateUndoUnit(string name, bool primary) {
return new UndoUnit(this, name);
}
internal IComponentChangeService ComponentChangeService {
get {
return this._componentChangeService;
}
}
///
///
/// This method is called instead of AddUndoUnit for undo units that
/// have been canceled. For undo systems that just treat undo as a
/// simple stack of undo units, typically you do not need to override
/// this method. This method does give you a chance to perform any
/// clean-up for a unit
///
protected virtual void DiscardUndoUnit(UndoUnit unit) {
}
///
///
/// Public dispose method.
///
public void Dispose() {
Dispose(true);
}
///
///
/// Protected dispose implementation.
///
protected virtual void Dispose(bool disposing) {
if (disposing) {
Trace("Disposing undo engine");
if (_host != null) {
_host.TransactionOpening -= new EventHandler(this.OnTransactionOpening);
_host.TransactionClosed -= new DesignerTransactionCloseEventHandler(this.OnTransactionClosed);
}
if (_componentChangeService != null) {
_componentChangeService.ComponentAdding -= new ComponentEventHandler(this.OnComponentAdding);
_componentChangeService.ComponentChanging -= new ComponentChangingEventHandler(this.OnComponentChanging);
_componentChangeService.ComponentRemoving -= new ComponentEventHandler(this.OnComponentRemoving);
_componentChangeService.ComponentAdded -= new ComponentEventHandler(this.OnComponentAdded);
_componentChangeService.ComponentChanged -= new ComponentChangedEventHandler(this.OnComponentChanged);
_componentChangeService.ComponentRemoved -= new ComponentEventHandler(this.OnComponentRemoved);
_componentChangeService.ComponentRename -= new ComponentRenameEventHandler(this.OnComponentRename);
}
_provider = null;
}
}
///
/// Helper function to retrieve the name of an object.
///
internal string GetName(object obj, bool generateNew) {
string componentName = null;
if (obj != null) {
IReferenceService rs = GetService(typeof(IReferenceService)) as IReferenceService;
if (rs != null) {
componentName = rs.GetName(obj);
}
else {
IComponent comp = obj as IComponent;
if (comp != null) {
ISite site = comp.Site;
if (site != null) {
componentName = site.Name;
}
}
}
}
if (componentName == null && generateNew) {
if (obj == null) {
componentName = "(null)";
}
else {
componentName = obj.GetType().Name;
}
}
return componentName;
}
///
///
/// Similar to GetService, but this will throw a NotSupportedException if the service
/// is not present.
///
protected object GetRequiredService(Type serviceType) {
object service = GetService(serviceType);
if (service == null) {
Exception ex = new InvalidOperationException(SR.GetString(SR.UndoEngineMissingService, serviceType.Name));
ex.HelpLink = SR.UndoEngineMissingService;
throw ex;
}
return service;
}
///
///
/// This just calls through to the service provider passed into
/// the constructor.
///
protected object GetService(Type serviceType) {
if (serviceType == null) {
throw new ArgumentNullException("serviceType");
}
if (_provider != null) {
return _provider.GetService(serviceType);
}
return null;
}
///
/// Handles the component added event.
///
private void OnComponentAdded(object sender, ComponentEventArgs e) {
foreach(UndoUnit unit in _unitStack) {
unit.ComponentAdded(e);
}
if (CurrentUnit != null) {
CheckPopUnit(PopUnitReason.Normal);
}
}
///
/// Handles the component adding event.
///
private void OnComponentAdding(object sender, ComponentEventArgs e) {
// Open a new unit unless there is already one open or we are
// currently executing a unit. If we need to create a unit, we
// will have to fabricate a good name.
//
if (_enabled && _executingUnit == null && _unitStack.Count == 0) {
string name;
if (e.Component != null) {
name = SR.GetString(SR.UndoEngineComponentAdd1,
GetName(e.Component, true));
}
else {
name = SR.GetString(SR.UndoEngineComponentAdd0);
}
_unitStack.Push(CreateUndoUnit(name, true));
}
// Now walk all the units and notify them. We don't
// care which order the units are notified.
//
foreach (UndoUnit unit in _unitStack) {
unit.ComponentAdding(e);
}
}
///
/// Handles the component changed event.
///
private void OnComponentChanged(object sender, ComponentChangedEventArgs e) {
foreach(UndoUnit unit in _unitStack) {
unit.ComponentChanged(e);
}
if (CurrentUnit != null) {
CheckPopUnit(PopUnitReason.Normal);
}
}
///
/// Handles the component changing event.
///
private void OnComponentChanging(object sender, ComponentChangingEventArgs e) {
// Open a new unit unless there is already one open or we are
// currently executing a unit. If we need to create a unit, we
// will have to fabricate a good name.
//
if (_enabled && _executingUnit == null && _unitStack.Count == 0) {
string name;
if (e.Member != null && e.Component != null) {
name = SR.GetString(SR.UndoEngineComponentChange2,
GetName(e.Component, true), e.Member.Name);
}
else if (e.Component != null) {
name = SR.GetString(SR.UndoEngineComponentChange1,
GetName(e.Component, true));
}
else {
name = SR.GetString(SR.UndoEngineComponentChange0);
}
_unitStack.Push(CreateUndoUnit(name, true));
}
// Now walk all the units and notify them. We don't
// care which order the units are notified.
//
foreach(UndoUnit unit in _unitStack) {
unit.ComponentChanging(e);
}
}
///
/// Handles the component removed event.
///
private void OnComponentRemoved(object sender, ComponentEventArgs e) {
foreach(UndoUnit unit in _unitStack) {
unit.ComponentRemoved(e);
}
if (CurrentUnit != null) {
CheckPopUnit(PopUnitReason.Normal);
}
// Now we need to raise ComponentChanged events for
// every component that had a reference to this removed component
List propsToUpdate = null;
if (_refToRemovedComponent != null
&& _refToRemovedComponent.TryGetValue(e.Component, out propsToUpdate)
&& propsToUpdate != null
&& _componentChangeService != null) {
foreach (ReferencingComponent ro in propsToUpdate) {
_componentChangeService.OnComponentChanged(ro.component, ro.member, null, null);
}
_refToRemovedComponent.Remove(e.Component);
}
}
///
/// Handles the component removing event.
///
private void OnComponentRemoving(object sender, ComponentEventArgs e) {
// Open a new unit unless there is already one open or we are
// currently executing a unit. If we need to create a unit, we
// will have to fabricate a good name.
//
if (_enabled && _executingUnit == null && _unitStack.Count == 0) {
string name;
if (e.Component != null) {
name = SR.GetString(SR.UndoEngineComponentRemove1,
GetName(e.Component, true));
}
else {
name = SR.GetString(SR.UndoEngineComponentRemove0);
}
_unitStack.Push(CreateUndoUnit(name, true));
}
// VSWhidbey #312230
// We need to keep track of all references in the container to the deleted component so
// that those references can be fixed up if an undo of this "remove" occurs.
//
if (_enabled && _host != null && _host.Container != null && _componentChangeService != null) {
List propsToUpdate = null;
foreach (IComponent comp in _host.Container.Components) {
if (comp == e.Component) {
continue;
}
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(comp);
foreach (PropertyDescriptor prop in props) {
if (prop.PropertyType.IsAssignableFrom(e.Component.GetType()) &&
!prop.Attributes.Contains(DesignerSerializationVisibilityAttribute.Hidden) &&
!prop.IsReadOnly) {
object obj = null;
try {
obj = prop.GetValue(comp);
}
catch (TargetInvocationException) {
continue;
}
if (obj != null && object.ReferenceEquals(obj, e.Component)) {
// found one!
if (propsToUpdate == null) {
propsToUpdate = new List();
if (_refToRemovedComponent == null) {
_refToRemovedComponent = new Dictionary>();
}
_refToRemovedComponent[e.Component] = propsToUpdate;
}
_componentChangeService.OnComponentChanging(comp, prop);
propsToUpdate.Add(new ReferencingComponent(comp, prop));
}
}
}
}
}
// Now walk all the units and notify them. We don't
// care which order the units are notified. By notifying
// all transactions we automatically support the cancelling
// of nested transactions.
//
foreach(UndoUnit unit in _unitStack) {
unit.ComponentRemoving(e);
}
}
///
/// Handles the component rename event.
///
private void OnComponentRename(object sender, ComponentRenameEventArgs e) {
// Open a new unit unless there is already one open or we are
// currently executing a unit. If we need to create a unit, we
// will have to fabricate a good name.
//
if (_enabled && _executingUnit == null && _unitStack.Count == 0) {
string name = SR.GetString(SR.UndoEngineComponentRename, e.OldName,
e.NewName);
_unitStack.Push(CreateUndoUnit(name, true));
}
// Now walk all the units and notify them. We don't
// care which order the units are notified. By notifying
// all transactions we automatically support the cancelling
// of nested transactions.
//
foreach(UndoUnit unit in _unitStack) {
unit.ComponentRename(e);
}
}
///
/// Handles the transaction closed event.
///
private void OnTransactionClosed(object sender, DesignerTransactionCloseEventArgs e) {
if (_executingUnit == null && CurrentUnit != null) {
PopUnitReason reason = e.TransactionCommitted ? PopUnitReason.TransactionCommit : PopUnitReason.TransactionCancel;
CheckPopUnit(reason);
}
}
///
/// Handles the transaction opening event.
///
private void OnTransactionOpening(object sender, EventArgs e) {
// When a transaction is opened, we always push a new unit unless
// we're executing a unit. We can
// push multiple units onto the stack to handle nested transactions.
//
if (_enabled && _executingUnit == null) {
_unitStack.Push(CreateUndoUnit(_host.TransactionDescription, _unitStack.Count == 0));
}
}
///
///
/// This event is raised immediately before an undo action is performed.
///
protected virtual void OnUndoing(EventArgs e) {
if (_undoingEvent != null) {
_undoingEvent(this, e);
}
}
///
///
/// This event is raised immediately after an undo action is performed. It
/// will always be raised even if an exception is thrown.
///
protected virtual void OnUndone(EventArgs e) {
if (_undoneEvent != null) {
_undoneEvent(this, e);
}
}
///
/// Debug tracing code.
///
[Conditional("DEBUG")]
private static void Trace(string text, params object[] values) {
Debug.WriteLineIf(traceUndo.TraceVerbose, "UndoEngine: " + string.Format(CultureInfo.CurrentCulture, text, values));
}
///
/// The reason that CheckPopUnit is being called.
///
private enum PopUnitReason {
Normal,
TransactionCommit,
TransactionCancel,
}
///
/// The component that needs to change as a result of another
/// component being deleted.
///
private struct ReferencingComponent {
public IComponent component;
public MemberDescriptor member;
public ReferencingComponent(IComponent component, MemberDescriptor member) {
this.component = component;
this.member = member;
}
}
///
///
/// This class embodies a unit of undoable work. The undo
/// engine creates an undo unit when a change to the designer
/// is about to be made. The undo unit is responsible for tracking
/// changes. The undo engine will call Close on the unit when
/// it no longer needs to track changes.
///
protected class UndoUnit {
private string _name; // the name of the undo unit
private UndoEngine _engine; // the undo engine we're tied to
private ArrayList _events; // the list of events we've captured
private ArrayList _changeEvents; // the list of change events we're currently capturing. Only valid until Commit is called.
private ArrayList _removeEvents; // the list of remove events we're currently capturing. Only valid until a matching Removed is encountered.
private ArrayList _ignoreAddingList; // the list of objects that are currently being added. We ignore change events between adding and added.
private ArrayList _ignoreAddedList; // the list of objects that are added. We do not serialize before state for change events that happen in the same transaction
private bool _reverse; // if true, we walk the events list from the bottom up
private Hashtable _lastSelection; // the selection as it was before we gathered undo info
///
///
/// Creates a new UndoUnit.
///
public UndoUnit(UndoEngine engine, string name) {
if (engine == null) {
throw new ArgumentNullException("engine");
}
if (name == null) {
Debug.Fail("Null name passed to new undo unit");
name = string.Empty;
}
UndoEngine.Trace("Creating undo unit '{0}'", name);
_name = name;
_engine = engine;
_reverse = true;
ISelectionService ss = _engine.GetService(typeof(ISelectionService)) as ISelectionService;
if (ss != null) {
ICollection selection = ss.GetSelectedComponents();
Hashtable selectedNames = new Hashtable();
foreach(object sel in selection) {
IComponent comp = sel as IComponent;
if (comp != null && comp.Site != null) {
selectedNames[comp.Site.Name] = comp.Site.Container;
}
}
_lastSelection = selectedNames;
}
}
///
///
/// The name of the unit.
///
public string Name {
get {
return _name;
}
}
///
///
/// This returns true if the undo unit has nothing
/// in it to undo. The unit will be discarded.
///
public virtual bool IsEmpty {
get {
return _events == null || _events.Count == 0;
}
}
///
///
/// The undo engine that was passed into the
/// constructor.
///
protected UndoEngine UndoEngine {
get {
return _engine;
}
}
///
/// Adds the given event to our event list.
///
private void AddEvent(UndoEvent e) {
if (_events == null) {
_events = new ArrayList();
}
_events.Add(e);
}
///
///
/// Called by the undo engine when it wants to
/// close this unit. The unit should do any final work
/// it needs to do to close.
///
public virtual void Close() {
if (_changeEvents != null) {
foreach (ChangeUndoEvent e in _changeEvents) {
e.Commit(_engine);
}
}
if (_removeEvents != null) {
foreach(AddRemoveUndoEvent e in _removeEvents) {
e.Commit(_engine);
}
}
// At close time we are done with this list. All change
// events were simultaneously added to the _events list.
_changeEvents = null;
_removeEvents = null;
_ignoreAddingList = null;
_ignoreAddedList = null;
}
///
///
/// The undo engine will call this on the active undo
/// unit in response to a component added event.
///
public virtual void ComponentAdded(ComponentEventArgs e) {
if (e.Component.Site != null &&
e.Component.Site.Container is INestedContainer)
{
// do nothing
}
else
AddEvent(new AddRemoveUndoEvent(_engine, e.Component, true));
if (_ignoreAddingList != null) {
_ignoreAddingList.Remove(e.Component);
}
if (_ignoreAddedList == null)
{
_ignoreAddedList = new ArrayList();
}
_ignoreAddedList.Add(e.Component);
}
///
///
/// The undo engine will call this on the active undo
/// unit in response to a component adding event.
///
public virtual void ComponentAdding(ComponentEventArgs e) {
if (_ignoreAddingList == null) {
_ignoreAddingList = new ArrayList();
}
_ignoreAddingList.Add(e.Component);
}
private static bool ChangeEventsSymmetric(ComponentChangingEventArgs changing, ComponentChangedEventArgs changed) {
if (changing == null || changed == null) {
return false;
}
return changing.Component == changed.Component && changing.Member == changed.Member;
}
private bool CanRepositionEvent(int startIndex, ComponentChangedEventArgs e) {
bool containsAdd = false;
bool containsRename = false;
bool containsSymmetricChange = false;
for (int i = startIndex + 1; i < _events.Count; i++) {
AddRemoveUndoEvent addEvt = _events[i] as AddRemoveUndoEvent;
RenameUndoEvent renameEvt = _events[i] as RenameUndoEvent;
ChangeUndoEvent changeEvt = _events[i] as ChangeUndoEvent;
if (addEvt != null && !addEvt.NextUndoAdds) {
containsAdd = true;
}
else if (changeEvt != null && ChangeEventsSymmetric(changeEvt.ComponentChangingEventArgs, e)) {
containsSymmetricChange = true;
}
else if (renameEvt != null) {
containsRename = true;
}
}
return containsAdd && !containsRename && !containsSymmetricChange;
}
///
///
/// The undo engine will call this on the active undo
/// unit in response to a component changed event.
///
public virtual void ComponentChanged(ComponentChangedEventArgs e) {
if (_events != null && e != null) {
for (int i = 0; i < _events.Count; i++) {
ChangeUndoEvent ce = _events[i] as ChangeUndoEvent;
// Determine if we've located the UndoEvent which was
// created as a result of a corresponding ComponentChanging event.
// If so, reposition to the "Changed" spot in the list if the following is true:
// - It must be for a DSV.Content property
// - There must be a AddEvent between the Changing and Changed
// - There are no renames in between Changing and Changed.
//
// See VSWhidbey 483192 for more info
if (ce != null && ChangeEventsSymmetric(ce.ComponentChangingEventArgs, e) && i != _events.Count - 1) {
if (e.Member != null && e.Member.Attributes.Contains(DesignerSerializationVisibilityAttribute.Content) &&
CanRepositionEvent(i, e)) {
_events.RemoveAt(i);
_events.Add(ce);
}
}
}
}
}
///
///
/// The undo engine will call this on the active undo
/// unit in response to a component changing event.
///
public virtual void ComponentChanging(ComponentChangingEventArgs e) {
// If we are in the process of adding this component, ignore any
// changes to it. The ending "Added" event will capture the
// component's state. This not just an optimization. If we get
// a change during an add, we can have an undo order that specifies
// a remove, and then a change to a removed component.
if (_ignoreAddingList != null && _ignoreAddingList.Contains(e.Component)) {
return;
}
if (_changeEvents == null) {
_changeEvents = new ArrayList();
}
// The site check here is done because the data team is calling
// us for components that are not yet sited. We end up
// writing them out as Guid-named locals. That's fine, except
// that we cannot capture after state for these types of things
// so we assert.
//
if (_engine != null && _engine.GetName(e.Component, false) != null) {
IComponent comp = e.Component as IComponent;
#if false
if (!(comp != null && comp.Site != null)) {
string name = _engine.GetName(e.Component, false);
Debug.Fail("adding event for an unsited component:" + name);
}
#endif
// The caller provided us with a component. This is the common
// case. We will add a new change event provided there is not
// already one open for this component.
//
bool hasChange = false;
for(int idx = 0; idx < _changeEvents.Count; idx++) {
ChangeUndoEvent ce = (ChangeUndoEvent)_changeEvents[idx];
if (ce.OpenComponent == e.Component && ce.ContainsChange(e.Member)) {
hasChange = true;
break;
}
}
if (!hasChange ||
(e.Member != null && e.Member.Attributes != null && e.Member.Attributes.Contains(DesignerSerializationVisibilityAttribute.Content))) {
#if DEBUG
string name = _engine.GetName(e.Component, false);
string memberName = "(none)";
if (e.Member != null && e.Member.Name != null) {
memberName = e.Member.Name;
}
if (name != null) {
Debug.WriteLineIf(traceUndo.TraceVerbose && hasChange, "Adding second ChangeEvent for " + name + " Member: " + memberName);
}
else {
Debug.Fail("UndoEngine: GetName is failing on successive calls");
}
#endif
ChangeUndoEvent changeEvent = null;
bool serializeBeforeState = true;
//perf: if this object was added in this undo unit we do not want to serialize before state for ChangeEvent since undo will remove it anyway
if (_ignoreAddedList != null && _ignoreAddedList.Contains(e.Component))
{
serializeBeforeState = false;
}
if (comp != null && comp.Site != null) {
changeEvent = new ChangeUndoEvent(_engine, e, serializeBeforeState);
}
else if (e.Component != null) {
IReferenceService rs = GetService(typeof(IReferenceService)) as IReferenceService;
if (rs != null) {
IComponent owningComp = rs.GetComponent(e.Component);
if (owningComp != null) {
changeEvent = new ChangeUndoEvent(_engine, new ComponentChangingEventArgs(owningComp, null), serializeBeforeState);
}
}
}
if (changeEvent != null) {
AddEvent(changeEvent);
_changeEvents.Add(changeEvent);
}
}
}
}
///
///
/// The undo engine will call this on the active undo
/// unit in response to a component removed event.
///
public virtual void ComponentRemoved(ComponentEventArgs e) {
// We should gather undo state in ComponentRemoved, but by
// this time the component's designer has been destroyed so
// it's too late. Instead, we captured state in the Removing
// method. But, it is possible for there to be component
// changes to other objects that happen between removing and removed,
// so we need to reorder the removing event so it's positioned after
// any changes.
//
if (_events != null) {
ChangeUndoEvent changeEvt = null;
int changeIdx = -1;
for (int idx = _events.Count - 1; idx >= 0; idx--) {
AddRemoveUndoEvent evt = _events[idx] as AddRemoveUndoEvent;
if (changeEvt == null) {
changeEvt = _events[idx] as ChangeUndoEvent;
changeIdx = idx;
}
if (evt != null && evt.OpenComponent == e.Component) {
evt.Commit(_engine);
// VSWhidbey 400094 - We should only reorder events if there
// are change events coming between OnRemoving and OnRemoved.
// If there are other events (such as AddRemoving), the serialization
// done in OnComponentRemoving might refer to components that aren't available.
if (idx != _events.Count - 1 && changeEvt != null) {
// ensure only change change events exist between these two events
bool onlyChange = true;
for (int i = idx + 1; i < changeIdx; i++) {
if (!(_events[i] is ChangeUndoEvent)) {
onlyChange = false;
break;
}
}
if (onlyChange) {
// reposition event after final ComponentChangingEvent
_events.RemoveAt(idx);
_events.Insert(changeIdx, evt);
}
}
break;
}
}
}
}
///
///
/// The undo engine will call this on the active undo
/// unit in response to a component removing event.
///
public virtual void ComponentRemoving(ComponentEventArgs e) {
if (e.Component.Site != null &&
e.Component.Site is INestedContainer) {
return;
}
if (_removeEvents == null) {
_removeEvents = new ArrayList();
}
try {
AddRemoveUndoEvent evt = new AddRemoveUndoEvent(_engine, e.Component, false);
AddEvent(evt);
_removeEvents.Add(evt);
}
catch (TargetInvocationException) { }
}
///
///
/// The undo engine will cal this on the active undo
/// unit in response to a component rename event.
///
public virtual void ComponentRename(ComponentRenameEventArgs e) {
AddEvent(new RenameUndoEvent(e.OldName, e.NewName));
}
///
///
/// Returns an instance of the rquested service.
///
protected object GetService(Type serviceType) {
return _engine.GetService(serviceType);
}
///
///
/// Override for object.ToString()
///
public override string ToString() {
return Name;
}
///
///
/// Either performs undo, or redo, depending on the
/// state of the unit. UndoUnit initially assumes that
/// the undoable work has already been "done", so the first
/// call to undo will undo the work. The next call will
/// undo the "undo", performing a redo.
///
public void Undo() {
UndoEngine.Trace("Performing undo '{0}'", Name);
UndoUnit savedUnit = _engine._executingUnit;
_engine._executingUnit = this;
DesignerTransaction transaction = null;
try {
if (savedUnit == null) {
_engine.OnUndoing(EventArgs.Empty);
}
// create a transaction here so things that do work
// on componentchanged can ignore that while the transaction
// is opened...big perf win.
//
transaction = _engine._host.CreateTransaction();
UndoCore();
} catch(CheckoutException) {
//if(ex == CheckoutException.Canceled) {
transaction.Cancel();
transaction = null;
throw;
//}
}
finally {
if (transaction != null) {
transaction.Commit();
}
_engine._executingUnit = savedUnit;
if (savedUnit == null) {
_engine.OnUndone(EventArgs.Empty);
}
}
}
///
///
/// The undo method invokes this method to perform the actual
/// undo / redo work. You should never call this method
/// directly; override it if you wish, but always call the
/// public Undo method to perform undo work. Undo notifies
/// the undo engine to suspend undo data gathering until
/// this undo is completed, which prevents new undo units
/// from being created in response to this unit doing work.
///
protected virtual void UndoCore() {
if (_events != null) {
if (_reverse) {
// How does BeforeUndo work? You'd think you should just call
// this in one pass, and then call Undo in another, but you'd be wrong.
// The complexity arises because there are undo events that have
// dependencies on other undo events. There are also undo events
// that have side effects with respect to other events. Here are examples:
//
// Rename is an undo event that other undo events depend on, because they
// store names. It must be performed in the right order and it must be
// performed before any subsequent event's BeforeUndo is called.
//
// Property change is an undo event that may have an unknown side effect
// if changing the property results in other property changes (for example,
// reparenting a control removes the control from its former parent). A
// property change undo event must have all BeforeUndo methods called
// before any Undo method is called.
//
// To do this, we have a property on UndoEvent called CausesSideEffects.
// As we run through UndoEvents, consecutive events that return true
// from this property are grouped so that their BeforeUndo methods are
// all called before their Undo methods. For events that do not have
// side effects, their BeforeUndo and Undo are invoked immediately.
for (int idx = _events.Count - 1; idx >= 0; idx--) {
int groupEndIdx = idx;
for (int groupIdx = idx; groupIdx >= 0; groupIdx--) {
if (((UndoEvent)_events[groupIdx]).CausesSideEffects) {
groupEndIdx = groupIdx;
}
else {
break;
}
}
for (int beforeIdx = idx; beforeIdx >= groupEndIdx; beforeIdx--) {
((UndoEvent)_events[beforeIdx]).BeforeUndo(_engine);
}
for (int undoIdx = idx; undoIdx >= groupEndIdx; undoIdx--) {
((UndoEvent)_events[undoIdx]).Undo(_engine);
}
Debug.Assert(idx >= groupEndIdx, "We're going backwards");
idx = groupEndIdx;
}
// Now, if we have a selection, apply it.
//
if (_lastSelection != null) {
ISelectionService ss = _engine.GetService(typeof(ISelectionService)) as ISelectionService;
if (ss != null) {
string[] names = new string[_lastSelection.Keys.Count];
_lastSelection.Keys.CopyTo(names, 0);
ArrayList list = new ArrayList(names.Length);
foreach (string name in names)
{
if (name != null) {
object comp = ((Container)_lastSelection[name]).Components[name];
if (comp != null) {
list.Add(comp);
}
}
}
ss.SetSelectedComponents(list, SelectionTypes.Replace);
}
}
}
else {
int count = _events.Count;
for (int idx = 0; idx < count; idx++) {
int groupEndIdx = idx;
for (int groupIdx = idx; groupIdx < count; groupIdx++) {
if (((UndoEvent)_events[groupIdx]).CausesSideEffects) {
groupEndIdx = groupIdx;
}
else {
break;
}
}
for (int beforeIdx = idx; beforeIdx <= groupEndIdx; beforeIdx++) {
((UndoEvent)_events[beforeIdx]).BeforeUndo(_engine);
}
for (int undoIdx = idx; undoIdx <= groupEndIdx; undoIdx++) {
((UndoEvent)_events[undoIdx]).Undo(_engine);
}
Debug.Assert(idx <= groupEndIdx, "We're going backwards");
idx = groupEndIdx;
}
}
}
_reverse = !_reverse;
}
///
/// This undo event handles addition and removal of
/// components.
///
private sealed class AddRemoveUndoEvent : UndoEvent {
private SerializationStore _serializedData;
private string _componentName;
private bool _nextUndoAdds;
private bool _committed;
private IComponent _openComponent;
///
/// Creates a new object that contains the state of
/// the event. The last parameter, add, determines
/// the initial mode of this event. If true, it means
/// this event is being created in response to a component
/// add. If false, it is being created in response to
/// a component remove.
///
public AddRemoveUndoEvent(UndoEngine engine, IComponent component, bool add) {
_componentName = component.Site.Name;
_nextUndoAdds = !add;
_openComponent = component;
UndoEngine.Trace("---> Creating {0} undo event for '{1}'", ( add ? "Add" : "Remove"), _componentName);
using (_serializedData = engine._serializationService.CreateStore()) {
engine._serializationService.Serialize(_serializedData, component);
}
// For add events, we commit as soon as we receive the event.
_committed = add;
}
///
/// Returns true if the add remove event has been comitted.
///
internal bool Committed {
get {
return _committed;
}
}
///
/// If this add/remove event is still open, OpenCompnent will contain the
/// component it is operating on.
///
internal IComponent OpenComponent {
get {
return _openComponent;
}
}
///
/// Returns true if undoing this event will add a component.
///
internal bool NextUndoAdds {
get {
return _nextUndoAdds;
}
}
///
/// Commits this event.
///
internal void Commit(UndoEngine engine) {
if (!Committed) {
UndoEngine.Trace("---> Committing remove of '{0}'", _componentName);
_committed = true;
}
}
///
/// Actually performs the undo action.
///
public override void Undo(UndoEngine engine) {
if (_nextUndoAdds) {
UndoEngine.Trace("---> Adding '{0}'", _componentName);
// We need to add this component. To add it, we deserialize it and then
// we add it to the designer host's container.
//
IDesignerHost host = engine.GetRequiredService(typeof(IDesignerHost)) as IDesignerHost;
if (host != null) {
engine._serializationService.DeserializeTo(_serializedData, host.Container);
}
}
else {
UndoEngine.Trace("---> Removing '{0}'", _componentName);
// We need to remove this component. Take the name and
// match it to an object, and then ask that object to delete itself.
//
IDesignerHost host = engine.GetRequiredService(typeof(IDesignerHost)) as IDesignerHost;
IComponent component = host.Container.Components[_componentName];
//Note: It's ok for the component to be null here. This could happen
//if the parent to this control is disposed first. Ex:SplitContainer
if (component != null) {
host.DestroyComponent(component);
}
}
_nextUndoAdds = !_nextUndoAdds;
}
}
///
/// The base class for all change events. We actually support
/// three different kinds of change events.
///
private sealed class ChangeUndoEvent : UndoEvent {
// This is only valid while the change is still open. The
// change is committed.
private object _openComponent;
// Static data we hang onto about this change.
private string _componentName;
private MemberDescriptor _member;
// Before and after state. Before state is built in the
// constructor. After state is built right before
// we undo for the first time.
private SerializationStore _before;
private SerializationStore _after;
private bool _savedAfterState;
///
/// Creates a new component change undo event. This event consists of a before and after snapshot
/// of a single component. A snapshot will not be taken if a name for the component cannot be
/// determined.
///
public ChangeUndoEvent(UndoEngine engine, ComponentChangingEventArgs e, bool serializeBeforeState) {
_componentName = engine.GetName(e.Component, true);
_openComponent = e.Component;
_member = e.Member;
UndoEngine.Trace("---> Creating change undo event for '{0}'", _componentName);
UndoEngine.Trace("---> Saving before snapshot for change to '{0}'", _componentName);
if (serializeBeforeState)
{
_before = Serialize(engine, _openComponent, _member);
}
}
public ComponentChangingEventArgs ComponentChangingEventArgs {
get {
return new ComponentChangingEventArgs(_openComponent, _member);
}
}
///
/// Indicates that undoing this event may cause side effects in other objects.
/// Chagne events fall into this category because, for example,
/// a change involving adding an object to one collection may have
/// a side effect of removing it from another collection. Events
/// with side effects are grouped at undo time so all their
/// BeforeUndo methods are called before their Undo methods.
/// Events without side effects have their BeforeUndo called
/// and then their Undo called immediately after.
///
public override bool CausesSideEffects { get { return true; } }
///
/// Returns true if the change event has been comitted.
///
public bool Committed {
get {
return _openComponent == null;
}
}
///
/// Returns the component this change event is currently
/// tracking. This will return null once the change event
/// is committed.
///
public object OpenComponent {
get {
return _openComponent;
}
}
///
/// Called before Undo is called. All undo events
/// get their BeforeUndo called, and then they all
/// get their Undo called. This allows the undo
/// event to examine the state of the world before
/// other undo events mess with it.
///
public override void BeforeUndo(UndoEngine engine) {
if (!_savedAfterState) {
_savedAfterState = true;
SaveAfterState(engine);
}
}
///
/// Determines if this
///
public bool ContainsChange(MemberDescriptor desc) {
if (_member == null) {
return true;
}
if (desc == null) {
return false;
}
return desc.Equals(_member);
}
///
/// Commits the unit. Comitting the unit saves the "after"
/// snapshot of the unit. If commit is called multiple times
/// only the first commit is registered.
///
public void Commit(UndoEngine engine) {
if (!Committed) {
UndoEngine.Trace("---> Committing change to '{0}'", _componentName);
_openComponent = null;
}
}
///
/// Saves the after state of this undo unit. This is deferred
/// until absolutely necessary.
///
private void SaveAfterState(UndoEngine engine) {
Debug.Assert(_after == null, "Change undo saving state twice.");
UndoEngine.Trace("---> Saving after snapshot for change to '{0}'", _componentName);
object component = null;
IReferenceService rs = engine.GetService(typeof(IReferenceService)) as IReferenceService;
if (rs != null) {
component = rs.GetReference(_componentName);
}
else {
IDesignerHost host = engine.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host != null) {
component = host.Container.Components[_componentName];
}
}
// It is OK for us to not find a component here. That can happen if our "after" state
// is owned by another change, like an add of the component.
if (component != null) {
_after = Serialize(engine, component, _member);
}
}
///
/// Helper function that serializes the given component into a byte array.
///
private SerializationStore Serialize(UndoEngine engine, object component, MemberDescriptor member) {
SerializationStore store;
using (store = engine._serializationService.CreateStore()) {
if (member != null && !(member.Attributes.Contains(DesignerSerializationVisibilityAttribute.Hidden))) {
engine._serializationService.SerializeMemberAbsolute(store, component, member);
}
else {
engine._serializationService.SerializeAbsolute(store, component);
}
}
return store;
}
///
/// Performs the actual undo. AFter it finishes
/// it will reverse the role of _before and _after
///
public override void Undo(UndoEngine engine) {
UndoEngine.Trace("---> Applying changes to '{0}'", _componentName);
Debug.Assert(_savedAfterState, "After state not saved. BeforeUndo was not called?");
if (_before != null) {
IDesignerHost host = engine.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host != null) {
engine._serializationService.DeserializeTo(_before, host.Container);
}
}
SerializationStore temp = _after;
_after = _before;
_before = temp;
}
}
///
/// This class handles the undo / redo for a rename
/// event.
///
private sealed class RenameUndoEvent : UndoEvent {
private string _before;
private string _after;
///
/// Creates a new rename undo event.
///
public RenameUndoEvent(string before, string after) {
_before = before;
_after = after;
UndoEngine.Trace("---> Creating rename undo event for '{0}'->'{1}'", _before, _after);
}
///
/// Simply undoes a rename by setting the name
/// back to the saved value.
///
public override void Undo(UndoEngine engine) {
UndoEngine.Trace("---> Renaming '{0}'->'{1}'", _after, _before);
IComponent comp = engine._host.Container.Components[_after];
if (comp != null) {
engine.ComponentChangeService.OnComponentChanging(comp, null);
comp.Site.Name = _before;
string temp = _after;
_after = _before;
_before = temp;
}
}
}
///
/// This abstract class is the base of each of our
/// undo events. There are different concrete implementations
/// of an undo event for different types of events.
/// For example, a property change event records the
/// difference between two property changes, while a
/// component add event creates and destroys components.
///
private abstract class UndoEvent {
///
/// Indicates that undoing this event may cause side effects in other objects.
/// Chagne events fall into this category because, for example,
/// a change involving adding an object to one collection may have
/// a side effect of removing it from another collection. Events
/// with side effects are grouped at undo time so all their
/// BeforeUndo methods are called before their Undo methods.
/// Events without side effects have their BeforeUndo called
/// and then their Undo called immediately after.
///
public virtual bool CausesSideEffects { get { return false; } }
///
/// Called before Undo is called. All undo events
/// get their BeforeUndo called, and then they all
/// get their Undo called. This allows the undo
/// event to examine the state of the world before
/// other undo events mess with it.
///
/// BeforeUndo returns true if before undo was
/// supported, and false if not. If before undo is
/// not supported, the undo unit should be undone
/// immediately.
///
public virtual void BeforeUndo(UndoEngine engine) {
}
///
/// Called by the undo unit when it wants to
/// undo this bit of work.
///
public abstract void Undo(UndoEngine engine);
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- NativeMethods.cs
- EncryptedPackage.cs
- TransformerInfoCollection.cs
- TreeIterators.cs
- WeakEventManager.cs
- SafeNativeMemoryHandle.cs
- oledbmetadatacolumnnames.cs
- GridViewAutoFormat.cs
- TypedColumnHandler.cs
- FilteredDataSetHelper.cs
- ExeConfigurationFileMap.cs
- PropertyEmitter.cs
- FixedSOMElement.cs
- GenericEnumConverter.cs
- SqlProfileProvider.cs
- KeyBinding.cs
- LocationReferenceValue.cs
- SamlEvidence.cs
- CryptoStream.cs
- WmpBitmapEncoder.cs
- UnsafeNativeMethods.cs
- IndexOutOfRangeException.cs
- IProvider.cs
- BamlReader.cs
- PrincipalPermission.cs
- ProxyWebPartConnectionCollection.cs
- MetadataException.cs
- DebugInfo.cs
- SearchForVirtualItemEventArgs.cs
- ObjectListGeneralPage.cs
- MouseEventArgs.cs
- ServiceHostingEnvironment.cs
- DataList.cs
- ThreadExceptionDialog.cs
- IntegerValidator.cs
- NetTcpSecurityElement.cs
- Pair.cs
- BamlResourceSerializer.cs
- ComUdtElement.cs
- FormViewDeletedEventArgs.cs
- CodeGotoStatement.cs
- ComponentRenameEvent.cs
- WindowPattern.cs
- EntityDataSourceContextDisposingEventArgs.cs
- GridViewHeaderRowPresenter.cs
- WebConfigurationManager.cs
- OutputCacheSettingsSection.cs
- Journaling.cs
- TreeViewItem.cs
- ListBoxItemAutomationPeer.cs
- XmlSchemaAttribute.cs
- ListViewGroup.cs
- TemplateXamlTreeBuilder.cs
- BinaryObjectInfo.cs
- xdrvalidator.cs
- CacheHelper.cs
- COMException.cs
- BeginStoryboard.cs
- ProvidePropertyAttribute.cs
- WindowPatternIdentifiers.cs
- CodeIndexerExpression.cs
- ClrProviderManifest.cs
- TypeBuilderInstantiation.cs
- DataGridViewLinkColumn.cs
- ErrorLog.cs
- TransformCollection.cs
- LineVisual.cs
- TraceFilter.cs
- SimpleHandlerFactory.cs
- HandlerMappingMemo.cs
- MetaData.cs
- cookie.cs
- WorkflowInstanceContextProvider.cs
- BufferedGraphicsManager.cs
- SQLBinaryStorage.cs
- Encoding.cs
- IntegerValidator.cs
- XmlEntity.cs
- CodeChecksumPragma.cs
- UndoManager.cs
- SafeRegistryKey.cs
- PropertyItemInternal.cs
- PageAdapter.cs
- SoapElementAttribute.cs
- DisplayMemberTemplateSelector.cs
- CheckBoxBaseAdapter.cs
- PtsHelper.cs
- SoapInteropTypes.cs
- WindowsPen.cs
- CodeGenerator.cs
- CallSiteOps.cs
- SharedPersonalizationStateInfo.cs
- WebZone.cs
- ParseChildrenAsPropertiesAttribute.cs
- TextRangeSerialization.cs
- StickyNote.cs
- NullableIntAverageAggregationOperator.cs
- SafePEFileHandle.cs
- PriorityBinding.cs
- ChannelParameterCollection.cs