Code:
/ FX-1434 / FX-1434 / 1.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 ListpropsToUpdate = 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) { ListpropsToUpdate = 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
- peernodestatemanager.cs
- _LocalDataStore.cs
- FileDialog_Vista_Interop.cs
- OracleSqlParser.cs
- InputLanguage.cs
- odbcmetadatacolumnnames.cs
- Helper.cs
- ContextStack.cs
- TokenBasedSetEnumerator.cs
- BookmarkManager.cs
- filewebrequest.cs
- CodeStatement.cs
- BuildProviderUtils.cs
- FontDriver.cs
- DesignConnection.cs
- dataprotectionpermissionattribute.cs
- MediaContextNotificationWindow.cs
- XmlSchemaFacet.cs
- _LoggingObject.cs
- EntityDataSource.cs
- MgmtResManager.cs
- ThemeDirectoryCompiler.cs
- ArcSegment.cs
- CheckBoxField.cs
- WebAdminConfigurationHelper.cs
- DataGridState.cs
- PropertyAccessVisitor.cs
- Font.cs
- SingleTagSectionHandler.cs
- AnnotationService.cs
- SmtpException.cs
- TagMapInfo.cs
- EventLogReader.cs
- WindowsScrollBarBits.cs
- ServiceObjectContainer.cs
- OracleDataReader.cs
- WebExceptionStatus.cs
- CopyCodeAction.cs
- SQLDecimalStorage.cs
- RegexWorker.cs
- AggregatePushdown.cs
- BooleanFunctions.cs
- HttpCapabilitiesEvaluator.cs
- TargetControlTypeCache.cs
- FixUpCollection.cs
- AddingNewEventArgs.cs
- Baml6ConstructorInfo.cs
- CodeArrayIndexerExpression.cs
- DynamicArgumentDesigner.xaml.cs
- DataSourceComponent.cs
- Pkcs7Recipient.cs
- PlacementWorkspace.cs
- HttpResponseHeader.cs
- Propagator.ExtentPlaceholderCreator.cs
- MimeAnyImporter.cs
- UserNamePasswordClientCredential.cs
- PrtTicket_Public.cs
- ObfuscationAttribute.cs
- XmlTextReaderImpl.cs
- NativeMethods.cs
- InputScopeManager.cs
- XmlSerializationGeneratedCode.cs
- ZipIOLocalFileDataDescriptor.cs
- ClosureBinding.cs
- GridViewAutoFormat.cs
- TextRunCacheImp.cs
- ResourceDictionary.cs
- ListViewInsertEventArgs.cs
- Row.cs
- TextTreeInsertElementUndoUnit.cs
- Vector3DKeyFrameCollection.cs
- TreeNode.cs
- XmlException.cs
- ArraySegment.cs
- XslTransform.cs
- UInt32.cs
- Task.cs
- WebHostScriptMappingsInstallComponent.cs
- RuleAction.cs
- ProbeMatchesCD1.cs
- TreeWalkHelper.cs
- GridViewSortEventArgs.cs
- ExecutionContext.cs
- SupportedAddressingMode.cs
- httpstaticobjectscollection.cs
- MatchingStyle.cs
- WebPartHelpVerb.cs
- WindowsTooltip.cs
- EventDescriptor.cs
- XmlMtomWriter.cs
- FunctionOverloadResolver.cs
- SqlDataSourceCache.cs
- EdmItemError.cs
- InfiniteIntConverter.cs
- Console.cs
- SubMenuStyle.cs
- StringReader.cs
- StateMachineAction.cs
- Point3DCollection.cs
- MobileControl.cs