TextEditorTyping.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 / TextEditorTyping.cs / 4 / TextEditorTyping.cs

                            //---------------------------------------------------------------------------- 
//
// File: TextEditorTyping.cs
//
// Copyright (C) Microsoft Corporation.  All rights reserved. 
//
// Description: Text editing service for controls. 
// 
//---------------------------------------------------------------------------
 
namespace System.Windows.Documents
{
    using MS.Internal;
    using System.Globalization; 
    using System.Threading;
    using System.ComponentModel; 
    using System.Text; 
    using System.Collections; // ArrayList
    using System.Runtime.InteropServices; 

    using System.Windows.Threading;
    using System.Windows.Input;
    using System.Windows.Controls; // ScrollChangedEventArgs 
    using System.Windows.Controls.Primitives;  // CharacterCasing, TextBoxBase
    using System.Windows.Media; 
    using System.Windows.Markup; 
    using System.Security;
    using System.Security.Permissions; 
    using System.Windows.Interop;
    using MS.Utility;
    using MS.Win32;
    using MS.Internal.Documents; 
    using MS.Internal.Commands; // CommandHelpers
 
    ///  
    /// Subcomponent of TextEditor class - Support for Typing
    ///  
    internal static class TextEditorTyping
    {
        //-----------------------------------------------------
        // 
        //  Class Internal Methods
        // 
        //----------------------------------------------------- 

        #region Class Internal Methods 

