TextRangeBase.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Framework / System / Windows / Documents / TextRangeBase.cs / 4 / TextRangeBase.cs

                            //---------------------------------------------------------------------------- 
//
// File: TextRangeBase.cs
//
// Copyright (C) Microsoft Corporation.  All rights reserved. 
//
// Description: Provides an abstract level of TextRange implementation 
//      Implemented as a static class containing a set of methods 
//      implementing members of abstract ITextRange interface.
//      These members are supposed to be called from concrete 
//      classes TextRange and TextSelection - to ensure that the
//      both have the same base implementation.
//
//      TextSelection is allowed to add additional actions over 
//      base ones. TextRange must do pure call redirections,
//      otherwise TextSelection inheritance from TextRange 
//      will be broken. 
//
//      Only methods that require virtualization for TextSelection 
//      implementation go here. All other methods of ITextRange
//      are implemented directly in TextRange clas in appropriate
//      ITextRange.Member.
// 
//---------------------------------------------------------------------------
 
namespace System.Windows.Documents 
{
    using MS.Internal; 
    using System.Collections;
    using System.Collections.Generic;
    using System.Threading;
    using System.Globalization; 
    using System.Text;
    using System.Xml; 
    using System.IO; 
    using MS.Internal.Documents;
    using System.Windows.Controls; // TextBlock 
    using MS.Internal.PresentationFramework;                   // SecurityHelper

    /// 
    /// A class a portion of text content. 
    /// Can be contigous or disjoint; supports rectangular table ranges.
    /// Provides an API for text and table editing operations. 
    ///  
    internal static class TextRangeBase
    { 
        //-----------------------------------------------------
        //
        // ITextRange Methods
        // 
        //-----------------------------------------------------
 
        #region ITextRange Methods 

        //...................................................... 
        //
        // Selection Building
        //
        //...................................................... 

        ///  
        ///  
        //
        internal static bool Contains(ITextRange thisRange, ITextPointer textPointer) 
        {
            NormalizeRange(thisRange);

            if (textPointer == null) 
            {
                throw new ArgumentNullException("textPointer"); 
            } 

            if (textPointer.TextContainer != thisRange.Start.TextContainer) 
            {
                throw new ArgumentException(SR.Get(SRID.NotInAssociatedTree), "textPointer");
            }
 
            // Correct position normalization on range boundary so that
            // our test would not depend on what side of formatting tags 
            // pointer is located. 
            if (textPointer.CompareTo(thisRange.Start) < 0)
            { 
                textPointer = textPointer.GetFormatNormalizedPosition(LogicalDirection.Forward);
            }
            else if (textPointer.CompareTo(thisRange.End) > 0)
            { 
                textPointer = textPointer.GetFormatNormalizedPosition(LogicalDirection.Backward);
            } 
 
            // Check if at least one segment contains this position.
            for (int i = 0; i < thisRange._TextSegments.Count; i++) 
            {
                if (thisRange._TextSegments[i].Contains(textPointer))
                {
                    return true; 
                }
            } 
 
            return false;
        } 

        /// 
        /// Base implementation of ITextRange.Select method.
        ///  
        /// 
        /// The range which is an object of this operation. 
        ///  
        /// 
        /// One of two boundary positions for building selection. 
        /// In case of table cell-crossing selection it is considered
        /// as an "anchor" position, so the selection will always include
        /// the cell at this position
        ///  
        /// 
        /// The other of two buondary positions for building selection. 
        /// In case of table cell-crossing selection it is considered 
        /// as a "moving" position, so the selection may not include the cell
        /// at this position - when the cell has bigger index than the anchor 
        /// one and when it is positioned at the very beginning of a cell,
        /// (if any of these two conditions if false then the cell at this position is included).
        /// 
        internal static void Select(ITextRange thisRange, ITextPointer position1, ITextPointer position2) 
        {
            Select(thisRange, position1, position2, /*includeCellAtMovingPosition:*/false); 
        } 

        ///  
        /// Base implementation of ITextRange.Select method.
        /// 
        /// 
        /// The range which is an object of this operation. 
        /// 
        ///  
        /// One of two boundary positions for building selection. 
        /// In case of table cell-crossing selection it is considered
        /// as an "anchor" position, so the selection will always include 
        /// the cell at this position
        /// 
        /// 
        /// The other of two buondary positions for building selection. 
        /// In case of table cell-crossing selection it is considered
        /// as a "moving" position, so the selection may not include the cell 
        /// at this position - when the cell has bigger index than the anchor 
        /// one and when it is positioned at the very beginning of a cell,
        /// and when includeCellAtMovingPosition==false (if any of these three 
        /// conditions if false then the cell at this position is included).
        /// 
        /// 
        /// True indicates that a cell at a movingPosition must be included 
        /// into a selection even when it is at cell start.
        /// False indicates that when a movingPosition is at cell start 
        /// and the cell has bigger index than anchor cell, then selection 
        /// should not include it - it only indicates cell crossing.
        /// When we build a table range from existing range's Start/End pair 
        /// we must use false for this parameter - because the end position
        /// of a table range is not included into it - by construction.
        /// When you use independent position - say, from hit-testing -
        /// then you typically use "true" for this parameter, unnless 
        /// you intentially cross cell boundary - as for one cell celection.
        ///  
        internal static void Select(ITextRange thisRange, ITextPointer position1, ITextPointer position2, bool includeCellAtMovingPosition) 
        {
            if (thisRange._TextSegments == null) 
            {
                // This is initializing call from TextRange constructor.
                // No need in change notifications, no need in position verification.
                TextRangeBase.SelectPrivate(thisRange, position1, position2, includeCellAtMovingPosition, /*markRangeChanged*/false); 
            }
            else 
            { 
                ValidationHelper.VerifyPosition(thisRange.Start.TextContainer, position1, "position1");
                ValidationHelper.VerifyPosition(thisRange.Start.TextContainer, position2, "position2"); 

                TextRangeBase.BeginChange(thisRange);
                try
                { 
                    TextRangeBase.SelectPrivate(thisRange, position1, position2, includeCellAtMovingPosition, /*markRangeChanged*/true);
                } 
                finally 
                {
                    TextRangeBase.EndChange(thisRange); 
                }
            }
        }
 
        /// 
        /// Selects a word containing this position 
        ///  
        /// 
        ///  
        /// A TextPointer containing a word to select.
        /// 
        internal static void SelectWord(ITextRange thisRange, ITextPointer position)
        { 
            if (position == null)
            { 
                throw new ArgumentNullException("position"); 
            }
 
            // Move position to character boundary (also respect atomics)
            //

            ITextPointer normalizedPosition = position.CreatePointer(); 
            normalizedPosition.MoveToInsertionPosition(LogicalDirection.Backward);
 
            TextSegment wordRange = TextPointerBase.GetWordRange(normalizedPosition); 

            TextRangeBase.Select(thisRange, wordRange.Start, wordRange.End); 
        }

        // Returns a word within which empty selection is located.
        // Returns TextSegment.Null if selection is not empty or 
        // if the position is between or at word boundary.
        internal static TextSegment GetAutoWord(ITextRange thisRange) 
        { 
            TextSegment autoWordRange = TextSegment.Null;
 
            if (thisRange.IsEmpty && //
                !TextPointerBase.IsAtWordBoundary(thisRange.Start, LogicalDirection.Forward) && //
                !TextPointerBase.IsAtWordBoundary(thisRange.Start, LogicalDirection.Backward))
            { 
                //
 
                autoWordRange = TextPointerBase.GetWordRange(thisRange.Start); 
                string autoWord = TextRangeBase.GetTextInternal(autoWordRange.Start, autoWordRange.End).TrimEnd(' ');
 
                string textFromWordStart = TextRangeBase.GetTextInternal(autoWordRange.Start, thisRange.Start);

                if (textFromWordStart.Length >= autoWord.Length)
                { 
                    // The caret is beyond the end of a word (in a whitespace area)
                    autoWordRange = TextSegment.Null; 
                } 
            }
 
            return autoWordRange;
        }

        ///  
        /// Selects a paragraph around the given position.
        ///  
        ///  
        /// 
        /// A position identifying a paragraph to select. 
        /// 
        internal static void SelectParagraph(ITextRange thisRange, ITextPointer position)
        {
            if (position == null) 
            {
                throw new ArgumentNullException("position"); 
            } 

            ITextPointer start; 
            ITextPointer end;
            FindParagraphOrListItemBoundaries(position, out start, out end);

            // Select the paragraph contents 
            TextRangeBase.Select(thisRange, start, end);
        } 
 
        // Apply initial typing heuristics -- adjust range for typing
        // when it spans one or more TableCells. 
        //
        // ApplyInitialTypingHeuristics/ApplyFinalTypingHeuristics are
        // called together, with a an extra step in between for TextSelection
        // overrides of the ApplyTypingHueristic method. 
        internal static void ApplyInitialTypingHeuristics(ITextRange thisRange)
        { 
            // When table cells selected, clear the start cell and collapse selection into it 
            if (thisRange.IsTableCellRange)
            { 
                TableCell cell;
                if (thisRange.Start is TextPointer &&
                    (cell = TextRangeEditTables.GetTableCellFromPosition((TextPointer)thisRange.Start)) != null)
                { 
                    // Select the first cell content to make springload formatting happen below
                    thisRange.Select(cell.ContentStart, cell.ContentEnd); 
                } 
                else
                { 
                    thisRange.Select(thisRange.Start, thisRange.Start);
                }
            }
        } 

