File: System\Windows\Controls\PasswordTextContainer.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description: Backing store for the PasswordBox control.
//
 
using System;
using System.Windows.Threading;
using System.Collections;
using System.Security;
using System.Diagnostics;
using System.Windows.Documents;
using MS.Internal;
using MS.Internal.Documents;
 
namespace System.Windows.Controls
{
    // Backing store for the PasswordBox control.
    //
    // This TextContainer implementation is unusual in that
    //
    //  - It never returns actual content through any of the abstract methods.
    //    If you need the actual content, you must cast to PasswordTextContainer
    //    and use the internal Password property.
    //
    //  - Performance doesn't scale with large documents.  The expectation is
    //    that the document will always be small (< 100 chars).
    //
    internal sealed class PasswordTextContainer : ITextContainer
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        // Creates a new PasswordTextContainer instance.
        internal PasswordTextContainer(PasswordBox passwordBox)
        {
            _passwordBox = passwordBox;
            _password = new SecureString();
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        /// Inserts text at a specified position.
        /// </summary>
        /// <param name="position">
        /// Position at which to insert the new text.
        /// </param>
        /// <param name="textData">
        /// Text to insert.
        /// </param>
        /// <remarks>
        /// Use the CanInsertText method to determine if text may be inserted
        /// at position.
        ///
        /// All positions at this location are repositioned
        /// before or after the inserted text according to their gravity.
        /// </remarks>
        internal void InsertText(ITextPointer position, string textData)
        {
            int offset;
            int i;
 
            BeginChange();
            try
            {
                offset = ((PasswordTextPointer)position).Offset;
 
                // Strangely, there is no SecureString.InsertAt(offset, string), so
                // we must use a loop here.
                for (i = 0; i < textData.Length; i++)
                {
                    _password.InsertAt(offset + i, textData[i]);
                }
 
                OnPasswordChange(offset, textData.Length);
            }
            finally
            {
                EndChange();
            }
        }
 
        /// <summary>
        /// Removes content covered by a pair of positions.
        /// </summary>
        /// <param name="startPosition">
        /// Position preceeding the first symbol to delete.  startPosition must be
        /// scoped by the same text element as endPosition.
        /// </param>
        /// <param name="endPosition">
        /// Position following the last symbol to delete.  endPosition must be
        /// scoped by the same text element as startPosition.
        /// </param>
        /// <remarks>
        /// Use CanDeleteContent to determine if content may be removed.
        /// </remarks>
        internal void DeleteContent(ITextPointer startPosition, ITextPointer endPosition)
        {
            int startOffset;
            int endOffset;
            int i;
 
            BeginChange();
            try
            {
                startOffset = ((PasswordTextPointer)startPosition).Offset;
                endOffset = ((PasswordTextPointer)endPosition).Offset;
 
                // Strangely, there is no SecureString.RemoveAt(offset, count), so
                // we must use a loop here.
                for (i = 0; i < endOffset - startOffset; i++)
                {
                    _password.RemoveAt(startOffset);
                }
 
                OnPasswordChange(startOffset, startOffset - endOffset);
            }
            finally
            {
                EndChange();
            }
        }
 
        /// <summary>
        /// </summary>
        internal void BeginChange()
        {
            _changeBlockLevel++;
 
            // We'll raise the Changing event when/if we get an actual
            // change added, inside AddChangeSegment.
        }
 
        /// <summary>
        /// </summary>
        internal void EndChange()
        {
            EndChange(false /* skipEvents */);
        }
 
        /// <summary>
        /// </summary>
        internal void EndChange(bool skipEvents)
        {
            TextContainerChangedEventArgs changes;
 
            // probably should throw
            Invariant.Assert(_changeBlockLevel > 0, "Unmatched EndChange call!");
 
            _changeBlockLevel--;
 
            if (_changeBlockLevel == 0 &&
                _changes != null)
            {
                changes = _changes;
                _changes = null;
 
                // Contact any listeners.
                if (this.Changed != null && !skipEvents)
                {
                    Changed(this, changes);
                }
            }
        }
 
        /// <summary>
        /// </summary>
        void ITextContainer.BeginChange()
        {
            BeginChange();
        }
 
        /// <summary>
        /// <see cref="System.Windows.Documents.ITextContainer.BeginChangeNoUndo"/>
        /// </summary>
        void ITextContainer.BeginChangeNoUndo()
        {
            // We don't support undo, so follow the BeginChange codepath.
            ((ITextContainer)this).BeginChange();
        }
 
        /// <summary>
        /// </summary>
        void ITextContainer.EndChange()
        {
            EndChange(false /* skipEvents */);
        }
 
        /// <summary>
        /// </summary>
        void ITextContainer.EndChange(bool skipEvents)
        {
            EndChange(skipEvents);
        }
 
        // Allocate a new ITextPointer at the specified offset.
        // Equivalent to this.Start.CreatePointer(offset), but does not
        // necessarily allocate this.Start.
        ITextPointer ITextContainer.CreatePointerAtOffset(int offset, LogicalDirection direction)
        {
            return new PasswordTextPointer(this, direction, offset);
        }
 
        // Allocate a new ITextPointer at a specificed offset in unicode chars within the document.
        ITextPointer ITextContainer.CreatePointerAtCharOffset(int charOffset, LogicalDirection direction)
        {
            return ((ITextContainer)this).CreatePointerAtOffset(charOffset, direction);
        }
 
        ITextPointer ITextContainer.CreateDynamicTextPointer(StaticTextPointer position, LogicalDirection direction)
        {
            return ((ITextPointer)position.Handle0).CreatePointer(direction);
        }
 
        StaticTextPointer ITextContainer.CreateStaticPointerAtOffset(int offset)
        {
            return new StaticTextPointer(this, ((ITextContainer)this).CreatePointerAtOffset(offset, LogicalDirection.Forward));
        }
 
        TextPointerContext ITextContainer.GetPointerContext(StaticTextPointer pointer, LogicalDirection direction)
        {
            return ((ITextPointer)pointer.Handle0).GetPointerContext(direction);
        }
 
        int ITextContainer.GetOffsetToPosition(StaticTextPointer position1, StaticTextPointer position2)
        {
            return ((ITextPointer)position1.Handle0).GetOffsetToPosition((ITextPointer)position2.Handle0);
        }
 
        int ITextContainer.GetTextInRun(StaticTextPointer position, LogicalDirection direction, char[] textBuffer, int startIndex, int count)
        {
            return ((ITextPointer)position.Handle0).GetTextInRun(direction, textBuffer, startIndex, count);
        }
 
        object ITextContainer.GetAdjacentElement(StaticTextPointer position, LogicalDirection direction)
        {
            return ((ITextPointer)position.Handle0).GetAdjacentElement(direction);
        }
 
        DependencyObject ITextContainer.GetParent(StaticTextPointer position)
        {
            return null;
        }
 
        StaticTextPointer ITextContainer.CreatePointer(StaticTextPointer position, int offset)
        {
            return new StaticTextPointer(this, ((ITextPointer)position.Handle0).CreatePointer(offset));
        }
 
        StaticTextPointer ITextContainer.GetNextContextPosition(StaticTextPointer position, LogicalDirection direction)
        {
            return new StaticTextPointer(this, ((ITextPointer)position.Handle0).GetNextContextPosition(direction));
        }
 
        int ITextContainer.CompareTo(StaticTextPointer position1, StaticTextPointer position2)
        {
            return ((ITextPointer)position1.Handle0).CompareTo((ITextPointer)position2.Handle0);
        }
 
        int ITextContainer.CompareTo(StaticTextPointer position1, ITextPointer position2)
        {
            return ((ITextPointer)position1.Handle0).CompareTo(position2);
        }
 
        object ITextContainer.GetValue(StaticTextPointer position, DependencyProperty formattingProperty)
        {
            return ((ITextPointer)position.Handle0).GetValue(formattingProperty);
        }
 
        // Adds a PasswordTextPointer to the list of live positions.
        // Positions in the list are updated as the document content changes.
        internal void AddPosition(PasswordTextPointer position)
        {
            int index;
 
            RemoveUnreferencedPositions();
 
            if (_positionList == null)
            {
                _positionList = new ArrayList();
            }
 
            index = FindIndex(position.Offset, position.LogicalDirection);
 
            _positionList.Insert(index, new WeakReference(position));
 
            DebugAssertPositionList();
        }
 
        // Removes a PasswordTextPointer from the list of live positions.
        // Positions in the list are updated as the document content changes.
        internal void RemovePosition(PasswordTextPointer searchPosition)
        {
            int index;
            PasswordTextPointer position;
 
            Invariant.Assert(_positionList != null);
 
            for (index = 0; index < _positionList.Count; index++)
            {
                position = GetPointerAtIndex(index);
 
                if (position == searchPosition)
                {
                    _positionList.RemoveAt(index);
                    // Tag index with a sentinel so we can assert below that we
                    // did find our position...
                    index = -1;
                    break;
                }
            }
            Invariant.Assert(index == -1, "Couldn't find position to remove!");
        }
 
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        /// <summary>
        /// Specifies whether or not the content of this PasswordTextContainer may be
        /// modified.
        /// </summary>
        /// <value>
        /// True if content may be modified, false otherwise.
        /// </value>
        /// <remarks>
        /// Methods that modify the PasswordTextContainer, such as InsertText or
        /// DeleteContent, will throw InvalidOperationExceptions if this
        /// property returns true.
        /// </remarks>
        bool ITextContainer.IsReadOnly
        {
            get
            {
                return false;
            }
        }
 
        /// <summary>
        /// A position preceding the first symbol of this PasswordTextContainer.
        /// </summary>
        /// <remarks>
        /// The returned ITextPointer has LogicalDirection.Backward gravity.
        /// </remarks>
        ITextPointer ITextContainer.Start
        {
            get
            {
                return this.Start;
            }
        }
 
        /// <summary>
        /// A position following the last symbol of this PasswordTextContainer.
        /// </summary>
        /// <remarks>
        /// The returned ITextPointer has LogicalDirection.Forward gravity.
        /// </remarks>
        ITextPointer ITextContainer.End
        {
            get
            {
                return this.End;
            }
        }
 
        /// <summary>
        /// Autoincremented counter of content changes in this TextContainer
        /// </summary>
        uint ITextContainer.Generation
        {
            get
            {
                //  This property is used only during textrange normalization.
                // For completeness, we should implement this counter since PasswordBox content is editable.
                // However in V1 we do not expose ranges in PasswordBox publicly.
                // Bug 1424449 tracks this issue.
                return 0;
            }
        }
 
        /// <summary>
        /// Collection of highlights applied to TextContainer content.
        /// </summary>
        Highlights ITextContainer.Highlights
        {
            get
            {
                if (_highlights == null)
                {
                    _highlights = new Highlights(this);
                }
 
                return _highlights;
            }
        }
 
        /// <summary>
        /// The object containing this PasswordTextContainer, from which property
        /// values are inherited.
        /// </summary>
        /// <remarks>
        /// May be null.
        /// </remarks>
        DependencyObject ITextContainer.Parent
        {
            get
            {
                return _passwordBox;
            }
        }
 
 
        // We need to store the text selection as we will use it later on to access
        // the UiScope during splitting text for selection purposes.
        ITextSelection ITextContainer.TextSelection
        {
            get
            {
                return _textSelection;
            }
 
            set
            {
                _textSelection = value;
            }
        }
 
        // Optional undo manager, always null for this ITextContainer.
        UndoManager ITextContainer.UndoManager
        {
            get
            {
                return null;
            }
        }
 
        // <see cref="System.Windows.Documents.ITextContainer/>
        ITextView ITextContainer.TextView
        {
            get
            {
                return this.TextView;
            }
 
            set
            {
                this.TextView = value;
            }
        }
 
        // TextView associated with this container.
        internal ITextView TextView
        {
            get
            {
                return _textview;
            }
 
            set
            {
                _textview = value;
            }
        }
 
        // Count of symbols in this tree, equivalent to this.Start.GetOffsetToPosition(this.End),
        // but doesn't necessarily allocate anything.
        int ITextContainer.SymbolCount
        {
            get
            {
                return this.SymbolCount;
            }
        }
 
        // Character count for the entire tree.
        int ITextContainer.IMECharCount
        {
            get
            {
                return this.SymbolCount;
            }
        }
 
        /// <summary>
        /// A position preceding the first symbol of this PasswordTextContainer.
        /// </summary>
        /// <remarks>
        /// The returned ITextPointer has LogicalDirection.Backward gravity.
        /// </remarks>
        internal ITextPointer Start
        {
            get
            {
                return new PasswordTextPointer(this, LogicalDirection.Backward, 0);
            }
        }
 
        /// <summary>
        /// A position following the last symbol of this PasswordTextContainer.
        /// </summary>
        /// <remarks>
        /// The returned ITextPointer has LogicalDirection.Forward gravity.
        /// </remarks>
        internal ITextPointer End
        {
            get
            {
                return new PasswordTextPointer(this, LogicalDirection.Forward, this.SymbolCount);
            }
        }
 
        // The actual content of the PasswordTextContainer.
        // GetText will not reveal this content.
        internal SecureString GetPasswordCopy()
        {
            return _password.Copy();
        }
 
        // Sets the content with a copy of a supplied SecureString.
        internal void SetPassword(SecureString value)
        {
            int symbolCount;
 
            symbolCount = this.SymbolCount;
            _password.Clear();
            OnPasswordChange(0, -symbolCount);
 
            _password = (value == null) ? new SecureString() : value.Copy();
            OnPasswordChange(0, this.SymbolCount);
        }
 
        // The character count of the content.  Because this TextContainer
        // does not accept embedded objects or text elements, the symbol count
        // is the Unicode character count.
        internal int SymbolCount
        {
            get
            {
                return _password.Length;
            }
        }
 
        // The character used to obfuscate the actual content when GetText is
        // called.  Defaults to '*'.
        internal char PasswordChar
        {
            get
            {
                return PasswordBox.PasswordChar;
            }
        }
 
        // PasswordBox associated with this content.
        internal PasswordBox PasswordBox
        {
            get
            {
                return _passwordBox;
            }
        }
 
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Internal Events
        //
        //------------------------------------------------------
 
        #region Internal Events
 
        event EventHandler ITextContainer.Changing
        {
            add { Changing += value; }
            remove { Changing -= value; }
        }
 
        event TextContainerChangeEventHandler ITextContainer.Change
        {
            add { Change += value; }
            remove { Change -= value; }
        }
 
        event TextContainerChangedEventHandler ITextContainer.Changed
        {
            add { Changed += value; }
            remove { Changed -= value; }
        }
 
        #endregion Internal Events
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        private void AddChange(ITextPointer startPosition, int symbolCount, PrecursorTextChangeType precursorTextChange)
        {
            Invariant.Assert(_changeBlockLevel > 0, "All public APIs must call BeginChange!");
            Invariant.Assert(!_isReadOnly, "Illegal to modify PasswordTextContainer inside Change event scope!");
 
            // Contact any listeners.
            if (this.Changing != null)
            {
                Changing(this, EventArgs.Empty);
            }
 
            // Fire the ChangingEvent now if we haven't already.
            if (_changes == null)
            {
                _changes = new TextContainerChangedEventArgs();
            }
 
            _changes.AddChange(precursorTextChange, startPosition.Offset, symbolCount, false /* collectTextChanges */);
 
            if (this.Change != null)
            {
                Invariant.Assert(precursorTextChange == PrecursorTextChangeType.ContentAdded || precursorTextChange == PrecursorTextChangeType.ContentRemoved);
                TextChangeType textChange = (precursorTextChange == PrecursorTextChangeType.ContentAdded) ?
                    TextChangeType.ContentAdded : TextChangeType.ContentRemoved;
 
                _isReadOnly = true;
                try
                {
                    Change(this, new TextContainerChangeEventArgs(startPosition, symbolCount, symbolCount, textChange));
                }
                finally
                {
                    _isReadOnly = false;
                }
            }
        }
 
        // Called whenever _password changes value.
        // Offset/delta identify the character offset at which chars have been
        // added (delta > 0) or removed (delta < 0).
        private void OnPasswordChange(int offset, int delta)
        {
            PasswordTextPointer textPosition;
            int symbolCount;
            PrecursorTextChangeType operation;
 
            if (delta != 0)
            {
                UpdatePositionList(offset, delta);
 
                textPosition = new PasswordTextPointer(this, LogicalDirection.Forward, offset);
 
                if (delta > 0)
                {
                    symbolCount = delta;
                    operation = PrecursorTextChangeType.ContentAdded;
                }
                else
                {
                    symbolCount = -delta;
                    operation = PrecursorTextChangeType.ContentRemoved;
                }
 
                AddChange(textPosition, symbolCount, operation);
            }
        }
 
        // Scans the list of live PasswordTextPositions, updating their
        // state to match a change to the document.
        // PasswordTextPositions "float" on the content, so need to adjust
        // to stay with local content after an insert or delete.
        private void UpdatePositionList(int offset, int delta)
        {
            int index;
            int backwardGravitySlot;
            PasswordTextPointer position;
 
            if (_positionList == null)
            {
                return;
            }
 
            RemoveUnreferencedPositions();
 
            // We ask for the first position at offset with Forward gravity.
            // This skips over all positions at offset with Backward gravity,
            // because we don't want to update them.
            index = FindIndex(offset, LogicalDirection.Forward);
 
            if (delta < 0)
            {
                // A delete.
                // Positions from offset to offset + -delta collapse to offset.
 
                // Track the first position index we found with Forward gravity.
                // As we walk along the list of positions scoped by the delete,
                // we'll use the index as a position to swap positions with
                // Backward gravity into.  This ensures that when we're done
                // all the positions with Offset == offset are sorted such that
                // Backward gravity positions precede Forward gravity positions.
                backwardGravitySlot = -1;
 
                for (; index < _positionList.Count; index++)
                {
                    position = GetPointerAtIndex(index);
 
                    if (position != null)
                    {
                        // If we found a position past the scope of the change,
                        // we can break out of this loop -- no more special cases.
                        if (position.Offset > offset + -delta)
                            break;
 
                        // Collapse the position down to the start offset.
                        position.Offset = offset;
 
                        // If the position has backward gravity, we need to make
                        // sure it stays sorted in our list -- positions at the
                        // same offset are sorted such that backward gravity
                        // positions precede forward gravity positions.
                        if (position.LogicalDirection == LogicalDirection.Backward)
                        {
                            if (backwardGravitySlot >= 0)
                            {
                                WeakReference tempWeakReference = (WeakReference)_positionList[backwardGravitySlot];
                                _positionList[backwardGravitySlot] = _positionList[index];
                                _positionList[index] = tempWeakReference;
                                backwardGravitySlot++;
                            }
                        }
                        else if (backwardGravitySlot == -1)
                        {
                            // This is the first position with Forward gravity,
                            // remember it.
                            backwardGravitySlot = index;
                        }
                    }
                }
            }
 
            // Fixup all the positions to the right of the insert/delete, but
            // not covered by a delete.
            for (; index < _positionList.Count; index++)
            {
                position = GetPointerAtIndex(index);
 
                if (position != null)
                {
                    position.Offset += delta;
                }
            }
 
            DebugAssertPositionList();
        }
 
        // Scans the list of live PasswordTextPositions, looking for entries
        // with no references.  If any are found, they are removed from the list.
        private void RemoveUnreferencedPositions()
        {
            int index;
            PasswordTextPointer position;
 
            if (_positionList == null)
            {
                return;
            }
 
            for (index = _positionList.Count-1; index >= 0; index--)
            {
                position = GetPointerAtIndex(index);
 
                if (position == null)
                {
                    _positionList.RemoveAt(index);
                }
            }
        }
 
        // Returns the index of the first PasswordTextPointer with the
        // specified offset and gravity in the live positions list.
        //
        // If no such position exists, returns the index of the next position,
        // the index at which a new position with the specified offset/gravity
        // would be inserted.
        private int FindIndex(int offset, LogicalDirection gravity)
        {
            PasswordTextPointer position;
            int index;
 
            Invariant.Assert(_positionList != null);
 
            for (index = 0; index < _positionList.Count; index++)
            {
                position = GetPointerAtIndex(index);
 
                if (position != null)
                {
                    if (position.Offset == offset &&
                        (position.LogicalDirection == gravity || gravity == LogicalDirection.Backward))
                    {
                        break;
                    }
 
                    if (position.Offset > offset)
                        break;
                }
            }
 
            return index;
        }
 
        // Debug only -- asserts the position list is in a good state.
        private void DebugAssertPositionList()
        {
            if (Invariant.Strict)
            {
                PasswordTextPointer position;
                int index;
                int lastOffset;
                LogicalDirection lastLogicalDirection;
 
                lastOffset = -1;
                lastLogicalDirection = LogicalDirection.Backward;
 
                for (index = 0; index < _positionList.Count; index++)
                {
                    position = GetPointerAtIndex(index);
 
                    if (position != null)
                    {
                        Invariant.Assert(position.Offset >= 0 && position.Offset <= _password.Length);
                        Invariant.Assert(lastOffset <= position.Offset);
 
                        if (index > 0 &&
                            position.LogicalDirection == LogicalDirection.Backward &&
                            lastOffset == position.Offset)
                        {
                            // Positions at the same offset should be ordered such
                            // that Backward gravity positions preceed Forward gravity positions.
                            Invariant.Assert(lastLogicalDirection != LogicalDirection.Forward);
                        }
 
                        lastOffset = position.Offset;
                        lastLogicalDirection = position.LogicalDirection;
                    }
                }
            }
        }
 
        // Returns a PasswordTextPointer at a given index within _positionList,
        // or null if the WeakReference at that index is dead.
        private PasswordTextPointer GetPointerAtIndex(int index)
        {
            WeakReference weakReference;
            object strongReference;
            PasswordTextPointer position;
 
            Invariant.Assert(_positionList != null);
 
            weakReference = (WeakReference)_positionList[index];
            Invariant.Assert(weakReference != null);
 
            strongReference = weakReference.Target;
            if (strongReference != null && !(strongReference is PasswordTextPointer))
            {
                // Diagnostics for bug 1267261.
                Invariant.Assert(false, $"Unexpected type: {strongReference.GetType()}");
            }
 
            position = (PasswordTextPointer)strongReference;
 
            return position;
        }
 
#if DEBUG
        // Debug only -- dumps the position list to the Console.
        // Use this method from the debugger's command window.
        private void DumpPositionList()
        {
            PasswordTextPointer position;
            int index;
 
            Debug.WriteLine($"{_positionList.Count} entries.");
 
            for (index = 0; index < _positionList.Count; index++)
            {
                position = GetPointerAtIndex(index);
 
                if (position != null)
                {
                    Debug.Write($"({position.DebugId}) {position.Offset}/{((position.LogicalDirection == LogicalDirection.Forward) ? "f " : "b ")}");
                }
                else
                {
                    Debug.Write("-/- ");
                }
            }
 
            Debug.WriteLine("");
        }
#endif // DEBUG
 
        #endregion Private methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // PasswordBox associated with this content.
        private readonly PasswordBox _passwordBox;
 
        // The TextContainer content.
        private SecureString _password;
 
        // List of live PasswordTextPositions.
        private ArrayList _positionList;
 
        // Collection of highlights applied to TextContainer content.
        private Highlights _highlights;
 
        // BeginChange ref count.  When non-zero, we are inside a change block.
        private int _changeBlockLevel;
 
        // Array of pending changes in the current change block.
        // Null outside of a change block.
        private TextContainerChangedEventArgs _changes;
 
        // TextView associated with this TextContainer.
        private ITextView _textview;
 
        // Set true during Change event callback.
        // When true, modifying the TextContainer is disallowed.
        private bool _isReadOnly;
 
        // implementation of ITextContainer.Changing
        private EventHandler Changing;
 
        // implementation of ITextContainer.Change
        private TextContainerChangeEventHandler Change;
 
        // implementation of ITextContainer.Changed
        private TextContainerChangedEventHandler Changed;
 
        // The current ITextSelection
        private ITextSelection _textSelection;
 
        #endregion Private Fields
    }
}