        /// 
        /// Registes all handlers needed for text editing control functioning.
        ///  
        /// 
        /// A type of control for which typing component is registered 
        ///  
        /// 
        /// If registerEventListeners is false, caller is responsible for calling OnXXXEvent methods on TextEditor from 
        /// UIElement and FrameworkElement virtual overrides (piggy backing on the
        /// UIElement/FrameworkElement class listeners).  If true, TextEditor will register
        /// its own class listener for events it needs.
        /// 
        /// This method will always register private command listeners.
        ///  
        internal static void _RegisterClassHandlers(Type controlType, bool registerEventListeners) 
        {
            if (registerEventListeners) 
            {
                EventManager.RegisterClassHandler(controlType, Keyboard.KeyDownEvent, new KeyEventHandler(OnKeyDown));
                EventManager.RegisterClassHandler(controlType, Keyboard.KeyUpEvent, new KeyEventHandler(OnKeyUp));
                EventManager.RegisterClassHandler(controlType, TextCompositionManager.TextInputEvent, new TextCompositionEventHandler(OnTextInput)); 
            }
 
            EventManager.RegisterClassHandler(controlType, Mouse.MouseMoveEvent, new MouseEventHandler(OnMouseMove), true /* handledEventsToo */); 
            EventManager.RegisterClassHandler(controlType, Mouse.MouseLeaveEvent, new MouseEventHandler(OnMouseLeave), true /* handledEventsToo */);
 
            CommandHelpers.RegisterCommandHandler(controlType, ApplicationCommands.CorrectionList   , new ExecutedRoutedEventHandler(OnCorrectionList)       , new CanExecuteRoutedEventHandler(OnQueryStatusCorrectionList)       , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyCorrectionList),   SR.Get(SRID.KeyCorrectionListDisplayString))         );
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.ToggleInsert         , new ExecutedRoutedEventHandler(OnToggleInsert)         , new CanExecuteRoutedEventHandler(OnQueryStatusNYI)                  , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyToggleInsert),     SR.Get(SRID.KeyToggleInsertDisplayString))           );
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.Delete               , new ExecutedRoutedEventHandler(OnDelete)               , new CanExecuteRoutedEventHandler(OnQueryStatusNYI)                  , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyDelete),           SR.Get(SRID.KeyDeleteDisplayString))                 );
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.Backspace            , new ExecutedRoutedEventHandler(OnBackspace)            , new CanExecuteRoutedEventHandler(OnQueryStatusNYI)                  , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyBackspace),        SR.Get(SRID.KeyBackspaceDisplayString)),   KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyShiftBackspace), SR.Get(SRID.KeyShiftBackspaceDisplayString)) ); 
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.DeleteNextWord       , new ExecutedRoutedEventHandler(OnDeleteNextWord)       , new CanExecuteRoutedEventHandler(OnQueryStatusNYI)                  , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyDeleteNextWord),   SR.Get(SRID.KeyDeleteNextWordDisplayString))         );
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.DeletePreviousWord   , new ExecutedRoutedEventHandler(OnDeletePreviousWord)   , new CanExecuteRoutedEventHandler(OnQueryStatusNYI)                  , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyDeletePreviousWord), SR.Get(SRID.KeyDeletePreviousWordDisplayString))   ); 
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.EnterParagraphBreak  , new ExecutedRoutedEventHandler(OnEnterBreak)           , new CanExecuteRoutedEventHandler(OnQueryStatusEnterBreak)           , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyEnterParagraphBreak), SR.Get(SRID.KeyEnterParagraphBreakDisplayString)) ); 
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.EnterLineBreak       , new ExecutedRoutedEventHandler(OnEnterBreak)           , new CanExecuteRoutedEventHandler(OnQueryStatusEnterBreak)           , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyEnterLineBreak),   SR.Get(SRID.KeyEnterLineBreakDisplayString))         );
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.TabForward           , new ExecutedRoutedEventHandler(OnTabForward)           , new CanExecuteRoutedEventHandler(OnQueryStatusTabForward)           , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyTabForward),       SR.Get(SRID.KeyTabForwardDisplayString))             ); 
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.TabBackward          , new ExecutedRoutedEventHandler(OnTabBackward)          , new CanExecuteRoutedEventHandler(OnQueryStatusTabBackward)          , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyTabBackward),      SR.Get(SRID.KeyTabBackwardDisplayString))            );
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.Space                , new ExecutedRoutedEventHandler(OnSpace)                , new CanExecuteRoutedEventHandler(OnQueryStatusNYI)                  , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeySpace),            SR.Get(SRID.KeySpaceDisplayString))                  );
            CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.ShiftSpace           , new ExecutedRoutedEventHandler(OnSpace)                , new CanExecuteRoutedEventHandler(OnQueryStatusNYI)                  , KeyGesture.CreateFromResourceStrings(SR.Get(SRID.KeyShiftSpace),       SR.Get(SRID.KeyShiftSpaceDisplayString))             );
        } 

        ///  
        /// Add the input language changed event handler and save it 
        /// into UIContext data slot.
        ///  
        internal static void _AddInputLanguageChangedEventHandler(TextEditor This)
        {
            TextEditorThreadLocalStore threadLocalStore;
 
            Invariant.Assert(This._dispatcher == null);
            This._dispatcher = Dispatcher.CurrentDispatcher; 
            Invariant.Assert(This._dispatcher != null); 

            threadLocalStore = TextEditor._ThreadLocalStore; 

            // Only add the input language changed event handler once that safe per UIContext
            if (threadLocalStore.InputLanguageChangeEventHandlerCount == 0)
            { 
                // Add input changed event handler into InputLanguageManager
                InputLanguageManager.Current.InputLanguageChanged += new InputLanguageEventHandler(OnInputLanguageChanged); 
 
                // Add the dispatcher shutdown finished event handler to remove InputLanguageChangedEventHandler
                // before dispose the dispatcher. 
                Dispatcher.CurrentDispatcher.ShutdownFinished += new EventHandler(OnDispatcherShutdownFinished);
            }

            threadLocalStore.InputLanguageChangeEventHandlerCount++; 
        }
 
        ///  
        /// Remove the input language changed event handler from UIContext data slot.
        ///  
        internal static void _RemoveInputLanguageChangedEventHandler(TextEditor This)
        {
            TextEditorThreadLocalStore threadLocalStore;
 
            threadLocalStore = TextEditor._ThreadLocalStore;
 
            // Decrease the input language changed event handler reference count 
            threadLocalStore.InputLanguageChangeEventHandlerCount--;
 
            // Remove the input language changed event handler when nobody reference it
            if (threadLocalStore.InputLanguageChangeEventHandlerCount == 0)
            {
                // Remove InputLanguageEventHandler 
                InputLanguageManager.Current.InputLanguageChanged -= new InputLanguageEventHandler(OnInputLanguageChanged);
 
                // Remove the dispatcher shutdown finished event handler 
                Dispatcher.CurrentDispatcher.ShutdownFinished -= new EventHandler(OnDispatcherShutdownFinished);
            } 
        }

        /// 
        /// Discards previous typing undo unit, to prevent 
        /// from merging it with the subsequent typing.
        ///  
        internal static void _BreakTypingSequence(TextEditor This) 
        {
            // Discard typing undo unit 
            This._typingUndoUnit = null;
        }

        // Handles any pending input events. 
        internal static void _FlushPendingInputItems(TextEditor This)
        { 
            TextEditorThreadLocalStore threadLocalStore; 

            if (This.TextView != null) 
            {
                This.TextView.ThrottleBackgroundTasksForUserInput();
            }
 
            threadLocalStore = TextEditor._ThreadLocalStore;
 
            if (threadLocalStore.PendingInputItems != null) 
            {
                try 
                {
                    for (int i = 0; i < threadLocalStore.PendingInputItems.Count; i++)
                    {
                        ((InputItem)threadLocalStore.PendingInputItems[i]).Do(); 

                        // After the first dequeue, clear the bit that tracks if 
                        // any events are handled after ctl+shift (change flow direction keyboard hotkey). 
                        threadLocalStore.PureControlShift = false;
                    } 
                }
                finally
                {
                    threadLocalStore.PendingInputItems.Clear(); 
                }
            } 
 
            // Clear the bit that tracks if any events are handled after
            // ctl+shift (change flow direction keyboard hotkey) one last 
            // time, in case the queue was empty.
            //
            // Because we only call this method in preparation for handling
            // a Command, we want this bit cleared. 
            threadLocalStore.PureControlShift = false;
        } 
 
        // Un-hides the mouse cursor.
        internal static void _ShowCursor() 
        {
            if (TextEditor._ThreadLocalStore.HideCursor)
            {
                TextEditor._ThreadLocalStore.HideCursor = false; 
                SafeNativeMethods.ShowCursor(true);
            } 
        } 

        // ................................................................ 
        //
        // Event Handlers
        //
        // ................................................................ 

        // KeyDownEvent handler - needed for handling FlowDirection commands on KeyUp 
        internal static void OnKeyDown(object sender, KeyEventArgs e) 
        {
            TextEditor This = TextEditor._GetTextEditor(sender); 

            if (This == null || !This._IsEnabled || (This.IsReadOnly && !This.IsReadOnlyCaretVisible) || !This._IsSourceInScope(e.OriginalSource))
            {
                return; 
            }
 
            // Ignore repeated events generated when the key is hold down for long time 
            if (e.IsRepeat)
            { 
                return;
            }

            // If UiScope has a ToolTip and it is open, any keyboard/mouse activity should close the tooltip. 
            This.CloseToolTip();
 
            TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; 

            // Clear a flag indicating that Shift key was pressed without any following key 
            // This flag is necessary for KeyUp(RightShift/LeftShift) processing.
            threadLocalStore.PureControlShift = false;

            // Shift+Ctrl combination must be executed only when it's "pure" - 
            // no mouse dragging/movement, no other key downs involved in a gesture.
            if (This.TextView != null && !This.UiScope.IsMouseCaptured) 
            { 
                if ((e.Key == Key.RightShift || e.Key == Key.LeftShift) && //
                    (e.KeyboardDevice.Modifiers & ModifierKeys.Control) != 0 && (e.KeyboardDevice.Modifiers & ModifierKeys.Alt) == 0) 
                {
                    threadLocalStore.PureControlShift = true; // will be cleared by any other key down
                }
                else if ((e.Key == Key.RightCtrl || e.Key == Key.LeftCtrl) && // 
                    (e.KeyboardDevice.Modifiers & ModifierKeys.Shift) != 0 && (e.KeyboardDevice.Modifiers & ModifierKeys.Alt) == 0)
                { 
                    threadLocalStore.PureControlShift = true; // will be cleared by any other key down 
                }
                else if (e.Key == Key.RightCtrl || e.Key == Key.LeftCtrl) 
                {
                    UpdateHyperlinkCursor(This);
                }
            } 

        } 
 
        // Handler for KeyUp events
        internal static void OnKeyUp(object sender, KeyEventArgs e) 
        {
            TextEditor This = TextEditor._GetTextEditor(sender);

            if (This == null || !This._IsEnabled || (This.IsReadOnly && !This.IsReadOnlyCaretVisible) || !This._IsSourceInScope(e.OriginalSource)) 
            {
                return; 
            } 

            // Delegate the work to specific handlers. 
            switch (e.Key)
            {
                case Key.RightShift:
                case Key.LeftShift: 
                    if (TextEditor._ThreadLocalStore.PureControlShift && (e.KeyboardDevice.Modifiers & ModifierKeys.Alt) == 0)
                    { 
                        TextEditorTyping.ScheduleInput(This, new KeyUpInputItem(This, e.Key, e.KeyboardDevice.Modifiers)); 
                    }
                    break; 

                case Key.LeftCtrl:
                case Key.RightCtrl:
                    UpdateHyperlinkCursor(This); 
                    break;
            } 
        } 

 
        // TextInputEvent handler.
        internal static void OnTextInput(object sender, TextCompositionEventArgs e)
        {
            TextEditor This = TextEditor._GetTextEditor(sender); 

            if (This == null || !This._IsEnabled || This.IsReadOnly || !This._IsSourceInScope(e.OriginalSource)) 
            { 
                return;
            } 

            FrameworkTextComposition composition = e.TextComposition as FrameworkTextComposition;

            // Ignore any event with an empty Text property. 
            // The public TextCompositionEventArgs ctor allows null Text values.
            // Also it's possible to have non-null ControlText or AltText with String.Empty Text values. 
            if (composition == null && 
                (e.Text == null || e.Text.Length == 0))
            { 
                return;
            }

            // Consider event handled 
            e.Handled = true;
 
            if (This.TextView != null) 
            {
                This.TextView.ThrottleBackgroundTasksForUserInput(); 
            }

            // If this event is our Cicero TextStore composition, we always handles through ITextStore::SetText.
            if (composition != null) 
            {
                if (composition.Owner == This.TextStore) 
                { 
                    This.TextStore.UpdateCompositionText(composition);
                } 
                else if (composition.Owner == This.ImmComposition)
                {
                    This.ImmComposition.UpdateCompositionText(composition);
                } 
            }
            else 
            { 
                // Input text (with springload formatting if any)
                // We'll delay the event handling, batching it up with other 
                // input if layout is too slow to keep up with the input stream.
                KeyboardDevice keyboard = e.Device as KeyboardDevice;
                TextEditorTyping.ScheduleInput(This, new TextInputItem(This, e.Text, /*isInsertKeyToggled:*/keyboard != null ? keyboard.IsKeyToggled(Key.Insert) : false));
 
            }
        } 
 
        #endregion Class Internal Methods
 
        //------------------------------------------------------
        //
        //  Private Methods
        // 
        //-----------------------------------------------------
 
        #region Private Methods 

        // ................................................................ 
        //
        // Command Handlers
        //
        // ................................................................ 

        ///  
        /// CorrectionList command QueryStatus handler 
        /// 
        private static void OnQueryStatusCorrectionList(object target, CanExecuteRoutedEventArgs args) 
        {
            TextEditor This = TextEditor._GetTextEditor(target);

            if (This == null) 
            {
                return; 
            } 

            if (This.TextStore != null) 
            {
                // Don't do actual reconversion, it just checks if the current selection is reconvertable.
                args.CanExecute = This.TextStore.QueryRangeOrReconvertSelection( /*fDoReconvert:*/ false);
            } 
            else
            { 
                // If there is no textstore, this command is not enabled. 
                args.CanExecute = false;
            } 
        }

        /// 
        /// CorrectionList command event handler. 
        /// 
        private static void OnCorrectionList(object target, ExecutedRoutedEventArgs args) 
        { 
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null)
            {
                return;
            } 

            if (This.TextStore != null) 
            { 
                This.TextStore.QueryRangeOrReconvertSelection( /*fDoReconvert:*/ true);
            } 
        }

        /// 
        /// ToggleInsert command handler 
        /// 
        ///  
        ///    Critical:This code toggles the state of the insert key and in doing so touches Cicero code which is critical (StartTrasitoryExtension and StopTransitoryExtension) 
        ///    TreatAsSafe: This code can do no harm and any state change can be reversed by hitting toggle again
        ///  
        [SecurityCritical,SecurityTreatAsSafe]
        private static void OnToggleInsert(object target, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target); 

            if (This == null || !This._IsEnabled || This.IsReadOnly) 
            { 
                return;
            } 

            This._OvertypeMode = !This._OvertypeMode;

            // Use Cicero's transitory extension for OverTyping. 
            if (TextServicesLoader.ServicesInstalled && (This.TextStore != null))
            { 
                TextServicesHost tsfHost = TextServicesHost.Current; 
                if (tsfHost != null)
                { 
                    if (This._OvertypeMode)
                    {
                        TextServicesHost.StartTransitoryExtension(This.TextStore);
                    } 
                    else
                    { 
                        TextServicesHost.StopTransitoryExtension(This.TextStore); 
                    }
                } 
            }
        }

        // ........................................................................... 
        //
        // Delete Characters 
        // 
        // ...........................................................................
 
        private static void OnDelete(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || This.IsReadOnly || !This._IsSourceInScope(args.Source))
            { 
                return; 
            }
 
            TextEditorTyping._FlushPendingInputItems(This);

            // Note, that Delete and Backspace keys behave differently.
            ((TextSelection)This.Selection).ClearSpringloadFormatting(); 

            // Forget previously suggested horizontal position 
            TextEditorSelection._ClearSuggestedX(This); 

            using (This.Selection.DeclareChangeBlock()) 
            {
                ITextPointer position = This.Selection.End;
                if (This.Selection.IsEmpty)
                { 
                    ITextPointer deletePosition = position.GetNextInsertionPosition(LogicalDirection.Forward);
 
                    if (deletePosition == null) 
                    {
                        // Nothing to delete. 
                        return;
                    }

                    if (TextPointerBase.IsAtRowEnd(deletePosition)) 
                    {
                        // Backspace and delete are a no-op at row end positions. 
                        return; 
                    }
 
                    if (position is TextPointer && !IsAtListItemStart(deletePosition) &&
                        HandleDeleteWhenStructuralBoundaryIsCrossed(This, (TextPointer)position, (TextPointer)deletePosition))
                    {
                        // We are crossing structural boundary and 
                        // selection was updated in HandleDeleteWhenStructuralBoundaryIsCrossed.
                        return; 
                    } 

                    // Selection is empty, extend selection forward to delete the following char. 
                    This.Selection.ExtendToNextInsertionPosition(LogicalDirection.Forward);
                }

                // Delete selected text. 
                This.Selection.Text = String.Empty;
            } 
        } 

        private static void OnBackspace(object sender, ExecutedRoutedEventArgs args) 
        {
            TextEditor This = TextEditor._GetTextEditor(sender);

            if (This == null || !This._IsEnabled || This.IsReadOnly || !This._IsSourceInScope(args.Source)) 
            {
                return; 
            } 

            TextEditorTyping._FlushPendingInputItems(This); 

            // Forget previously suggested horizontal position.
            TextEditorSelection._ClearSuggestedX(This);
 
            using (This.Selection.DeclareChangeBlock())
            { 
                ITextPointer position = This.Selection.Start; 

                // Note that this is different than the previous insertion position in backward direction, 
                // in case of combining characters and surrogates.
                ITextPointer backspacePosition = null;

                // In case when selection is empty we will need to expand 
                // it backward. Check first whether we are crossing
                // any structural boundary - to disable the operation 
                // in such case. 
                if (This.Selection.IsEmpty)
                { 
                    // Identify a case for special actions in the beginning of paragraphs or list items

                    if (This.AcceptsRichContent && IsAtListItemStart(position))
                    { 
                        // Remove a bullet from this list item.
                        // Note that doing anything more aggressive like unindenting 
                        // would make backspace very inconvenient for merging two same-level list items. 
                        TextRangeEditLists.ConvertListItemsToParagraphs((TextRange)This.Selection);
                    } 
                    else if (This.AcceptsRichContent &&
                             (IsAtListItemChildStart(position, false /* emptyChildOnly */) || IsAtIndentedParagraphOrBlockUIContainerStart(This.Selection.Start)))
                    {
                        // Unindent the list by one level. 
                        TextEditorLists.DecreaseIndentation(This);
                    } 
                    else 
                    {
                        // Find a preceding position. 
                        ITextPointer deletePosition = position.GetNextInsertionPosition(LogicalDirection.Backward);

                        if (deletePosition == null)
                        { 
                            // Nothing to delete.
                            ((TextSelection)This.Selection).ClearSpringloadFormatting(); 
                            return; 
                        }
 
                        if (TextPointerBase.IsAtRowEnd(deletePosition))
                        {
                            // Backspace and delete are a no-op at row end positions.
                            ((TextSelection)This.Selection).ClearSpringloadFormatting(); 
                            return;
                        } 
 
                        if (position is TextPointer &&
                            HandleDeleteWhenStructuralBoundaryIsCrossed(This, (TextPointer)position, (TextPointer)deletePosition)) 
                        {
                            // We are crossing structural boundary and
                            // selection was updated in HandleDeleteWhenStructuralBoundaryIsCrossed.
                            return; 
                        }
 
                        // Normalize the current position backward. 
                        position = position.GetFrozenPointer(LogicalDirection.Backward);
 
                        // If TextView is valid, we can get the backspace position from TextView and then
                        // delete the content from the backspace position to the current position.
                        // Otherwise, we move the selection to the previous insertion position then delete.
                        if (This.TextView != null && 
                            position.HasValidLayout &&
                            position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text) 
                        { 
                            // Get the backspace caret unit position from TextView that support surrogate
                            // and all internal characters 
                            backspacePosition = This.TextView.GetBackspaceCaretUnitPosition(position);
                            Invariant.Assert(backspacePosition != null);

                            // bug 1733868 
                            // backspacePosition should always be less than position.
                            // But backspacing before '\n' (no preceding '\r') exposes 
                            // this bug. 
                            if (backspacePosition.CompareTo(position) == 0)
                            { 
                                // As of 6/30/2006 we're too close to ship to fix
                                // this bug cleanly.  Ideally, we would stop referencing
                                // the position at the end-of-line (which mil text does not
                                // consider a valid position), and instead reference the start 
                                // of the next line (flipping the original position's gravity).
                                // 
                                // As a work-around, take the previous insertion position, 
                                // ignoring glyph level backspace positions.
                                // 
                                This.Selection.ExtendToNextInsertionPosition(LogicalDirection.Backward);
                                backspacePosition = null;
                            }
                            // If there is no text preceding the backspacePosition, extend to the next 
                            // insertion position to make sure we cleanup any empty Inlines left
                            // after the delete.  We don't want a non-empty selection if there is 
                            // bordering text, because we might normalize outside of a run of combining 
                            // marks otherwise.
                            else if (backspacePosition.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text) 
                            {
                                This.Selection.Select(This.Selection.End, backspacePosition);
                                backspacePosition = null;
                            } 
                        }
                        else 
                        { 
                            // Selection is empty, extend it backward to include the preceeding char.
                            This.Selection.ExtendToNextInsertionPosition(LogicalDirection.Backward); 
                        }
                    }
                }
 
                // Save current formatting properties for springload formatting before backspace
                // Note, that Delete and Backspace keys behave differently: it's by design. 
                if (This.AcceptsRichContent) 
                {
                    ((TextSelection)This.Selection).ClearSpringloadFormatting(); 
                    ((TextSelection)This.Selection).SpringloadCurrentFormatting();
                }

                // If backspace position is available from TextView, we can delete it directly 
                // without the normalization. Because we already normalized the backspace position.
                if (backspacePosition != null) 
                { 
                    Invariant.Assert(backspacePosition.CompareTo(position) < 0);
                    // Delete the content from the backspace to the current position 
                    backspacePosition.DeleteContentToPosition(position);
                }
                else
                { 
                    // Delete selected text
                    This.Selection.Text = String.Empty; 
                    position = This.Selection.Start; 
                }
 
                // Set the caret position with the Backward direction,
                // because we want to appear close to previous character.
                // However, we do not allow to stop at end of line.
                // We alow to stop next to space - to be consistent with typing behavior. 
                This.Selection.SetCaretToPosition(position, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/true);
            } 
        } 

        // Helper for OnDelete/OnBackspace, handles special case scenarios for delete when table or BlockUIContainer boundaries are crossed. 
        // Returns true if passed positions were in this category and appropriate editing action was taken for handling delete operation.
        // Otherwise, returns false.
        private static bool HandleDeleteWhenStructuralBoundaryIsCrossed(TextEditor This, TextPointer position, TextPointer deletePosition)
        { 
            if (!TextRangeEditTables.IsTableStructureCrossed(position, deletePosition) &&
                !IsBlockUIContainerBoundaryCrossed(position, deletePosition) && 
                !TextPointerBase.IsAtRowEnd(position)) 
            {
                return false; 
            }

            LogicalDirection directionOfDelete = position.CompareTo(deletePosition) < 0 ? LogicalDirection.Forward : LogicalDirection.Backward;
 
            Block paragraphOrBlockUIContainerToDelete = position.ParagraphOrBlockUIContainer;
 
            // Check if an empty paragraph or BlockUIContainer needs to be deleted. 
            if (paragraphOrBlockUIContainerToDelete != null)
            { 
                if (directionOfDelete == LogicalDirection.Forward)
                {
                    //
 

 
 
                    if (paragraphOrBlockUIContainerToDelete.NextBlock != null &&
                        paragraphOrBlockUIContainerToDelete is Paragraph && Paragraph.HasNoTextContent((Paragraph)paragraphOrBlockUIContainerToDelete) || // empty paragraph 
                        paragraphOrBlockUIContainerToDelete is BlockUIContainer && paragraphOrBlockUIContainerToDelete.IsEmpty) // empty BlockUIContainer
                    {
                        paragraphOrBlockUIContainerToDelete.RepositionWithContent(null);
                    } 
                }
                else 
                { 
                    if (paragraphOrBlockUIContainerToDelete.PreviousBlock != null &&
                        paragraphOrBlockUIContainerToDelete is Paragraph && Paragraph.HasNoTextContent((Paragraph)paragraphOrBlockUIContainerToDelete) || // empty paragraph 
                        paragraphOrBlockUIContainerToDelete is BlockUIContainer && paragraphOrBlockUIContainerToDelete.IsEmpty) // empty BlockUIContainer
                    {
                        paragraphOrBlockUIContainerToDelete.RepositionWithContent(null);
                    } 
                }
            } 
 
            // Set caret position.
            This.Selection.SetCaretToPosition(deletePosition, directionOfDelete, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/true); 

            if (directionOfDelete == LogicalDirection.Backward)
            {
                // Clear springload formatting in case of backspace 
                ((TextSelection)This.Selection).ClearSpringloadFormatting();
            } 
 
            return true;
        } 

        // Tests if the position is at the beginning of indented paragraph -
        // to allow Backspace to decrease indentation
        private static bool IsAtIndentedParagraphOrBlockUIContainerStart(ITextPointer position) 
        {
            if ((position is TextPointer) && TextPointerBase.IsAtParagraphOrBlockUIContainerStart(position)) 
            { 
                Block paragraphOrBlockUIContainer = ((TextPointer)position).ParagraphOrBlockUIContainer;
                if (paragraphOrBlockUIContainer != null) 
                {
                    FlowDirection flowDirection = paragraphOrBlockUIContainer.FlowDirection;
                    Thickness margin = paragraphOrBlockUIContainer.Margin;
 
                    return
                        flowDirection == FlowDirection.LeftToRight && margin.Left > 0 || 
                        flowDirection == FlowDirection.RightToLeft && margin.Right > 0 || 
                        (paragraphOrBlockUIContainer is Paragraph && ((Paragraph)paragraphOrBlockUIContainer).TextIndent > 0);
                } 
            }

            return false;
        } 

        // Tests if the position is at the beginning of some list item - 
        // to allow Backspace to delete the bullet. 
        private static bool IsAtListItemStart(ITextPointer position)
        { 
            // Check for empty ListItem case
            if (typeof(ListItem).IsAssignableFrom(position.ParentType) &&
                position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
                position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) 
            {
                return true; 
            } 

            while (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) 
            {
                Type parentType = position.ParentType;
                if (TextSchema.IsBlock(parentType))
                { 
                    if (TextSchema.IsParagraphOrBlockUIContainer(parentType))
                    { 
                        position = position.GetNextContextPosition(LogicalDirection.Backward); 
                        if (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
                            typeof(ListItem).IsAssignableFrom(position.ParentType)) 
                        {
                            return true;
                        }
                    } 
                    return false;
                } 
                position = position.GetNextContextPosition(LogicalDirection.Backward); 
            }
            return false; 
        }

        // Tests if a position is at the start of a Block
        // within a ListItem. 
        //
        // position must be normalized at an insertion point. 
        private static bool IsAtListItemChildStart(ITextPointer position, bool emptyChildOnly) 
        {
            if (position.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.ElementStart) 
            {
                return false;
            }
 
            if (emptyChildOnly &&
                position.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.ElementEnd) 
            { 
                return false;
            } 

            ITextPointer navigator = position.CreatePointer();

            // Cross inline opening tags. 
            while (navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
                   typeof(Inline).IsAssignableFrom(navigator.ParentType)) 
            { 
                navigator.MoveToElementEdge(ElementEdge.BeforeStart);
            } 

            // Check if navigator is at the start of a block.
            if (!(navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
                TextSchema.IsParagraphOrBlockUIContainer(navigator.ParentType))) 
            {
                return false; 
            } 

            // Move just past the block. 
            navigator.MoveToElementEdge(ElementEdge.BeforeStart);
            return typeof(ListItem).IsAssignableFrom(navigator.ParentType);
        }
 
        // Tests if position1 and position2 cross a BlockUIContainer boundary.
        private static bool IsBlockUIContainerBoundaryCrossed(TextPointer position1, TextPointer position2) 
        { 
            return
                (position1.Parent is BlockUIContainer || position2.Parent is BlockUIContainer) && 
                position1.Parent != position2.Parent;
        }

        // ........................................................................... 
        //
        // Delete Words 
        // 
        // ...........................................................................
 
        private static void OnDeleteNextWord(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || This.IsReadOnly)
            { 
                return; 
            }
 
            if (This.Selection.IsTableCellRange)
            {
                return;
            } 

            TextEditorTyping._FlushPendingInputItems(This); 
 
            ITextPointer wordBoundary = This.Selection.End.CreatePointer();
 
            // When selection is not empty the command deletes selected content
            // without extending it to the word bopundary. For empty selection
            // the command deletes a content from caret position to
            // nearest word boundary in a given direction 
            if (This.Selection.IsEmpty)
            { 
                TextPointerBase.MoveToNextWordBoundary(wordBoundary, LogicalDirection.Forward); 
            }
 
            if (TextRangeEditTables.IsTableStructureCrossed(This.Selection.Start, wordBoundary))
            {
                return;
            } 

            ITextRange textRange = new TextRange(This.Selection.Start, wordBoundary); 
 
            // When a range is TableCellRange we do not want to make deletions
            if (textRange.IsTableCellRange) 
            {
                return;
            }
 
            if (!textRange.IsEmpty)
            { 
                using (This.Selection.DeclareChangeBlock()) 
                {
                    // Note asymetry with Backspace: we do not load springload formatting here 
                    if (This.AcceptsRichContent)
                    {
                        ((TextSelection)This.Selection).ClearSpringloadFormatting();
                    } 

                    This.Selection.Select(textRange.Start, textRange.End); 
 
                    // Delete selected text
                    This.Selection.Text = String.Empty; 
                }
            }
        }
 
        private static void OnDeletePreviousWord(object sender, ExecutedRoutedEventArgs args)
        { 
            TextEditor This = TextEditor._GetTextEditor(sender); 

            if (This == null || !This._IsEnabled || This.IsReadOnly) 
            {
                return;
            }
 
            if (This.Selection.IsTableCellRange)
            { 
                // 
                return;
            } 

            TextEditorTyping._FlushPendingInputItems(This);

            ITextPointer wordBoundary = This.Selection.Start.CreatePointer(); 

            // When selection is not empty the command deletes selected content 
            // without extending it to the word bopundary. For empty selection 
            // the command deletes a content from caret position to
            // nearest word boundary in a given direction 
            if (This.Selection.IsEmpty)
            {
                TextPointerBase.MoveToNextWordBoundary(wordBoundary, LogicalDirection.Backward);
            } 

            // When the movement to word boundary crosses table structure, ignore the command 
            if (TextRangeEditTables.IsTableStructureCrossed(wordBoundary, This.Selection.Start)) 
            {
                return; 
            }

            // Build a range from a start of a word preceding start of selection, ending at the end of whole selection
            // This range is supposed to be deleted by the operation. 
            ITextRange textRange = new TextRange(wordBoundary, This.Selection.End);
 
            // When a range is TableCellRange we do not want to make deletions 
            if (textRange.IsTableCellRange)
            { 
                return;
            }

            if (!textRange.IsEmpty) 
            {
                using (This.Selection.DeclareChangeBlock()) 
                { 
                    // Note asymetry with Backspace: we DO load springload formatting here
                    if (This.AcceptsRichContent) 
                    {
                        ((TextSelection)This.Selection).ClearSpringloadFormatting();
                        This.Selection.Select(textRange.Start, textRange.End);
                        ((TextSelection)This.Selection).SpringloadCurrentFormatting(); 
                    }
                    else 
                    { 
                        This.Selection.Select(textRange.Start, textRange.End);
                    } 

                    // Delete selected text
                    This.Selection.Text = String.Empty;
                } 
            }
        } 
 
        // ...........................................................................
        // 
        // Enter Breaks
        //
        // ...........................................................................
 
        /// 
        /// EnterParagraphBreak/EnterLineBreak command QueryStatus handler 
        ///  
        private static void OnQueryStatusEnterBreak(object sender, CanExecuteRoutedEventArgs args)
        { 
            TextEditor This = TextEditor._GetTextEditor(sender);

            if (This == null || !This._IsEnabled || This.IsReadOnly)
            { 
                args.ContinueRouting = true;
                return; 
            } 

            if (This.Selection.IsTableCellRange || !This.AcceptsReturn) 
            {
                args.ContinueRouting = true;
                return;
            } 

            args.CanExecute = true; 
        } 

        // EnterParagraphBreak/EnterLineBreak command handler 
        private static void OnEnterBreak(object sender, ExecutedRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || This.IsReadOnly)
            { 
                return; 
            }
 
            if (This.Selection.IsTableCellRange || !This.AcceptsReturn || !This.UiScope.IsKeyboardFocused)
            {
                return;
            } 

            TextEditorTyping._FlushPendingInputItems(This); 
 
            // We do not merge Enter typing with other typing - for better undo structuring
            using (This.Selection.DeclareChangeBlock()) 
            {
                // Flag to indicate if selection was changed. It may be unaffected in following cases:
                // 1. In plain text case, Environment.NewLine can not fit in MaxLength
                // 2. In rich text case, we cannot split a hyperlink ancestor to insert a paragraph break 
                bool wasSelectionChanged;
 
                if (This.AcceptsRichContent && This.Selection.Start is TextPointer) 
                {
                    // Paragraph insertion for the case of rich text 
                    wasSelectionChanged = HandleEnterBreakForRichText(This, args.Command);
                }
                else
                { 
                    // Newline insertion for plain text
                    wasSelectionChanged = HandleEnterBreakForPlainText(This); 
                } 

                // Update caret and clear SuggestedX only when selection has changed. 
                if (wasSelectionChanged)
                {
                    // Position the caret.
                    This.Selection.SetCaretToPosition(This.Selection.End, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); 

                    // Forget previously suggested horizontal position 
                    TextEditorSelection._ClearSuggestedX(This); 
                }
            } 
        }

        // Helper for OnEnterBreak for rich text case
        private static bool HandleEnterBreakForRichText(TextEditor This, ICommand command) 
        {
            bool wasSelectionChanged = true; 
 
            // Save current inline settings to continue on the next paragraph
            ((TextSelection)This.Selection).SpringloadCurrentFormatting(); 

            if (!This.Selection.IsEmpty)
            {
                // Delete selected content 
                This.Selection.Text = String.Empty;
            } 
 
            if (HandleEnterBreakWhenStructuralBoundaryIsCrossed(This, command))
            { 
                // We are crossing structural boundary and
                // selection was updated if HandleEnterBreakWhenStructuralBoundaryIsCrossed returned true
            }
            else 
            {
                TextPointer newEnd = ((TextSelection)This.Selection).End; 
 
                if (command == EditingCommands.EnterParagraphBreak)
                { 
                    if (newEnd.HasNonMergeableInlineAncestor && !TextPointerBase.IsPositionAtNonMergeableInlineBoundary(newEnd))
                    {
                        // Selection end is in the middle of a hyperlink element, enter is a no-op.
                        wasSelectionChanged = false; 
                    }
                    else 
                    { 
                        newEnd = TextRangeEdit.InsertParagraphBreak(newEnd, /*moveIntoSecondParagraph*/true);
                    } 
                }
                else if (command == EditingCommands.EnterLineBreak)
                {
                    newEnd = newEnd.InsertLineBreak(); 
                }
 
                if (wasSelectionChanged) 
                {
                    This.Selection.Select(newEnd, newEnd); 
                }
            }

            return wasSelectionChanged; 
        }
 
        // Helper for OnEnterBreak for plain text case 
        private static bool HandleEnterBreakForPlainText(TextEditor This)
        { 
            bool wasSelectionChanged = true;

            // Filter Environment.NewLine based on TextBox.MaxLength
            string filteredText = This._FilterText(Environment.NewLine, This.Selection); 

            if (filteredText != String.Empty) 
            { 
                This.Selection.Text = Environment.NewLine;
            } 
            else
            {
                // Do not update selection if Environment.NewLine can not fit in.
                wasSelectionChanged = false; 
            }
 
            return wasSelectionChanged; 
        }
 
        // Helper for rich text OnEnterBreak case, handles special cases when a
        // structural boundary such as listitem, table, blockuicontainer is crossed.
        private static bool HandleEnterBreakWhenStructuralBoundaryIsCrossed(TextEditor This, ICommand command)
        { 
            Invariant.Assert(This.Selection.Start is TextPointer);
            TextPointer position = (TextPointer)This.Selection.Start; 
 
            bool structuralBoundaryIsCrossed = true;
 
            if (TextPointerBase.IsAtRowEnd(position))
            {
                // For both ParagraphBreak and LineBreak commands, insert a new row after the current one
                TextRange range = ((TextSelection)This.Selection).InsertRows(+1); 
                This.Selection.SetCaretToPosition(range.Start, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false);
            } 
            else if (This.Selection.IsEmpty && 
                     (TextPointerBase.IsInEmptyListItem(position) || IsAtListItemChildStart(position, true /* emptyChildOnly */)) &&
                     command == EditingCommands.EnterParagraphBreak) 
            {
                // Unindent the list by one level.
                TextEditorLists.DecreaseIndentation(This);
            } 
            else if (TextPointerBase.IsBeforeFirstTable(position) ||
                TextPointerBase.IsAtBlockUIContainerStart(position)) 
            { 
                // Calling EnsureInsertionPosition has the effect of inserting a paragraph BEFORE the table or BlockUIContainer/Table.
                // In this case, we do not want to move selection end to the paragraph just created. 

                TextRangeEditTables.EnsureInsertionPosition(position);
            }
            else if (TextPointerBase.IsAtBlockUIContainerEnd(position)) 
            {
                // Calling EnsureInsertionPosition has the effect of inserting a paragraph AFTER the BlockUIContainer. 
                // Update selection end to position in the following paragraph. 

                TextPointer newEnd = TextRangeEditTables.EnsureInsertionPosition(position); 
                This.Selection.Select(newEnd, newEnd);
            }
            else
            { 
                structuralBoundaryIsCrossed = false;
            } 
 
            return structuralBoundaryIsCrossed;
        } 

        // ...........................................................................
        //
        // Flow Direction 
        //
        // ........................................................................... 
 
        /// 
        /// LeftToRightFlowDirection command event handler. 
        /// 
        private static void OnFlowDirectionCommand(TextEditor This, Key key)
        {
            // 

            using (This.Selection.DeclareChangeBlock()) 
            { 
                if (key == Key.LeftShift)
                { 
                    if (This.AcceptsRichContent && (This.Selection is TextSelection))
                    {
                        // NOTE: We do not call OnApplyProperty to avoid recursion for FlushPendingInput
                        ((TextSelection)This.Selection).ApplyPropertyValue(FlowDocument.FlowDirectionProperty, FlowDirection.LeftToRight, /*applyToParagraphs*/true); 
                    }
                    else 
                    { 
                        Invariant.Assert(This.UiScope != null);
                        UIElementPropertyUndoUnit.Add(This.TextContainer, This.UiScope, FrameworkElement.FlowDirectionProperty, FlowDirection.LeftToRight); 
                        This.UiScope.SetValue(FrameworkElement.FlowDirectionProperty, FlowDirection.LeftToRight);
                    }
                }
                else 
                {
                    Invariant.Assert(key == Key.RightShift); 
 
                    if (This.AcceptsRichContent && (This.Selection is TextSelection))
                    { 
                        // NOTE: We do not call OnApplyProperty to avoid recursion for FlushPendingInput
                        ((TextSelection)This.Selection).ApplyPropertyValue(FlowDocument.FlowDirectionProperty, FlowDirection.RightToLeft, /*applyToParagraphs*/true);
                    }
                    else 
                    {
                        Invariant.Assert(This.UiScope != null); 
                        UIElementPropertyUndoUnit.Add(This.TextContainer, This.UiScope, FrameworkElement.FlowDirectionProperty, FlowDirection.RightToLeft); 
                        This.UiScope.SetValue(FrameworkElement.FlowDirectionProperty, FlowDirection.RightToLeft);
                    } 
                }
                ((TextSelection)This.Selection).UpdateCaretState(CaretScrollMethod.Simple);
            }
        } 

        // ........................................................................... 
        // 
        // In some controls, Space and Shift+Space keys are mapped to
        // scroll down and scroll up commands respectively. 
        // In TextEditor, we handle them as text input.
        // Using the command system allows controls to override the existing default behavior.
        // ...........................................................................
 
        // Space, Shift+Space handler
        private static void OnSpace(object sender, ExecutedRoutedEventArgs e) 
        { 
            TextEditor This = TextEditor._GetTextEditor(sender);
 
            if (This == null || !This._IsEnabled || This.IsReadOnly || !This._IsSourceInScope(e.OriginalSource))
            {
                return;
            } 

            // If this event is our Cicero TextStore composition, we always handles through ITextStore::SetText. 
            if (This.TextStore != null && This.TextStore.IsComposing) 
            {
                return; 
            }

            if (This.ImmComposition != null && This.ImmComposition.IsComposition)
            { 
                return;
            } 
 
            // Consider event handled
            e.Handled = true; 

            if (This.TextView != null)
            {
                This.TextView.ThrottleBackgroundTasksForUserInput(); 
            }
 
            ScheduleInput(This, new TextInputItem(This, " ", /*isInsertKeyToggled:*/!This._OvertypeMode)); 
        }
 
        // ...........................................................................
        //
        // Tab and Back-Tab
        // 
        // ...........................................................................
 
        ///  
        /// ForwardTabStop command QueryStatus handler
        ///  
        private static void OnQueryStatusTabForward(object sender, CanExecuteRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(sender);
            if (This != null && This.AcceptsTab) 
            {
                args.CanExecute = true; 
            } 
            else
            { 
                args.ContinueRouting = true;
            }
        }
 
        /// 
        /// BackwardTabStop command QueryStatus handler 
        ///  
        private static void OnQueryStatusTabBackward(object sender, CanExecuteRoutedEventArgs args)
        { 
            TextEditor This = TextEditor._GetTextEditor(sender);
            if (This != null && This.AcceptsTab)
            {
                args.CanExecute = true; 
            }
            else 
            { 
                args.ContinueRouting = true;
            } 
        }

        // Tab handler.
        private static void OnTabForward(object sender, ExecutedRoutedEventArgs args) 
        {
            TextEditor This = TextEditor._GetTextEditor(sender); 
 
            if (This == null || !This._IsEnabled || This.IsReadOnly)
            { 
                return;
            }

            TextEditorTyping._FlushPendingInputItems(This); 

            if (HandleTabInTables(This, LogicalDirection.Forward)) 
            { 
                // All done on table level.
                return; 
            }

            if (This.AcceptsRichContent && (!This.Selection.IsEmpty || TextPointerBase.IsAtParagraphOrBlockUIContainerStart(This.Selection.Start)) &&
                EditingCommands.IncreaseIndentation.CanExecute(null, (IInputElement)sender)) 
            {
                // In RichTextBox Tab/Shift+Tab keys work as paragraph/list indentation 
                EditingCommands.IncreaseIndentation.Execute(null, (IInputElement)sender); 
            }
            else 
            {
                // In plain text we treat tab as a characters always
                DoTextInput(This, "\t", /*isInsertKeyToggled:*/!This._OvertypeMode, /*acceptControlCharacters:*/true);
            } 
        }
 
        // Shift+Tab handler. 
        private static void OnTabBackward(object sender, ExecutedRoutedEventArgs args)
        { 
            TextEditor This = TextEditor._GetTextEditor(sender);

            if (This == null || !This._IsEnabled || This.IsReadOnly)
            { 
                return;
            } 
 
            //
            TextEditorTyping._FlushPendingInputItems(This); 

            if (HandleTabInTables(This, LogicalDirection.Backward))
            {
                // All done on table level. 
                return;
            } 
 
            if (This.AcceptsRichContent && (!This.Selection.IsEmpty || TextPointerBase.IsAtParagraphOrBlockUIContainerStart(This.Selection.Start)) &&
                EditingCommands.DecreaseIndentation.CanExecute(null, (IInputElement)sender)) 
            {
                // In RichTextBox Tab/Shift+Tab keys work as paragraph/list indentation
                EditingCommands.DecreaseIndentation.Execute(null, (IInputElement)sender);
            } 
            else
            { 
                // In plain text we treat tab as a characters always 
                DoTextInput(This, "\t", /*isInsertKeyToggled:*/!This._OvertypeMode, /*acceptControlCharacters:*/true);
            } 
        }

        // Command handler for Tab and ShiftTab - moves caret between table cells
        // if the selection is within a table. Otherwise does nothing and returns false. 
        private static bool HandleTabInTables(TextEditor This, LogicalDirection direction)
        { 
            if (!This.AcceptsRichContent) 
            {
                return false; 
            }

            if (This.Selection.IsTableCellRange)
            { 
                // When table cell range is selected, Tab simply collapses
                // a selection to a content of a first cell 
                This.Selection.SetCaretToPosition(This.Selection.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); 
                return true;
            } 

            if (This.Selection.IsEmpty && TextPointerBase.IsAtRowEnd(This.Selection.End))
            {
                // From the end of row we go to the first cell of a next row 
                TableCell cell = null;
                TableRow row = ((TextPointer)This.Selection.End).Parent as TableRow; 
                Invariant.Assert(row != null); 
                TableRowGroup body = row.RowGroup;
                int rowIndex = body.Rows.IndexOf(row); 

                if (direction == LogicalDirection.Forward)
                {
                    if (rowIndex + 1 < body.Rows.Count) 
                    {
                        cell = body.Rows[rowIndex + 1].Cells[0]; 
                    } 
                }
                else 
                {
                    if (rowIndex > 0)
                    {
                        cell = body.Rows[rowIndex - 1].Cells[body.Rows[rowIndex - 1].Cells.Count - 1]; 
                    }
                } 
 
                if (cell != null)
                { 
                    This.Selection.Select(cell.ContentStart, cell.ContentEnd);
                }
                return true;
            } 

            // Check if selection is within a table 
            TextElement parent = ((TextPointer)This.Selection.Start).Parent as TextElement; 
            while (parent != null && !(parent is TableCell))
            { 
                parent = parent.Parent as TextElement;
            }
            if (parent is TableCell)
            { 
                TableCell cell = (TableCell)parent;
                TableRow row = cell.Row; 
                TableRowGroup body = row.RowGroup; 

                int cellIndex = row.Cells.IndexOf(cell); 
                int rowIndex = body.Rows.IndexOf(row);

                if (direction == LogicalDirection.Forward)
                { 
                    if (cellIndex + 1 < row.Cells.Count)
                    { 
                        cell = row.Cells[cellIndex + 1]; 
                    }
                    else if (rowIndex + 1 < body.Rows.Count) 
                    {
                        cell = body.Rows[rowIndex + 1].Cells[0];
                    }
                    else 
                    {
                        // 
                    } 
                }
                else 
                {
                    if (cellIndex > 0)
                    {
                        cell = row.Cells[cellIndex - 1]; 
                    }
                    else if (rowIndex > 0) 
                    { 
                        cell = body.Rows[rowIndex - 1].Cells[body.Rows[rowIndex - 1].Cells.Count - 1];
                    } 
                    else
                    {
                        //
                    } 
                }
 
                Invariant.Assert(cell != null); 
                This.Selection.Select(cell.ContentStart, cell.ContentEnd);
                return true; 
            }

            return false;
        } 

        // ...................................................... 
        // 
        //  Handling Text Input
        // 
        // ......................................................

        /// 
        /// This is a single method used to insert user input characters. 
        /// It takes care of typing undo, springload formatting, overtype mode etc.
        ///  
        ///  
        /// 
        /// Text to insert. 
        /// 
        /// 
        /// Reflects a state of Insert key at the moment of textData input.
        ///  
        /// 
        /// True indicates that control characters like '\t' or '\r' etc. can be inserted. 
        /// False means that all control characters are filtered out. 
        /// 
        private static void DoTextInput(TextEditor This, string textData, bool isInsertKeyToggled, bool acceptControlCharacters) 
        {
            // Hide the mouse cursor on user input.
            HideCursor(This);
 
            // Remove control characters. Note that this is not included into _FilterText,
            // because we want such kind of filtering only for real input, 
            // not for copy/paste. 
            if (!acceptControlCharacters)
            { 
                for (int i = 0; i < textData.Length; i++)
                {
                    if (Char.IsControl(textData[i]))
                    { 
                        textData = textData.Remove(i--, 1);  // decrement i to compensate for character removal
                    } 
                } 
            }
 
            string filteredText = This._FilterText(textData, This.Selection);
            if (filteredText.Length == 0)
            {
                return; 
            }
 
            TextEditorTyping.OpenTypingUndoUnit(This); 

            UndoCloseAction closeAction = UndoCloseAction.Rollback; 

            try
            {
                using (This.Selection.DeclareChangeBlock()) 
                {
                    This.Selection.ApplyTypingHeuristics(This.AllowOvertype && This._OvertypeMode && filteredText != "\t"); 
 
                    This.SetSelectedText(filteredText, InputLanguageManager.Current.CurrentInputLanguage);
 
                    // Create caret position normalized backward to keep formatting of a character just typed
                    ITextPointer caretPosition = This.Selection.End.CreatePointer(LogicalDirection.Backward);

                    // Set selection at the end of input content 
                    This.Selection.SetCaretToPosition(caretPosition, LogicalDirection.Backward, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true);
                    // Note: Using explicit backward orientation we keep formatting with 
                    // a previous character during typing. 

                    closeAction = UndoCloseAction.Commit; 
                }
            }
            finally
            { 
                TextEditorTyping.CloseTypingUndoUnit(This, closeAction);
            } 
        } 

        // Takes state originating with a KeyDownEvent or TextInputEvent and 
        // schedules it for eventual handling.
        //
        // Normally we delay handling until a Background priority event fires.
        // This has the effect of batching multiple input events when 
        // layout cannot keep up with the input stream.
        // 
        // However, if any mouse events are pending, we handle the event 
        // immediately, since otherwise we risk the possibility of handling
        // the events out of order. 
        private static void ScheduleInput(TextEditor This, InputItem item)
        {
            if (!This.AcceptsRichContent || IsMouseInputPending(This))
            { 
                // We have to do the work now, or we'll get out of synch.
                TextEditorTyping._FlushPendingInputItems(This); 
                item.Do(); 
            }
            else 
            {
                TextEditorThreadLocalStore threadLocalStore;

                threadLocalStore = TextEditor._ThreadLocalStore; 

                if (threadLocalStore.PendingInputItems == null) 
                { 
                    threadLocalStore.PendingInputItems = new ArrayList(1);
                    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(BackgroundInputCallback), This); 
                }

                threadLocalStore.PendingInputItems.Add(item);
            } 
        }
 
        // Returns true if any mouse input event is currently waiting in the 
        // win32 message queue for processing.
        // Avalon doesn't keep a separate queue for input events.  Instead 
        // it interleaves work items with the win32 input queue.
        /// 
        ///     Critical - Calls PeekMessage, and accesses the root window
        ///     TreatAsSafe - The information it returns is safe to return. 
        /// 
        [SecurityCritical,SecurityTreatAsSafe] 
        private static bool IsMouseInputPending(TextEditor This) 
        {
            bool mouseInputPending = false; 
            IWin32Window win32Window = PresentationSource.CriticalFromVisual(This.UiScope) as IWin32Window;
            if (win32Window != null)
            {
                IntPtr hwnd = IntPtr.Zero; 
                new UIPermission(UIPermissionWindow.AllWindows).Assert(); // BlessedAssert
                try 
                { 
                    hwnd = win32Window.Handle;
                } 
                finally
                {
                    UIPermission.RevertAssert();
                } 

                if (hwnd != (IntPtr)0) 
                { 
                    System.Windows.Interop.MSG message = new System.Windows.Interop.MSG();
                    mouseInputPending = UnsafeNativeMethods.PeekMessage(ref message, new HandleRef(null, hwnd), NativeMethods.WM_MOUSEFIRST, NativeMethods.WM_MOUSELAST, NativeMethods.PM_NOREMOVE); 
                }
            }

            return mouseInputPending; 
        }
 
        // Background priority callback used to process keystrokes. 
        private static object BackgroundInputCallback(object This)
        { 
            TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore;

            Invariant.Assert(This is TextEditor);
            Invariant.Assert(threadLocalStore.PendingInputItems != null); 

            try 
            { 
                TextEditorTyping._FlushPendingInputItems((TextEditor)This);
            } 
            finally
            {
                threadLocalStore.PendingInputItems = null;
            } 

            return null; 
        } 

        ///  
        /// Callback for shutdown finished dispatcher. Before shutdown dispatcher, we should clean
        /// InputLanguageChangedEventHandler.
        /// 
        private static void OnDispatcherShutdownFinished(object sender, EventArgs args) 
        {
            // Remove the dispatcher shutdown finished event handler 
            Dispatcher.CurrentDispatcher.ShutdownFinished -= new EventHandler(OnDispatcherShutdownFinished); 

            // Remove the input language changed event handler 
            InputLanguageManager.Current.InputLanguageChanged -= new InputLanguageEventHandler(OnInputLanguageChanged);

            TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore;
 
            // Clear InputLanguageChangeEventHandler count
            threadLocalStore.InputLanguageChangeEventHandlerCount = 0; 
        } 

        // InputLanguageChanged handler. 
        private static void OnInputLanguageChanged(object sender, InputLanguageEventArgs e)
        {
            TextSelection.OnInputLanguageChanged(e.NewLanguage);
        } 

        // Base class for keyboard/text input items. 
        // Individual keystroke/text events are batched and handled together 
        // when layout cannot keep up with the input stream.
        private abstract class InputItem 
        {
            // Ctor.
            internal InputItem(TextEditor textEditor)
            { 
                _textEditor = textEditor;
            } 
 
            // Handles the input event.
            internal abstract void Do(); 

            // The TextEditor instance on which this input item applies.
            TextEditor _textEditor;
 
            protected TextEditor TextEditor
            { 
                get 
                {
                    return _textEditor; 
                }
            }
        }
 
        // Holds state originating from a single TextInputEvent.
        private class TextInputItem : InputItem 
        { 
            // Ctor.
            internal TextInputItem(TextEditor textEditor, string text, bool isInsertKeyToggled) 
                : base (textEditor)
            {
                _text = text;
                _isInsertKeyToggled = isInsertKeyToggled; 
            }
 
            // Inserts event content into the document. 
            internal override void Do()
            { 
                if (TextEditor.UiScope == null)
                {
                    // We dont want to process the input item if the editor has already been detached from its UiScope.
                    return; 
                }
 
                DoTextInput(TextEditor, _text, _isInsertKeyToggled, /*acceptControlCharacters:*/false); 
            }
 
            // Text to input.
            private readonly string _text;
            private readonly bool _isInsertKeyToggled;
        } 

        // Holds state originating from a single KeyDownEvent. 
        private class KeyUpInputItem : InputItem 
        {
            // Ctor. 
            internal KeyUpInputItem(TextEditor textEditor, Key key, ModifierKeys modifiers)
                : base(textEditor)
            {
                _key = key; 
                _modifiers = modifiers;
            } 
 
            // Fires the command associated with a keystroke.
            internal override void Do() 
            {
                if (TextEditor.UiScope == null)
                {
                    // We dont want to process the input item if the editor has already been detached from its UiScope. 
                    return;
                } 
 
                // Delegate the work to specific handlers.
                switch (_key) 
                {
                    case Key.RightShift:
                        // Only support RTL flow direction in case of having the installed
                        // bidi input language. 
                        if (TextSelection.IsBidiInputLanguageInstalled() == true)
                        { 
                            TextEditorTyping.OnFlowDirectionCommand(TextEditor, _key); 
                        }
                        break; 
                    case Key.LeftShift:
                        TextEditorTyping.OnFlowDirectionCommand(TextEditor, _key);
                        break;
 
                    default:
                        Invariant.Assert(false, "Unexpected key value!"); 
                        break; 
                }
            } 

            // Key associated with the original event.
            private readonly Key _key;
 
            // Modifier state when the original event fired.
            private readonly ModifierKeys _modifiers; 
        } 

        // ---------------------------------------------------------- 
        //
        // Merge Typing Undo Units
        //
        // ---------------------------------------------------------- 

        #region Merge Typing Undo Units 
 
        /// 
        /// The helper for typing undo unit merging. 
        /// Supposed to be called in the beginning of typing block -
        /// before making any changes.
        /// Assumes that CloseTypingUndoUnit method will be called
        /// after the change is completed. 
        /// 
        private static void OpenTypingUndoUnit(TextEditor This) 
        { 
            UndoManager undoManager = This._GetUndoManager();
 
            if (undoManager != null && undoManager.IsEnabled)
            {
                if (This._typingUndoUnit != null && undoManager.LastUnit == This._typingUndoUnit && !This._typingUndoUnit.Locked)
                { 
                    undoManager.Reopen(This._typingUndoUnit);
                } 
                else 
                {
                    This._typingUndoUnit = new TextParentUndoUnit(This.Selection); 
                    undoManager.Open(This._typingUndoUnit);
                }
            }
        } 

        ///  
        /// The helper for typing undo unit megring. 
        /// Supposed to be called at the end of typing block -
        /// after all changes are done. 
        /// Assumes that OpenTypingUndoUnit method was called
        /// in the beginning of this sequence.
        /// 
        private static void CloseTypingUndoUnit(TextEditor This, UndoCloseAction closeAction) 
        {
            UndoManager undoManager = This._GetUndoManager(); 
 
            if (undoManager != null && undoManager.IsEnabled)
            { 
                if (This._typingUndoUnit != null && undoManager.LastUnit == This._typingUndoUnit && !This._typingUndoUnit.Locked)
                {
                    if (This._typingUndoUnit is TextParentUndoUnit)
                    { 
                        ((TextParentUndoUnit)This._typingUndoUnit).RecordRedoSelectionState();
                    } 
                    undoManager.Close(This._typingUndoUnit, closeAction); 
                }
            } 
            else
            {
                This._typingUndoUnit = null;
            } 
        }
 
        ///  
        /// StartInputCorrection command QueryStatus handler
        ///  
        private static void OnQueryStatusNYI(object target, CanExecuteRoutedEventArgs args)
        {
            TextEditor This = TextEditor._GetTextEditor(target);
 
            if (This == null)
            { 
                return; 
            }
 
            args.CanExecute = true;
        }

        #endregion Merge Typing Undo Units 

        // MouseMoveEvent listener. 
        private static void OnMouseMove(object sender, MouseEventArgs e) 
        {
            // Un-vanish the cursor on any mouse move. 
            _ShowCursor();
        }

        // MouseMoveEvent listener. 
        // We only need this event because of the edge case where
        // moving the mouse from the outermost pixel of the UiScope to 
        // another UIElement's real estate doesn't raise a MouseMoveEvent. 
        private static void OnMouseLeave(object sender, MouseEventArgs e)
        { 
            // Un-vanish the cursor on any mouse leave.
            _ShowCursor();
        }
 
        // Hides the mouse cursor when the user starts typing.
        private static void HideCursor(TextEditor This) 
        { 
            if (!TextEditor._ThreadLocalStore.HideCursor &&
                SystemParameters.MouseVanish && 
                This.UiScope.IsMouseOver)
            {
                TextEditor._ThreadLocalStore.HideCursor = true;
                SafeNativeMethods.ShowCursor(false); 
            }
        } 
 
        // When the mouse cursor is over a Hyperlink, force a cursor update
        // to display the "hand" cursor appropriately. 
        private static void UpdateHyperlinkCursor(TextEditor This)
        {
            if (This.UiScope is RichTextBox && This.TextView != null && This.TextView.IsValid)
            { 
                TextPointer pointer = (TextPointer)This.TextView.GetTextPositionFromPoint(Mouse.GetPosition(This.TextView.RenderScope), false);
 
                if (pointer != null && 
                    pointer.Parent is TextElement &&
                    TextSchema.HasHyperlinkAncestor((TextElement)pointer.Parent)) 
                {
                    Mouse.UpdateCursor();
                }
            } 
        }
 
        #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