        // Apply typing heuristics 
        //  - extend for overtype. 
        //  - prevent paragraph merges when only the leading edge of that
        //    last paragraph is selected. 
        //
        // ApplyInitialTypingHeuristics/ApplyFinalTypingHeuristics are
        // called together, with a an extra step in between for TextSelection
        // overrides of the ApplyTypingHueristic method. 
        internal static void ApplyFinalTypingHeuristics(ITextRange thisRange, bool overType)
        { 
            // Expand empty selection forward in overtype mode 
            if (overType && thisRange.IsEmpty &&
                !TextPointerBase.IsNextToAnyBreak(thisRange.End, LogicalDirection.Forward)) 
            {
                //

 
                ITextPointer nextPosition = thisRange.End.CreatePointer();
                nextPosition.MoveToNextInsertionPosition(LogicalDirection.Forward); 
                if (!TextRangeEditTables.IsTableStructureCrossed(thisRange.Start, nextPosition)) 
                {
                    TextRange range = new TextRange(thisRange.Start, nextPosition); 
                    Invariant.Assert(!range.IsTableCellRange);

                    range.Text = String.Empty;
                } 
            }
 
            // If the range is non-empty, and its end just passes a paragraph break, 
            // pull the end back to stop a paragraph merge on the next keystroke.
            if (!thisRange.IsEmpty && 
                (TextPointerBase.IsNextToAnyBreak(thisRange.End, LogicalDirection.Backward) ||
                 TextPointerBase.IsAfterLastParagraph(thisRange.End)))
            {
                ITextPointer newEnd = thisRange.End.GetNextInsertionPosition(LogicalDirection.Backward); 
                thisRange.Select(thisRange.Start, newEnd);
            } 
        } 

        ///  
        /// 
        /// 
        internal static void ApplyTypingHeuristics(ITextRange thisRange, bool overType)
        { 
            BeginChange(thisRange);
            try 
            { 
                ApplyInitialTypingHeuristics(thisRange);
                ApplyFinalTypingHeuristics(thisRange, overType); 
            }
            finally
            {
                EndChange(thisRange); 
            }
        } 
 
        internal static void FindParagraphOrListItemBoundaries(ITextPointer position, out ITextPointer start, out ITextPointer end)
        { 
            // Identify a maximum portion of text around navigator
            // which may be wrapped by Paragraph
            start = position.CreatePointer();
            end = position.CreatePointer(); 
            SkipParagraphContent(start, LogicalDirection.Backward);
            SkipParagraphContent(end, LogicalDirection.Forward); 
        } 

        // Moves the navigator in the given direction over all characters, 
        // embedded objects and formatting tags.
        //

        private static void SkipParagraphContent(ITextPointer navigator, LogicalDirection direction) 
        {
            TextPointerContext nextContext = navigator.GetPointerContext(direction); 
 
            while (true)
            { 
                if (nextContext == TextPointerContext.None //
                    || //
                    // Entering non-inline content
                    (nextContext == TextPointerContext.ElementStart && direction == LogicalDirection.Forward || // 
                    nextContext == TextPointerContext.ElementEnd && direction == LogicalDirection.Backward) && //
                    !typeof(Inline).IsAssignableFrom(navigator.GetElementType(direction)) // 
                    || 
                    // Exiting non-inline content
                    (nextContext == TextPointerContext.ElementEnd && direction == LogicalDirection.Forward || // 
                    nextContext == TextPointerContext.ElementStart && direction == LogicalDirection.Backward) && //
                    !typeof(Inline).IsAssignableFrom(navigator.ParentType))
                {
                    // End of paragraph content reached. Stop here. 
                    break;
                } 
 
                //Need to bail out if MoveToNextContentPosition fails
                if (!navigator.MoveToNextContextPosition(direction)) 
                {
                    break;
                }
                nextContext = navigator.GetPointerContext(direction); 
            }
        } 
 
        // Calculates a value of a given property on this range
        internal static object GetPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty) 
        {
            if (TextSchema.IsCharacterProperty(formattingProperty))
            {
                return GetCharacterPropertyValue(thisRange, formattingProperty); 
            }
            else 
            { 
                Invariant.Assert(TextSchema.IsParagraphProperty(formattingProperty), "The property is expected to be one of either character or paragraph formatting one");
                return GetParagraphPropertyValue(thisRange, formattingProperty); 
            }
        }

        // Calculates character formatting property 
        private static object GetCharacterPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty)
        { 
            // 

            object startValue = GetCharacterValueFromPosition(thisRange.Start, formattingProperty); 

            // Need to run over all text runs to check that the value is the same for all of them.
            // We'll stop on the first different value if any; and return MixedValue.Instance
            for (int i = 0; i < thisRange._TextSegments.Count; i++) 
            {
                TextSegment textSegment = thisRange._TextSegments[i]; 
 
                ITextPointer position = textSegment.Start.CreatePointer();
                bool moved = true; 
                while (moved && position.CompareTo(textSegment.End) < 0)
                {
                    // Check whether the value in this text run is the same as at the beginning
                    object value = GetCharacterValueFromPosition(position, formattingProperty); 
                    if (!TextSchema.ValuesAreEqual(value, startValue))
                    { 
                        return DependencyProperty.UnsetValue; // 
                    }
 
                    // Skip text run
                    if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
                    {
                        moved = position.MoveToNextContextPosition(LogicalDirection.Forward); 
                    }
 
                    // try to skip formatting tags 
                    moved = position.MoveToInsertionPosition(LogicalDirection.Forward);
 
                    if (!moved)
                    {
                        // Go to the next run, of there was no formatting boundary
                        moved = position.MoveToNextInsertionPosition(LogicalDirection.Forward); 
                    }
                } 
            } 

            return startValue; 
        }

        // Gets a non-inherited property from a given position
        private static object GetCharacterValueFromPosition(ITextPointer pointer, DependencyProperty formattingProperty) 
        {
            object value = null; 
 
            if (formattingProperty != Inline.TextDecorationsProperty)
            { 
                value = pointer.GetValue(formattingProperty);
            }
            else
            { 
                if (pointer is TextPointer) // Implement only for concrete TextCotainer returning null otherwise - for optimization
                { 
                    DependencyObject element = ((TextPointer)pointer).Parent as TextElement; 
                    while (value == null && (element is Inline || element is Paragraph || element is TextBlock))
                    { 
                        value = element.GetValue(formattingProperty);

                        element = element is TextElement ? ((TextElement)element).Parent : null;
                    } 
                }
            } 
 
            return value;
        } 

        // Calculates paragraph formatting property
        // Returns DependencyProperty.UnsetValue if different areas of the range have different value for this property.
        private static object GetParagraphPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty) 
        {
            object startValue = null; 
 
            // Need to run over all text runs to check that the value is the same for all of them.
            // We'll stop on the first different value if any; and return MixedValue.Instance 
            for (int i = 0; i < thisRange._TextSegments.Count; i++)
            {
                TextSegment textSegment = thisRange._TextSegments[i];
 
                ITextPointer position = textSegment.Start.CreatePointer();
 
                // Find position scoped by paragraph - in backward direction - to get start value 
                while (!typeof(Paragraph).IsAssignableFrom(position.ParentType) &&
                    position.MoveToNextContextPosition(LogicalDirection.Backward)) ; 

                // Traverse the segment to find all other paragraph positions
                bool moved = true;
                while (moved && position.CompareTo(textSegment.End) <= 0) 
                {
                    if (typeof(Paragraph).IsAssignableFrom(position.ParentType)) 
                    { 
                        object value = position.GetValue(formattingProperty);
                        if (startValue == null) 
                        {
                            startValue = value;
                        }
 
                        if (!TextSchema.ValuesAreEqual(value, startValue))
                        { 
                            return DependencyProperty.UnsetValue; 
                        }
 
                        position.MoveToElementEdge(ElementEdge.AfterEnd);
                    }
                    moved = position.MoveToNextContextPosition(LogicalDirection.Forward);
                } 
            }
 
            // Most properties does not allow null as a value, 
            // so if we still have null try to get a value from range start position.
            // For some properties (like TextDecorations) it still may remain null. 
            if (startValue == null)
            {
                startValue = thisRange.Start.GetValue(formattingProperty);
            } 

            return startValue; 
        } 

        // Returns true if this range start and end pointers cross a paragraph boundary, false otherwise. 
        internal static bool IsParagraphBoundaryCrossed(ITextRange thisRange)
        {
            ITextPointer startNavigator = thisRange.Start.CreatePointer();
            ITextPointer endNavigator = thisRange.End.CreatePointer(); 

            if (TextPointerBase.IsAfterLastParagraph(endNavigator)) 
            { 
                endNavigator.MoveToInsertionPosition(LogicalDirection.Backward);
            } 

            // Walk upto the closest block ancestor
            while (typeof(Inline).IsAssignableFrom(startNavigator.ParentType))
            { 
                startNavigator.MoveToElementEdge(ElementEdge.AfterEnd);
            } 
            while (typeof(Inline).IsAssignableFrom(endNavigator.ParentType)) 
            {
                endNavigator.MoveToElementEdge(ElementEdge.AfterEnd); 
            }

            // start and end are within the scope of the same paragraph?
            return !startNavigator.HasEqualScope(endNavigator); 
        }
 
        //......................................................... 
        //
        //  Change Notifications 
        //
        //.........................................................

        ///  
        /// 
        ///  
        internal static void BeginChange(ITextRange thisRange) 
        {
            BeginChangeWorker(thisRange, String.Empty); 
        }

        /// 
        ///  
        /// 
        internal static void BeginChangeNoUndo(ITextRange thisRange) 
        { 
            BeginChangeWorker(thisRange, null);
        } 

        /// 
        /// 
        ///  
        internal static void EndChange(ITextRange thisRange)
        { 
            EndChange(thisRange, false /* disableScroll */, false /* skipEvents */ ); 
        }
 
        /// 
        /// 
        /// 
        internal static void EndChange(ITextRange thisRange, bool disableScroll, bool skipEvents) 
        {
            ChangeBlockUndoRecord changeBlockUndoRecord; 
            bool isChanged; 
            ITextContainer textContainer;
 
            Invariant.Assert(thisRange._ChangeBlockLevel > 0, "Unmatched EndChange call!");

            textContainer = thisRange.Start.TextContainer;
 
            try
            { 
                // 
                // Complete the content changed block.
                // 
                try
                {
                    // Raise first public event -- TextContainer.EndChange.
                    textContainer.EndChange(skipEvents); 
                }
                finally 
                { 
                    // Always drop the ChangeBlockLevel, no matter what happens.
                    // This ensures that we won't ignore future events if the 
                    // application recovers from an exception.
                    thisRange._ChangeBlockLevel--;

                    // Clear out thisRange.IsChanged now so that it isn't 
                    // left dangling if TextContainer.EndChange throws
                    // an exception. 
                    isChanged = thisRange._IsChanged; 
                    if (thisRange._ChangeBlockLevel == 0)
                    { 
                        thisRange._IsChanged = false;
                    }
                }
 
                //
                // Complete the range repositioned block. 
                // 
                if (thisRange._ChangeBlockLevel == 0 && isChanged)
                { 
                    // Raise the second public event -- TextRange.Changed.
                    thisRange.NotifyChanged(disableScroll, skipEvents);
                }
            } 
            finally
            { 
                // Make sure we close the undo record no matter what happened. 
                changeBlockUndoRecord = (ChangeBlockUndoRecord)thisRange._ChangeBlockUndoRecord;
                if (changeBlockUndoRecord != null && thisRange._ChangeBlockLevel == 0) 
                {
                    try
                    {
                        changeBlockUndoRecord.OnEndChange(); 
                    }
                    finally 
                    { 
                        thisRange._ChangeBlockUndoRecord = null;
                    } 
                }
            }
        }
 
        internal static void NotifyChanged(ITextRange thisRange, bool disableScroll)
        { 
            thisRange.FireChanged(); 
        }
 
        #endregion ITextRange Methods

        // ....................................................................
        // 
        // Static Helpers for dealing with content without range instantiation
        // 
        // .................................................................... 

        #region TextRange Helpers 

        // Returns the text covered by two TextPositions as a string.
        // Includes rules for translating paragraph breaks and embedded objects.
        internal static string GetTextInternal(ITextPointer startPosition, ITextPointer endPosition) 
        {
            Char[] charArray = null; // used for extracting text runs 
 
            return GetTextInternal(startPosition, endPosition, ref charArray);
        } 

        // Returns the text covered by two TextPositions as a string.
        // Includes rules for translating paragraph breaks and embedded objects.
        // 
        // Use this overload when looping over large quantities of text to avoid
        // re-allocating a temporary buffer. 
        internal static string GetTextInternal(ITextPointer startPosition, ITextPointer endPosition, ref Char[] charArray) 
        {
            // Buffer for building a resulting plain text 
            StringBuilder textBuffer = new StringBuilder();

            // Stack of List context - needed for efficient bullet generation
            Stack listItemCounter = null; 

            ITextPointer navigator = startPosition.CreatePointer(); 
 
            Invariant.Assert(startPosition.CompareTo(endPosition) <= 0, "expecting: startPosition <= endPosition");
 
            while (navigator.CompareTo(endPosition) < 0)
            {
                Type elementType;
 
                TextPointerContext symbolType = navigator.GetPointerContext(LogicalDirection.Forward);
                switch (symbolType) 
                { 
                    case TextPointerContext.Text:
                        PlainConvertTextRun(textBuffer, navigator, endPosition, ref charArray); 
                        break;
                    case TextPointerContext.ElementEnd:
                        elementType = navigator.ParentType;
 
                        if (typeof(Paragraph).IsAssignableFrom(elementType) ||
                            typeof(BlockUIContainer).IsAssignableFrom(elementType)) 
                        { 
                            PlainConvertParagraphEnd(textBuffer, navigator);
                        } 
                        else if (typeof(LineBreak).IsAssignableFrom(elementType))
                        {
                            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                            textBuffer.Append(Environment.NewLine); 
                        }
                        else if (typeof(List).IsAssignableFrom(elementType)) 
                        { 
                            PlainConvertListEnd(navigator, ref listItemCounter);
                        } 
                        else
                        {
                            // All other closing tags - just skip them
                            navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
                        }
                        break; 
                    case TextPointerContext.EmbeddedElement : 
                        textBuffer.Append('\u0020'); // Substitute SPACE for embedded objects.
                        navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
                        break;
                    case TextPointerContext.ElementStart :
                        elementType = navigator.GetElementType(LogicalDirection.Forward);
                        if (typeof(AnchoredBlock).IsAssignableFrom(elementType)) 
                        {
                            // Floaters and figures must start from a new line 
                            textBuffer.Append(Environment.NewLine); 
                        }
                        else if (typeof(List).IsAssignableFrom(elementType) && navigator is TextPointer) 
                        {
                            // New list level opens
                            PlainConvertListStart(navigator, ref listItemCounter);
                        } 
                        else if (typeof(ListItem).IsAssignableFrom(elementType))
                        { 
                            // List items must be preceeded by a list marker 
                            PlainConvertListItemStart(textBuffer, navigator, ref listItemCounter);
                        } 
                        else
                        {
                            PlainConvertAccessKey(textBuffer, navigator);
                        } 
                        navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        break; 
 
                    default:
                        Invariant.Assert(false, "Unexpected vlue for TextPointerContext"); 
                        break;
                }
            }
 
            return textBuffer.ToString();
        } 
 
        // Part of plain text converter: called from GetTextInternal when processing Text runs
        private static void PlainConvertTextRun(StringBuilder textBuffer, ITextPointer navigator, ITextPointer endPosition, ref Char[] charArray) 
        {
            // Copy this text run into destination
            int runLength = navigator.GetTextRunLength(LogicalDirection.Forward);
            charArray = EnsureCharArraySize(charArray, runLength); 
            runLength = TextPointerBase.GetTextWithLimit(navigator, LogicalDirection.Forward, charArray, 0, runLength, endPosition);
            textBuffer.Append(charArray, 0, runLength); 
            navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
        }
 
        // Part of plain text converter: called from GetTextInternal when processing ElementEnd for Paragraph elements.
        // Outputs \n - for regular paragraphs and TableRow ends or \t for TableCell ends.
        private static void PlainConvertParagraphEnd(StringBuilder textBuffer, ITextPointer navigator)
        { 
            // Check for a special case for a single paragraph within a TableCell
            // which must be serialized as "\t" character. 
            navigator.MoveToElementEdge(ElementEdge.BeforeStart); 
            bool theParagraphIsTheFirstInCollection = navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart;
            navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
            navigator.MoveToElementEdge(ElementEdge.AfterEnd);
            TextPointerContext symbolType = navigator.GetPointerContext(LogicalDirection.Forward);

            if (theParagraphIsTheFirstInCollection && symbolType == TextPointerContext.ElementEnd && 
                typeof(TableCell).IsAssignableFrom(navigator.ParentType))
            { 
                // This is an end of a table cell 

                navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
                symbolType = navigator.GetPointerContext(LogicalDirection.Forward);
                if (symbolType == TextPointerContext.ElementStart)
                {
                    // Next table cell starts after this one. Use '\t' as a cell separator 
                    textBuffer.Append('\t');
                } 
                else 
                {
                    // This was the last cell in a row. Use '\r\n' as a line separator 
                    textBuffer.Append(Environment.NewLine);
                }
            }
            else 
            {
                // Ordinary paragraph end 
                textBuffer.Append(Environment.NewLine); 
            }
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementStart for List elements.
        // Initializes a stack of list item counters and pushes a new zero for the opened list level.
        private static void PlainConvertListStart(ITextPointer navigator, ref Stack listItemCounter) 
        {
            List list = (List)navigator.GetAdjacentElement(LogicalDirection.Forward); 
 
            // Initialize list context
            if (listItemCounter == null) 
            {
                listItemCounter = new Stack(1);
            }
            listItemCounter.Push(0); 
        }
 
        // Part of plain text converter: called from GetTextInternal when processing ElementEnd for Listelements 
        // Pops a current value from a stack of list item indices.
        private static void PlainConvertListEnd(ITextPointer navigator, ref Stack listItemCounter) 
        {
            // Note that we do not expect List tag balansing:
            // We can get more List closing tags than we had opening ones -
            // it happens when range starts in the middle of a list. 
            if (listItemCounter != null && listItemCounter.Count > 0)
            { 
                listItemCounter.Pop(); 
            }
            navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
        }

        // Part of plain text converter: called from GetTextInternal when processing ElementStart for ListItem elements
        // Uses s stack of list items indices and updates it for following list items. 
        private static void PlainConvertListItemStart(StringBuilder textBuffer, ITextPointer navigator, ref Stack listItemCounter)
        { 
            if (navigator is TextPointer) // can do somethinng useful only in concrete TextContainer - not in an abstract one 
            {
                List list = (List)((TextPointer)navigator).Parent; 
                ListItem listItem = (ListItem)navigator.GetAdjacentElement(LogicalDirection.Forward);

                // Initialize list context
                if (listItemCounter == null) 
                {
                    listItemCounter = new Stack(1); 
                } 
                if (listItemCounter.Count == 0)
                { 
                    // List is taken from its middle position. Need to identify starting item number
                    listItemCounter.Push(((IList)listItem.SiblingListItems).IndexOf(listItem));
                }
 
                // Get list item number
                Invariant.Assert(listItemCounter.Count > 0, "expectinng listItemCounter.Count > 0"); 
                int listItemIndex = listItemCounter.Pop(); 
                int indexBase = list != null ? list.StartIndex : 0;
                TextMarkerStyle markerStyle = list != null ? list.MarkerStyle : TextMarkerStyle.Disc; 

                WriteListMarker(textBuffer, markerStyle, listItemIndex + indexBase);

                // Advance 
                listItemIndex++;
                listItemCounter.Push(listItemIndex); 
            } 
        }
 
        // Part of plain text converter: called from GetTextInternal when processing ElementStart for AccessKey elements
        // Uses s stack of list items indices and updates it for following list items.
        private static void PlainConvertAccessKey(StringBuilder textBuffer, ITextPointer navigator)
        { 
            // Creating an "_" prefix for AccessKey character (represented as a Run with special serialization attribution)
            object element = navigator.GetAdjacentElement(LogicalDirection.Forward); 
            if (AccessText.HasCustomSerialization(element)) 
            {
                textBuffer.Append(AccessText.AccessKeyMarker); 
            }
        }

        // Helper for GetTextInternal, manages a char buffer. 
        // NOTE: Does not preserve the content of a buffer
        private static Char[] EnsureCharArraySize(Char[] charArray, int textLength) 
        { 
            if (charArray == null)
            { 
                charArray = new char[textLength + 10];
            }
            else if (charArray.Length < textLength)
            { 
                int newLength = charArray.Length * 2;
                if (newLength < textLength) 
                { 
                    newLength = textLength + 10;
                } 

                charArray = new Char[newLength];
            }
            return charArray; 
        }
 
        // Writes a text representation of a list marker 
        private static void WriteListMarker(StringBuilder textBuffer, TextMarkerStyle listMarkerStyle, int listItemNumber)
        { 
            string markerText = null;
            Char[] charArray = null;

            switch (listMarkerStyle) 
            {
                case TextMarkerStyle.None : 
                    markerText = ""; 
                    break;
                case TextMarkerStyle.Disc : 
                    markerText = "\x2022"; // Bullet // not a "\x9f";
                    break;
                case TextMarkerStyle.Circle :
                    markerText = "\x25CB"; // White Circle // not a "\xa1"; 
                    break;
                case TextMarkerStyle.Square : 
                    markerText = "\x25A1"; // White Box // not a "\x71"; 
                    break;
                case TextMarkerStyle.Box : 
                    markerText = "\x25A0"; // Black Box // not a "\xa7";
                    break;

                case TextMarkerStyle.Decimal: 
                    charArray = ConvertNumberToString(listItemNumber, false, DecimalNumerics);
                    break; 
 
                case TextMarkerStyle.LowerLatin:
                    charArray = ConvertNumberToString(listItemNumber, true, LowerLatinNumerics); 
                    break;

                case TextMarkerStyle.UpperLatin:
                    charArray = ConvertNumberToString(listItemNumber, true, UpperLatinNumerics); 
                    break;
 
                case TextMarkerStyle.LowerRoman: 
                    markerText = ConvertNumberToRomanString(listItemNumber, false);
                    break; 

                case TextMarkerStyle.UpperRoman:
                    markerText = ConvertNumberToRomanString(listItemNumber, true);
                    break; 
            }
 
            if (markerText != null) 
            {
                textBuffer.Append(markerText); 
            }
            else if (charArray != null)
            {
                textBuffer.Append(charArray, 0, charArray.Length); 
            }
            textBuffer.Append('\t'); 
        } 

        private const char NumberSuffix = '.'; 

        private const string DecimalNumerics = "0123456789";
        private const string LowerLatinNumerics = "abcdefghijklmnopqrstuvwxyz";
        private const string UpperLatinNumerics = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 

        private static string[][] RomanNumerics = new string[][] 
        { 
            new string[] { "m??", "cdm", "xlc", "ivx" },
            new string[] { "M??", "CDM", "XLC", "IVX" } 
        };

        /// 
        /// Convert a number to string, consisting of digits followed by the NumberSuffix character. 
        /// 
        /// Number to convert. 
        /// True if there is no zero digit (e.g., alpha numbering). 
        /// Set of digits (e.g., 0-9 or a-z).
        /// Returns the number string as an array of characters. 
        private static char[] ConvertNumberToString(int number, bool oneBased, string numericSymbols)
        {
            //  Whether zero-based or one-based numbering is used affects how we
            //  count and how we determine the maximum number of values for a 
            //  given number of digits.
            // 
            //  The following table illustrates how counting differs. In both 
            //  cases we're using base-2 numbering (i.e., two distinct digits),
            //  but with 1-based counting each of those two digits can be a 
            //  significant leading digit.
            //
            //            0-based     1-based
            //    ---------------------------- 
            //      0           0          --
            //      1           1           a 
            //      2          10           b 
            //      3          11          aa
            //      4         100          ab 
            //      5         101          ba
            //      6         110          bb
            //      7         111         aaa
            //      8        1000         aab 
            //      9        1001         aba
            //     10        1010         abb 
            //     11        1011         baa 
            //     12        1100         bab
            //     13        1101         bba 
            //     14        1110         bbb
            //     15        1111        aaaa
            //     16       10000        aaab
            // 
            //  For zero-based counting, adding a leading zero does not change
            //  the value of a number. Thus, the set of all N-digit numbers is 
            //  a proper subset of the set of (N+1)-digit numbers. Thus the set 
            //  of values that can be represented by N *or fewer* digits is the
            //  same as the number of combinations of exactly N digits, i.e., 
            //
            //      b ^ N
            //
            //  where b is the base of the numbering system. 
            //
            //  For one-based counting, there is no zero digit. Thus, the set 
            //  of N-digit numbers and the set of (N+1)-digit numbers are 
            //  disjoint sets. Thus, while the number of combinations of
            //  *exactly* N digits is still b ^ N, the maximum value that 
            //  can be represented by N *or fewer* digits is:
            //
            //  Max(N)
            //      where N = 1   :   b 
            //      where N > 1   :   (b ^ N) + Max(N - 1)
            // 
            if (oneBased) 
            {
                // Subtract 1 from 1-based numbers so we can use zero-based 
                // indexing. The formula for Max(N) given above should now be
                // thought of as a limit rather than a maximum.
                --number;
            } 

            Invariant.Assert(number >= 0, "expecting: number >= 0"); 
 
            char[] result;
 
            int b = numericSymbols.Length;
            if (number < b)
            {
                // Optimize common case of single-digit numbers. 
                result = new char[2]; // digit + suffix
                result[0] = numericSymbols[number]; 
                result[1] = NumberSuffix; 
            }
            else 
            {
                // Disjoint is 1 if and only if the set of numbers with N
                // digits and the set of numbers with (N+1) digits are
                // disjoint (see comment above). Otherwise it is zero. 
                int disjoint = oneBased ? 1 : 0;
 
                // Count digits. 
                int digits = 1;
                for (int limit = b, pow = b; number >= limit; ++digits) 
                {
                    pow *= b;
                    limit = pow + (limit * disjoint);
                } 

                // Build string in reverse order starting with suffix. 
                result = new char[digits + 1]; // digits + suffix 
                result[digits] = NumberSuffix;
                for (int i = digits - 1; i >= 0; --i) 
                {
                    result[i] = numericSymbols[number % b];
                    number = (number / b) - disjoint;
                } 
            }
 
            return result; 
        }
 
        /// 
        /// Convert 1-based number to a Roman numeric string
        /// followed by NumberSuffix character.
        ///  
        /// 
        /// Roman number is 1-based. The Roman numeric string is a series of symbols. Following 
        /// is the list of symbols and its value. 
        ///
        ///     Symbol      Value 
        ///         I           1
        ///         V           5
        ///         X          10
        ///         L          50 
        ///         C         100
        ///         D         500 
        ///         M        1000 
        ///
        /// The rule of Roman number prohibits the use of more than 3 consecutive identical symbol 
        /// but using subtraction of symbol standing for multiples of 10, so the value 4 is written
        /// as IV (5-1) rather than IIII.
        ///
        /// Due to the writing rule and the fact that the symbol represents not the numeral digit 
        /// but the value of the number. Roman number system cannot represent value larger than 3999.
        /// 
        /// See, http://www.ccsn.nevada.edu/math/ancient_systems.htm 
        ///
        /// However, there exists a more relaxing use of Roman numbers to represent values 4000 and 
        /// 4999 by using 4 consecutive M. The value 4999 is than written as 'MMMMCMXCIX'. Such use
        /// however is not widely accepted.
        ///
        /// See, http://www.guernsey.net/~sgibbs/roman.html 
        ///
        /// For values larger than 3999, an overscore is used on the symbol to indicate 1000 multiplication. 
        ///                                    ___ 
        /// So, value 7000 would be written as VII. This writing rule has a fair amount of disagreement
        /// since it is widely understood that it is not invented by the Romans and they rarely had a 
        /// need for large numbers during their time. Furthermore, accepting this writing rule just
        /// for the sake of being able to write larger number would create a new limitation of the values
        /// greater than 3,999,999. Unicode 4.0 does not encode these overscore symbols.
        /// 
        /// See, http://www.gwydir.demon.co.uk/jo/roman/number.htm
        ///      http://www.novaroma.org/via_romana/numbers.html 
        /// 
        /// Implementation-wise, IE adopts a general limitation of 3999 and simply convert the value
        /// into a regular numeric form. 
        ///
        /// We'll follow the mainstream and adopt the 3999 limit. The fallback would also do would IE does.
        ///
        ///  
        private static string ConvertNumberToRomanString(
            int number, 
            bool uppercase 
            )
        { 
            if (number > 3999)
            {
                // Roman numeric string not supported
                return number.ToString(CultureInfo.InvariantCulture); 
            }
 
            StringBuilder builder = new StringBuilder(); 

            AddRomanNumeric(builder, number / 1000, RomanNumerics[uppercase ? 1 : 0][0]); 
            number %= 1000;
            AddRomanNumeric(builder, number / 100, RomanNumerics[uppercase ? 1 : 0][1]);
            number %= 100;
            AddRomanNumeric(builder, number / 10, RomanNumerics[uppercase ? 1 : 0][2]); 
            number %= 10;
            AddRomanNumeric(builder, number, RomanNumerics[uppercase ? 1 : 0][3]); 
 
            builder.Append(NumberSuffix);
 
            return builder.ToString();
        }

 
        /// 
        /// Convert number 0 - 9 into Roman numeric 
        ///  
        /// string builder
        /// number to convert 
        /// Roman numeric char for one five and ten
        private static void AddRomanNumeric(
            StringBuilder builder,
            int number, 
            string oneFiveTen
            ) 
        { 
            Invariant.Assert(number >= 0 && number <= 9, "expecting: number >= 0 && number <= 9");
 
            if (number >= 1 && number <= 9)
            {
                if (number == 4 || number == 9)
                    builder.Append(oneFiveTen[0]); 

                if (number == 9) 
                { 
                    builder.Append(oneFiveTen[2]);
                } 
                else
                {
                    if (number >= 4)
                        builder.Append(oneFiveTen[1]); 

                    for (int i = number % 5; i > 0 && i < 4; i--) 
                        builder.Append(oneFiveTen[0]); 
                }
            } 
        }

        #endregion TextRange Helpers
 
        //------------------------------------------------------
        // 
        // ITextRange Properties 
        //
        //----------------------------------------------------- 

        #region ITextRange Properties

        //...................................................... 
        //
        //  Boundary Positions 
        // 
        //......................................................
 
        internal static ITextPointer GetStart(ITextRange thisRange)
        {
            NormalizeRange(thisRange);
 
            Invariant.Assert(thisRange._TextSegments != null && thisRange._TextSegments.Count > 0, "expecting nonempty _TextSegments array for Start position");
            return thisRange._TextSegments[0].Start; 
        } 

        internal static ITextPointer GetEnd(ITextRange thisRange) 
        {
            NormalizeRange(thisRange);

            Invariant.Assert(thisRange._TextSegments != null && thisRange._TextSegments.Count > 0, "expecting nonempty _TextSegments array for End position"); 
            return thisRange._TextSegments[thisRange._TextSegments.Count - 1].End;
        } 
 
        internal static bool GetIsEmpty(ITextRange thisRange)
        { 
            NormalizeRange(thisRange);

            // We assume that if a range is empty then it uses the same instance
            // of TextPointer for both Start and End positions. 
            Invariant.Assert(
                (thisRange._TextSegments.Count == 1 && 
                (object)thisRange._TextSegments[0].Start == (object)thisRange._TextSegments[0].End) 
                ==
                (thisRange.Start.CompareTo(thisRange.End) == 0), 
                "Range emptiness assumes using one instance of TextPointer for both start and end");

            return (thisRange._TextSegments.Count == 1 &&
                 (object)thisRange._TextSegments[0].Start == (object)thisRange._TextSegments[0].End); 
        }
 
        internal static List GetTextSegments(ITextRange thisRange) 
        {
            // NOTE: We cannot normalize thisRange because it will rebuild a collection 
            // of textSegments and will cause range move notification, leading to stack overflow.
            //

 
            return thisRange._TextSegments;
        } 
 
        //......................................................
        // 
        //  Content - rich and plain
        //
        //......................................................
 
        // Implementation of a getter for a ITextRange.Text property
        internal static string GetText(ITextRange thisRange) 
        { 
            NormalizeRange(thisRange);
 
            if (!thisRange.IsTableCellRange)
            {
                //
 

                // Extend the range from its start position to include initial list marker (if any). 
                // We do not do this auto-extension inside GetTextInternal to avoid undesirable 
                // "bulleting" effects on random plain text (say, in Run.TextProperty serialization).
                // THis is TextRange.get_Text-specific feature. 
                ITextPointer start = thisRange.Start;
                while (start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
                    !typeof(AnchoredBlock).IsAssignableFrom(start.ParentType))
                { 
                    // Any start tag is harmless except for AnchoredBlocks that would produce extra NewLines. So don't cross them.
                    start = start.GetNextContextPosition(LogicalDirection.Backward); 
                } 

                return TextRangeBase.GetTextInternal(start, thisRange.End); 
            }
            else
            {
                string text; 

                // 
                text = String.Empty; 
                for (int i = 0; i < thisRange._TextSegments.Count; i++)
                { 
                    TextSegment textSegment;

                    textSegment = thisRange._TextSegments[i];
 
                    text += TextRangeBase.GetTextInternal(textSegment.Start, textSegment.End);
                    // 
                } 

                return text; 
            }
        }

        // Implementation of a setter fot ITextRange.Text property 
        internal static void SetText(ITextRange thisRange, string textData)
        { 
            NormalizeRange(thisRange); 

            if (textData == null) 
            {
                throw new ArgumentNullException("textData");
            }
 
            ITextPointer explicitInsertPosition = null;
 
            TextRangeBase.BeginChange(thisRange); 
            try
            { 
                // Delete content covered by this range
                if (!thisRange.IsEmpty)
                {
                    if (thisRange.Start is TextPointer && 
                        ((TextPointer)thisRange.Start).Parent == ((TextPointer)thisRange.End).Parent &&
                        ((TextPointer)thisRange.Start).Parent is Run && 
                        textData.Length > 0) 
                    {
                        // When textrange start/end are parented by the same Run, we can optimize 
                        // and delete content without any checks.
                        //
                        // Note that NOT doing so has a serious side effect in this case.
                        // Low-level code in TextRangeEdit does not preserve an empty run 
                        // with no formatting properties after deletion.
                        // We dont want to loose the empty Run, 
                        // when we are just about to set the range text to non-empty string. 
                        // Otherwise, newly inserted text might have undesirable formatting properties
                        // applied due to an insertion position within an adjacent Run. 

                        if (thisRange.Start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text &&
                            thisRange.End.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
                        { 
                            // If we're deleting with surrounding text, make sure we insert later between the surrounding text.
                            // Because we will invalidate layout with the delete, it's possible that thisRange.Start 
                            // will normalize itself to a different character offset on the next reference. 
                            // This is because -- unfortunately -- when layout is valid we use ITextView.IsAtCaretUnitBoundary
                            // to normalize unicode offsets, but when layout is dirty we use a different code path 
                            // that ignores the current font and simply checks Unicode values for surrogates and
                            // combining marks.  See bug 1683515 for an example.
                            explicitInsertPosition = thisRange.Start;
                        } 

                        TextContainer textContainer = ((TextPointer)thisRange.Start).TextContainer; 
                        textContainer.DeleteContentInternal((TextPointer)thisRange.Start, (TextPointer)thisRange.End); 
                    }
                    else 
                    {
                        thisRange.Start.DeleteContentToPosition(thisRange.End);
                    }
 
                    if (thisRange.Start is TextPointer)
                    { 
                        TextRangeEdit.MergeFlowDirection((TextPointer)thisRange.Start); 
                    }
 
                    thisRange.Select(thisRange.Start, thisRange.Start);
                }

                // Insert text at end position 
                // Note that the non-emptiness check below is not an optimization:
                // In case of empty text the code block in it would change an empty range 
                // orientation, which is undesirable side effect. 
                // Also if the inserted text is empty we need to avoid ensuring insertion position,
                // which can create paragraphs etc. 
                if (textData.Length > 0)
                {
                    ITextPointer insertPosition = (explicitInsertPosition == null) ? thisRange.Start : explicitInsertPosition;
 
                    // Ensure last paragraph existence and prepare ends for the new selection
                    bool pastedFragmentEndsWithNewLine = textData.EndsWith("\n", StringComparison.Ordinal); 
 
                    // We are going to insert paragraph implicitly when the block content becomes totally empty.
                    // Store the fact that implicit paragraph was inserted to exclude ane extra paragraph break 
                    // from the end of pasted fragment
                    bool implicitParagraphInserted = insertPosition is TextPointer &&
                        TextSchema.IsValidChild(/*position*/insertPosition, /*childType*/typeof(Block)) &&
                        (insertPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.None || 
                        insertPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) &&
                        (insertPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.None || 
                        insertPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd); 

                    // Make sure that the range is positioned at insertion position 
                    if (insertPosition is TextPointer && explicitInsertPosition == null)
                    {
                        TextPointer insertionPosition = TextRangeEditTables.EnsureInsertionPosition((TextPointer)insertPosition);
                        thisRange.Select(insertionPosition, insertionPosition); 
                        insertPosition = thisRange.Start;
                    } 
                    Invariant.Assert(TextSchema.IsInTextContent(insertPosition), "range.Start is expected to be in text content"); 

                    ITextPointer newStart = insertPosition.GetFrozenPointer(LogicalDirection.Backward); 
                    ITextPointer newEnd = insertPosition.CreatePointer(LogicalDirection.Forward);

                    if ((newStart is TextPointer) && ((TextPointer)newStart).Paragraph != null)
                    { 
                        // Rich text - '\n' must be replaced by Paragraphs
                        TextPointer insertionPosition = (TextPointer)newStart.CreatePointer(LogicalDirection.Forward); 
                        string[] textParagraphs = textData.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None); 

                        int length = textParagraphs.Length; 
                        if (implicitParagraphInserted && pastedFragmentEndsWithNewLine)
                        {
                            length--;
                        } 

                        for (int i = 0; i < length; i++) 
                        { 
                            insertionPosition.InsertTextInRun(textParagraphs[i]);
                            if (i < length - 1) 
                            {
                                if (insertionPosition.HasNonMergeableInlineAncestor)
                                {
                                    // We cannot split a Hyperlink or other non-mergeable Inline element, 
                                    // so insert a space character instead (similar to embedded object).
                                    // Note that this means, SetText would loose 
                                    // paragraph break information in this case. 
                                    insertionPosition.InsertTextInRun(" ");
                                } 
                                else
                                {
                                    // insertionPosition gets repositioned to just inside
                                    // the following Paragraph. 
                                    insertionPosition = insertionPosition.InsertParagraphBreak();
                                } 
                                // Keep newEnd in sync with the paragraph break. 
                                // We can't rely on LogicalDirection alone for
                                // anything other than simple text inserts. 
                                newEnd = insertionPosition;
                            }
                        }
 
                        if (implicitParagraphInserted && pastedFragmentEndsWithNewLine)
                        { 
                            // We must include ending paragraph break into a resulting range 
                            newEnd = newEnd.GetNextInsertionPosition(LogicalDirection.Forward);
                            if (newEnd == null) 
                            {
                                newEnd = newStart.TextContainer.End; // set end of range to IsAfterLastParagraph position
                            }
 
                            // Note: As a result of this logic with implicitParagraphInserted && pastedFragmentEndsWithNewLine
                            // we have the following behavior: 
                            // Given that: 
                            //    range = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd);
                            // the statement: 
                            //    range.Text = "foo\r\n";
                            // has the effect of leaving flowDocument with this content (note: just one paragraph):
                            //    foo
                            // and range selecting the whole content: 
                            //    range.Text == "foo\r\n"
                            // 
                            // the statement: 
                            //    range.Text = "foo";
                            // results with the same content in flowDocument (one paragraph) 
                            // but the range is not extended beyond last paragraph end:
                            //    range.Text == "foo".
                        }
                    } 
                    else
                    { 
                        // Non-paragraph text - insert without '\n' conversion 
                        newStart.InsertTextInRun(textData);
                    } 

                    // Select the range
                    TextRangeBase.SelectPrivate(thisRange, newStart, newEnd, /*includeCellAtMovingPosition:*/false, /*markRangeChanged*/true);
                } 
            }
            finally 
            { 
                TextRangeBase.EndChange(thisRange);
            } 
        }

        internal static string GetXml(ITextRange thisRange)
        { 
            NormalizeRange(thisRange);
 
            // Create XmlWriter 
            StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
            XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter); 

            TextRangeSerialization.WriteXaml(xmlWriter, thisRange, /*useFlowDocumentAsRoot:*/false, /*wpfPayload:*/null);

            return stringWriter.ToString(); 
        }
 
        internal static bool CanSave(ITextRange thisRange, string dataFormat) 
        {
            NormalizeRange(thisRange); 

            bool canSave = (
                dataFormat == DataFormats.Text ||
                dataFormat == DataFormats.Xaml || 
                (SecurityHelper.CheckUnmanagedCodePermission() && (
                    dataFormat == DataFormats.XamlPackage || 
                    dataFormat == DataFormats.Rtf))); 

            return canSave; 
        }

        internal static bool CanLoad(ITextRange thisRange, string dataFormat)
        { 
            NormalizeRange(thisRange);
 
            bool canLoad = ( 
                dataFormat == DataFormats.Text ||
                dataFormat == DataFormats.Xaml || 
                (SecurityHelper.CheckUnmanagedCodePermission() && (
                    dataFormat == DataFormats.XamlPackage ||
                    dataFormat == DataFormats.Rtf)));
 
            return canLoad;
        } 
 
        internal static void Save(ITextRange thisRange, Stream stream, string dataFormat, bool preserveTextElements)
        { 
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            } 
            if (dataFormat == null)
            { 
                throw new ArgumentNullException("dataFormat"); 
            }
 
            NormalizeRange(thisRange);

            if (dataFormat == DataFormats.Text)
            { 
                string text = thisRange.Text;
                StreamWriter textStreamWriter = new StreamWriter(stream); 
                textStreamWriter.Write(text); 
                textStreamWriter.Flush();
            } 
            else if (dataFormat == DataFormats.Xaml)
            {
                StreamWriter xamlStreamWriter = new StreamWriter(stream);
                XmlTextWriter xamlXmlWriter = new XmlTextWriter(xamlStreamWriter); 
                // Passing null as wpfPayload parameter we request to produce
                // xaml without images - all of them will be repllaced by whitespaces. 
                TextRangeSerialization.WriteXaml(xamlXmlWriter, thisRange, /*useFlowDocumentAsRoot:*/false, /*wpfPayload:*/null, preserveTextElements); 
                xamlXmlWriter.Flush();
            } 
            else if (dataFormat == DataFormats.XamlPackage && SecurityHelper.CheckUnmanagedCodePermission())
            {
                // Non-null stream here means unconditional request to create a WPF package for the range
                // independently whether there are images in it or not. 
                WpfPayload.SaveRange(thisRange, ref stream, /*useFlowDocumentAsRoot:*/false);
            } 
            else if (dataFormat == DataFormats.Rtf && SecurityHelper.CheckUnmanagedCodePermission()) 
            {
                Stream wpfPayloadMemory = null; 
                // Passing null as a wpfPayloadStream we allow to not create wpf package
                // when it is not needed (there is no images in the range)
                string xamlText = WpfPayload.SaveRange(thisRange, ref wpfPayloadMemory, /*useFlowDocumentAsRoot:*/false);
                // Convert xaml to rtf text to set rtf data into data object. 
                string rtfText = TextEditorCopyPaste.ConvertXamlToRtf(xamlText, wpfPayloadMemory);
                StreamWriter rtfStreamWriter = new StreamWriter(stream); 
                rtfStreamWriter.Write(rtfText); 
                rtfStreamWriter.Flush();
            } 
            else
            {
                // Unsupported format - thows exception
                throw new ArgumentException(SR.Get(SRID.TextRange_UnsupportedDataFormat, dataFormat), "dataFormat"); 
            }
        } 
 
        internal static void Load(TextRange thisRange, Stream stream, string dataFormat)
        { 
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            } 
            if (dataFormat == null)
            { 
                throw new ArgumentNullException("dataFormat"); 
            }
 
            NormalizeRange(thisRange);

            // Reset the stream position to the beginning
            if (stream.CanSeek) 
            {
                stream.Seek(0, SeekOrigin.Begin); 
            } 

            if (dataFormat == DataFormats.Text) 
            {
                StreamReader textStreamReader = new StreamReader(stream);
                string text = textStreamReader.ReadToEnd();
                thisRange.Text = text; 
            }
            else if (dataFormat == DataFormats.Xaml) 
            { 
                StreamReader xamlStreamReader = new StreamReader(stream);
                string xamlText = xamlStreamReader.ReadToEnd(); 
                thisRange.Xml = xamlText;
            }
            else if (dataFormat == DataFormats.XamlPackage && SecurityHelper.CheckUnmanagedCodePermission())
            { 
                object element = WpfPayload.LoadElement(stream);
                if (!(element is Section) && !(element is Span)) 
                { 
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream");
                } 
                thisRange.SetXmlVirtual((TextElement)element);
            }
            else if (dataFormat == DataFormats.Rtf && SecurityHelper.CheckUnmanagedCodePermission())
            { 
                //
                StreamReader rtfStreamReader = new StreamReader(stream); 
                string rtfText = rtfStreamReader.ReadToEnd(); 
                MemoryStream memoryStream = TextEditorCopyPaste.ConvertRtfToXaml(rtfText);
                if (memoryStream == null) 
                {
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream");
                }
                TextElement textElement = WpfPayload.LoadElement(memoryStream) as TextElement; 
                if (!(textElement is Section) && !(textElement is Span))
                { 
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream"); 
                }
                thisRange.SetXmlVirtual(textElement); 
            }
            else
            {
                // Unsupported format - thows exception 
                throw new ArgumentException(SR.Get(SRID.TextRange_UnsupportedDataFormat, dataFormat), "dataFormat");
            } 
        } 

        // Ref count of open change blocks -- incremented/decremented 
        // around BeginChange/EndChange calls.
        internal static int GetChangeBlockLevel(ITextRange thisRange)
        {
            return thisRange._ChangeBlockLevel; 
        }
 
        //...................................................... 
        //
        //  Embedded Object Selection 
        //
        //......................................................

        internal static UIElement GetUIElementSelected(ITextRange range) 
        {
            ITextPointer start = range.Start.CreatePointer(); 
            TextPointerContext context = start.GetPointerContext(LogicalDirection.Forward); 
            while (context == TextPointerContext.ElementStart || context == TextPointerContext.ElementEnd)
            { 
                start.MoveToNextContextPosition(LogicalDirection.Forward);
                context = start.GetPointerContext(LogicalDirection.Forward);
            }
            if (context == TextPointerContext.EmbeddedElement) 
            {
                ITextPointer end = range.End.CreatePointer(); 
                context = end.GetPointerContext(LogicalDirection.Backward); 
                while (context == TextPointerContext.ElementStart || context == TextPointerContext.ElementEnd)
                { 
                    end.MoveToNextContextPosition(LogicalDirection.Backward);
                    context = end.GetPointerContext(LogicalDirection.Backward);
                }
                if (context == TextPointerContext.EmbeddedElement && start.GetOffsetToPosition(end) == 1) 
                {
                    return start.GetAdjacentElement(LogicalDirection.Forward) as UIElement; 
                } 
            }
            return null; 
        }

        //......................................................
        // 
        //  Table Selection Properties
        // 
        //...................................................... 

        internal static bool GetIsTableCellRange(ITextRange thisRange) 
        {
            NormalizeRange(thisRange);

            return thisRange._IsTableCellRange; 
        }
 
        #endregion ITextRange Properties 

        //------------------------------------------------------ 
        //
        //  Private Methods
        //
        //------------------------------------------------------ 

        #region Private Methods 
 
        // Worker for the BeginChange/BeginChangeNoUndo variants.
        // If description is null, no default undo unit is opened. 
        private static void BeginChangeWorker(ITextRange thisRange, string description)
        {
            ITextContainer textContainer = thisRange.Start.TextContainer;
 
            if (description != null && thisRange._ChangeBlockUndoRecord == null && thisRange._ChangeBlockLevel == 0)
            { 
                thisRange._ChangeBlockUndoRecord = new ChangeBlockUndoRecord(textContainer, description); 
            }
 
            Invariant.Assert(thisRange._ChangeBlockLevel > 0 || !thisRange._IsChanged, "_changed must be false on new move sequence");
            thisRange._ChangeBlockLevel++;

            if (description != null) 
            {
                textContainer.BeginChange(); 
            } 
            else
            { 
                textContainer.BeginChangeNoUndo();
            }
        }
 
        // Creates a one-segment collection from a pair of text positions
        // The segment normalization is done by the following rules: 
        // 1. If start and end pointers have equal positions, or became equal after normalization, 
        //    then the segment uses one instance of ITextPointer for both ends,
        //    which guarantees the segment emptiness in the subsequente editing 
        //    around it. This single pointer takes orientation from start parameter
        //    and normalized in that direction.
        // 2. In case when a segment is non-empty, two positions will be created
        //    and normalized inward - towards a segment contents; 
        //    Their gravities will be directed outward (start - Backward, end -  Forward),
        //    so that any insertion happend at segment edge position goes inside a segment 
        //    - the behavior we need to inserting stuff into TextRanges. 
        private static void CreateNormalizedTextSegment(ITextRange thisRange, ITextPointer start, ITextPointer end)
        { 
            ValidationHelper.VerifyPositionPair(start, end);

            // Normalize the segment
            if (start.CompareTo(end) == 0) 
            {
                // When the range is empty we must keep it that way during normalization 
                if (!IsAtNormalizedPosition(thisRange, start, start.LogicalDirection)) 
                {
                    start = GetNormalizedPosition(thisRange, start, start.LogicalDirection); 
                    end = start;
                }
            }
            else 
            {
                start = GetNormalizedPosition(thisRange, start, LogicalDirection.Forward); 
                if (!TextPointerBase.IsAfterLastParagraph(end)) 
                {
                    // NOTE: Position after the last paragraph is special. 
                    // Even though this position is not valid insertion position,
                    // we allow ranges to reach it. This is necessary to be able
                    // to "select all" content, and select the last paragraph.
                    end = GetNormalizedPosition(thisRange, end, LogicalDirection.Backward); 
                }
 
                // Collapse range in case of overlapped normalization result 
                if (start.CompareTo(end) >= 0)
                { 
                    // The range is effectuvely empty, so collapse it to single pointer instance
                    if (start.LogicalDirection == LogicalDirection.Backward)
                    {
                        // Choose a position normalized backward, 
                        start = end.GetFrozenPointer(LogicalDirection.Backward);
 
                        // NOTE that otherwise we will use start position, 
                        // which is oriented and normalizd Forward
                    } 
                    end = start;
                }
                else
                { 
                    // Handle Floater/Figure boundaries: non-empty ranges never cross them
                    if (start is TextPointer) 
                    { 
                        TextPointer adjustedStart = (TextPointer)start;
                        TextPointer adjustedEnd = (TextPointer)end; 
                        NormalizeAnchoredBlockBoundaries(ref adjustedStart, ref adjustedEnd);
                        start = adjustedStart;
                        end = adjustedEnd;
                    } 

                    Invariant.Assert(start.CompareTo(end) <= 0, "expecting start <= end"); 
 
                    // Normalize the segment, start and end may have become equal now.
                    if (start.CompareTo(end) == 0) 
                    {
                        // When the range is empty we must keep it that way during normalization
                        if (!IsAtNormalizedPosition(thisRange, start, start.LogicalDirection))
                        { 
                            start = GetNormalizedPosition(thisRange, start, start.LogicalDirection);
                            end = start; 
                        } 
                    }
 
                    //


 

 
 

 

                }
            }
 
            // Set this text segment as a selected range
            // 
            thisRange._TextSegments = new List(1); 
            thisRange._TextSegments.Add(new TextSegment(start, end));
            thisRange._IsTableCellRange = false; 
        }

        private static bool IsAtNormalizedPosition(ITextRange thisRange, ITextPointer position, LogicalDirection direction)
        { 
            bool isAtNormalizedPosition;
 
            if (thisRange.IgnoreTextUnitBoundaries) 
            {
                isAtNormalizedPosition = TextPointerBase.IsAtFormatNormalizedPosition(position, direction); 
            }
            else
            {
                isAtNormalizedPosition = TextPointerBase.IsAtInsertionPosition(position, direction); 
            }
 
            return isAtNormalizedPosition; 
        }
 
        private static ITextPointer GetNormalizedPosition(ITextRange thisRange, ITextPointer position, LogicalDirection direction)
        {
            ITextPointer normalizedPosition;
 
            if (thisRange.IgnoreTextUnitBoundaries)
            { 
                normalizedPosition = position.GetFormatNormalizedPosition(direction); 
            }
            else 
            {
                normalizedPosition = position.GetInsertionPosition(direction);
            }
 
            return normalizedPosition;
        } 
 
        // Helper for CreateNormalizedTextSegment
        // Checks whether start and end cross any Floater/Figure boundaries. 
        // If yes, normalizes the position(s) so that a non-empty range never crosses Floater/Figure boundaries.
        // Returns true if the range crosses AnchoredBlock boundary and was adjusted to not do so.
        // Returns false if it does not cross AnchoredBlock boundary and start/end stay where they were.
        internal static void NormalizeAnchoredBlockBoundaries(ref TextPointer start, ref TextPointer end) 
        {
            // Check AnchoredBlocks ancestors at start 
            TextElement outerAnchoredBlock = start.Parent as TextElement; 
            while (outerAnchoredBlock != null)
            { 
                // Find the next ancestor AncoredBlock
                while (outerAnchoredBlock != null && !typeof(AnchoredBlock).IsAssignableFrom(outerAnchoredBlock.GetType()))
                {
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement; 
                }
 
                if (outerAnchoredBlock != null) 
                {
                    // Anchored block found. Check whether the other position belongs to it. 
                    AnchoredBlock innerAnchoredBlock = null;
                    TextElement innerElement = end.Parent as TextElement;
                    while (innerElement != null && innerElement != outerAnchoredBlock)
                    { 
                        if (innerElement is AnchoredBlock)
                        { 
                            innerAnchoredBlock = (AnchoredBlock)innerElement; 
                        }
                        innerElement = innerElement.Parent as TextElement; 
                    }
                    if (innerElement == outerAnchoredBlock)
                    {
                        // Common ancestor AnchoredBlock is found. 
                        if (innerAnchoredBlock != null)
                        { 
                            end = innerAnchoredBlock.ElementEnd; 
                        }
                        return; 
                    }

                    // The AnchoredElement found at start position does not include end.
                    // Expand start to include the whole outerAnchoredBlock 
                    start = outerAnchoredBlock.ElementStart;
 
                    // and go to the next possible AnchoredBlock level 
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement;
                } 
            }

            // Check AnchoredBlocks ancestors at end
            outerAnchoredBlock = end.Parent as TextElement; 
            while (outerAnchoredBlock != null)
            { 
                // Find the next ancestor AncoredBlock 
                while (outerAnchoredBlock != null && !typeof(AnchoredBlock).IsAssignableFrom(outerAnchoredBlock.GetType()))
                { 
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement;
                }

                if (outerAnchoredBlock != null) 
                {
                    // Anchored block found. Check whether the other position belongs to it. 
                    AnchoredBlock innerAnchoredBlock = null; 
                    TextElement innerElement = start.Parent as TextElement;
                    while (innerElement != null && innerElement != outerAnchoredBlock) 
                    {
                        if (innerElement is AnchoredBlock)
                        {
                            innerAnchoredBlock = (AnchoredBlock)innerElement; 
                        }
                        innerElement = innerElement.Parent as TextElement; 
                    } 
                    if (innerElement == outerAnchoredBlock)
                    { 
                        // Common ancestor AnchoredBlock is found.
                        if (innerAnchoredBlock != null)
                        {
                            start = innerAnchoredBlock.ElementStart; 
                        }
                        return; 
                    } 

                    // The AnchoredElement found at end position does not include start. 
                    // Expand end to include the whole outerAnchoredBlock
                    end = outerAnchoredBlock.ElementEnd;

                    // and go to the next possible AnchoredBlock level 
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement;
                } 
            } 
        }
 
        // Method used in all public entry points to
        // ensure that thisRange is really normalized appropriately.
        private static void NormalizeRange(ITextRange thisRange)
        { 
            if (thisRange._ContentGeneration == thisRange._TextSegments[0].Start.TextContainer.Generation)
            { 
                // There were no content changes since range has been built, 
                // so no normalization needed.
                return; 
            }

            ITextPointer start = thisRange._TextSegments[0].Start;
            ITextPointer end = thisRange._TextSegments[thisRange._TextSegments.Count - 1].End; 

            if (thisRange._IsTableCellRange) 
            { 
                Invariant.Assert(thisRange._TextSegments[0].Start is TextPointer);
 
                // Table range - normalization may lead to full range rebuild
                TextRangeEditTables.IdentifyValidBoundaries(thisRange, out start, out end);

                // 

                SelectPrivate(thisRange, start, end, /*includeCellAtMovingPosition:*/false, /*markRangeChanged*/false); 
            } 
            else
            { 
                // Text range - normalization on both ends must be ensured
                bool needNormalization = false;

                if ((object)start == (object)end) 
                {
                    if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) 
                    { 
                        // Empty range can be normalized in any direction,
                        // so we use direction-neutral predicate 
                        needNormalization = true;
                    }
                }
                else if (start.CompareTo(end) == 0) 
                {
                    // The range which initially was not empty is not collapsed, 
                    // so we need to re-create it to use one TextPointer instance 
                    // instead of two.
                    // Note that gravity for the start pointer is Backward 
                    // in this case - so that resulting caret will be normalized/
                    // oriented backward.
                    needNormalization = true;
                } 
                else if (
                    !TextPointerBase.IsAtInsertionPosition(start, LogicalDirection.Forward) || 
                    !TextPointerBase.IsAtInsertionPosition(end, LogicalDirection.Backward)) 
                {
                    // If for a non-empty range, start/end are not at insertion position, 
                    // we need to normalize it.
                    needNormalization = true;
                }
 
                if (needNormalization)
                { 
                    CreateNormalizedTextSegment(thisRange, start, end); 
                }
            } 

            // Store content generation which will be needed in range normalization for
            // avoiding unnecessary work.
            thisRange._ContentGeneration = thisRange._TextSegments[0].Start.TextContainer.Generation; 
        }
 
        // Implementation body of range Select method 
        // When the range is being built for the very first time OR a table range is being rebuilt during normalization,
        // we do not fire any range notifications. 
        // NOTE: Because this method is called from NormalizeRange method we should totally avoid calling
        // any ITextRange methods involving normalization (such as Start/End)
        private static void SelectPrivate(ITextRange thisRange, ITextPointer position1, ITextPointer position2, bool includeCellAtMovingPosition, bool markRangeChanged)
        { 
            List textSegments;
            bool isTableCellRange; 
 
            Invariant.Assert(position1 != null, "null check: position1");
            Invariant.Assert(position2 != null, "null check: position2"); 

            if (position1 is TextPointer)
            {
                textSegments = TextRangeEditTables.BuildTableRange( 
                    /*anchorPosition:*/(TextPointer)position1,
                    /*movingPosition:*/(TextPointer)position2, 
                    includeCellAtMovingPosition, 
                    out isTableCellRange);
            } 
            else
            {
                // We have abstract TextContainer - never expect/build table range in this case
                Invariant.Assert(!thisRange._IsTableCellRange, "range is not expected to be in IsTableCellRange state - 1"); 
                textSegments = null;
                isTableCellRange = false; 
            } 

            if (textSegments != null) 
            {
                // Note also that table range is always in normalized condition - by construction
                //
                thisRange._TextSegments = textSegments; 
                thisRange._IsTableCellRange = isTableCellRange;
            } 
            else 
            {
                // Simple textsegment case 
                ITextPointer newStart = position1;
                ITextPointer newEnd = position2;

                // Swap pointers to order them properly. 
                // We do not sewap them when they are equal,
                // so that for empty range we are predictable about 
                // collapsed position orientation - taken from the first parameter 
                // (position1) (as per CreateNormalizedTextSegment semantics).
                if (position1.CompareTo(position2) > 0) 
                {
                    newStart = position2;
                    newEnd = position1;
                } 

                // Create new segment. Note that we do this unconditionally, 
                // not trying to bypass it if new positions look the same as currently set, 
                // because we need to ensure range normalization here.
                TextRangeBase.CreateNormalizedTextSegment(thisRange, newStart, newEnd); 
                Invariant.Assert(!thisRange._IsTableCellRange, "Expecting that the range is in text segment state now - must be set by CreateNOrmalizedTextSegment");

                // Before setting final range state we need to check if we are still in TextSegment condition
                if (position1 is TextPointer) 
                {
                    ITextPointer finalStart = thisRange._TextSegments[0].Start; 
                    ITextPointer finalEnd = thisRange._TextSegments[thisRange._TextSegments.Count - 1].End; 
                    if (finalStart.CompareTo(newStart) != 0 || finalEnd.CompareTo(newEnd) != 0)
                    { 
                        // This means that as a result of position normalization they have been moved
                        // so we must check what is the range state now.
                        // NOTE: We are in TextSegment state now, so anchor/moving ordering is not important,
                        // so we use thisRange.Start/End (normalized) whose order may be different from 
                        // position1/position2.
                        // Note: we use includeCellAtMovingPosition=false here because the movingPosition is taken from a constructed table range, not from input 
                        textSegments = TextRangeEditTables.BuildTableRange( 
                            /*anchorPosition:*/(TextPointer)finalStart,
                            /*movingPosition:*/(TextPointer)finalEnd, 
                            /*includeCellAtMovingPosition:*/false,
                            out isTableCellRange);
                        if (textSegments != null)
                        { 
                            thisRange._TextSegments = textSegments;
                            thisRange._IsTableCellRange = isTableCellRange; 
                        } 
                    }
                } 
            }

            // Store content generation which will be needed in range normalization for
            // avoiding unnecessary work. 
            thisRange._ContentGeneration = thisRange._TextSegments[0].Start.TextContainer.Generation;
 
            if (markRangeChanged) 
            {
                // 
                TextRangeBase.MarkRangeChanged(thisRange);
            }
        }
 
        /// 
        /// Raises the Changed event for this range. 
        /// 
        /// It must be called within a BeginChange/EndChange
        /// block. 
        /// 
        private static void MarkRangeChanged(ITextRange thisRange)
        {
            Invariant.Assert(thisRange._ChangeBlockLevel > 0, "changeBlockLevel > 0 is expected"); 
            thisRange._IsChanged = true;
        } 
 
        #endregion Private Methods
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK