DocumentGrid.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / MS / Internal / documents / DocumentGrid.cs / 1 / DocumentGrid.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// 
// Description: DocumentGrid displays DocumentPaginator content in a grid-like 
//              arrangement and is used by DocumentViewer to display documents.
// 
// History:

// 10/21/04 - jdersch created for complete and utter overhaul for the new
//            DocumentViewer control. 
//
//--------------------------------------------------------------------------- 
using MS.Internal; 
using MS.Internal.Media;
using MS.Utility; 
using MS.Win32;
using System.Threading;
using System.Windows;
using System.Windows.Controls; 
using System.Windows.Controls.Primitives;
using System.Windows.Documents; 
using System.Windows.Input; 
using System.Windows.Media;
using System.Windows.Threading; 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using System.Diagnostics;
 
namespace MS.Internal.Documents 
{
 
    /// 
    /// DocumentGrid is an internal Avalon FrameworkElement that executes all the
    /// "heavy lifting" involved in loading and displaying an DocumentPaginator-based
    /// document inside of a DocumentViewer control. 
    /// 
    /// http://d2/DRX/default.aspx 
    internal class DocumentGrid : FrameworkElement, IDocumentScrollInfo 
    {
        //----------------------------------------------------- 
        //
        //  Constructors
        //
        //----------------------------------------------------- 
        #region Constructors
 
        ///  
        /// Static constructor
        ///  
        static DocumentGrid()
        {
            //Register for the RequestBringIntoView event so we can get BIV events for
            //TextEditor IP movements. 
            EventManager.RegisterClassHandler(typeof(DocumentGrid),
                RequestBringIntoViewEvent, 
                new RequestBringIntoViewEventHandler(OnRequestBringIntoView)); 
            //Register the default ContextMenu
            DocumentGridContextMenu.RegisterClassHandler(); 
        }

        /// 
        /// The constructor 
        /// 
        public DocumentGrid() : base() 
        { 
            Initialize();
        } 

        #endregion Constructors

        //------------------------------------------------------ 
        //
        //  Internal Methods 
        // 
        //-----------------------------------------------------
        #region Internal Methods 
        /// 
        /// Hit-Test on the multi-page UI scope to return a DocumentPage
        /// that contains this point
        ///  
        /// Point in pixel unit, relative to the UI Scope's coordinates
        /// A DocumentPage that is hit or null if no page is hit 
        internal DocumentPage GetDocumentPageFromPoint(Point point) 
        {
            DocumentPageView dp = GetDocumentPageViewFromPoint(point); 

            // if we hit a DocumentPageView we can return its DocumentPage.
            if (dp != null)
            { 
                return dp.DocumentPage;
            } 
 
            //Nothing hit, return null.
            return null; 
        }

        #endregion Internal Methods
 

        //------------------------------------------------------ 
        // 
        //  Public Interfaces
        // 
        //------------------------------------------------------
        #region Interface Implementations

 
        #region IDocumentScrollInfo
        //----------------------------------------------------- 
        // 
        //  IDocumentScrollInfo Methods
        // 
        //------------------------------------------------------

        /// 
        /// Scroll content by one line to the top. 
        /// 
        public void LineUp() 
        { 
            if (_canVerticallyScroll)
            { 
                SetVerticalOffsetInternal(VerticalOffset - _verticalLineScrollAmount);
            }
        }
 
        /// 
        /// Scroll content by one line to the bottom. 
        ///  
        public void LineDown()
        { 
            if (_canVerticallyScroll)
            {
                SetVerticalOffsetInternal(VerticalOffset + _verticalLineScrollAmount);
 
                //Perf Tracing - Mark LineDown Start
                if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
                { 
                    EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXLINEDOWNGUID),
                                                         MS.Utility.EventType.Info, 
                                                         (int)VerticalOffset
                                                         );
                }
            } 
        }
 
        ///  
        /// Scroll content by one line to the left.
        ///  
        public void LineLeft()
        {
            if (_canHorizontallyScroll)
            { 
                SetHorizontalOffsetInternal(HorizontalOffset - _horizontalLineScrollAmount);
            } 
        } 

        ///  
        /// Scroll content by one line to the right.
        /// 
        public void LineRight()
        { 
            if (_canHorizontallyScroll)
            { 
                SetHorizontalOffsetInternal(HorizontalOffset + _horizontalLineScrollAmount); 
            }
        } 

        /// 
        /// Scroll content by one viewport to the top.
        ///  
        public void PageUp()
        { 
            SetVerticalOffsetInternal(VerticalOffset - ViewportHeight); 
        }
 
        /// 
        /// Scroll content by one viewport to the bottom.
        /// 
        public void PageDown() 
        {
            SetVerticalOffsetInternal(VerticalOffset + ViewportHeight); 
 
            //Perf Tracing - Mark PageDown Start
            if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
            {
                EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEDOWNGUID),
                                                     MS.Utility.EventType.Info,
                                                     (int)VerticalOffset); 
            }
        } 
 
        /// 
        /// Scroll content by one viewport to the left. 
        /// 
        public void PageLeft()
        {
            SetHorizontalOffsetInternal(HorizontalOffset - ViewportWidth); 
        }
 
        ///  
        /// Scroll content by one viewport to the right.
        ///  
        public void PageRight()
        {
            SetHorizontalOffsetInternal(HorizontalOffset + ViewportWidth);
        } 

        ///  
        /// Scroll content up via the mousewheel. 
        /// 
        public void MouseWheelUp() 
        {
            if (_canVerticallyScroll)
            {
                SetVerticalOffsetInternal(VerticalOffset - MouseWheelVerticalScrollAmount); 
            }
            else 
            { 
                PageUp();
            } 
        }

        /// 
        /// Scroll content down via the mousewheel. 
        /// 
        public void MouseWheelDown() 
        { 
            if (_canVerticallyScroll)
            { 
                SetVerticalOffsetInternal(VerticalOffset + MouseWheelVerticalScrollAmount);
            }
            else
            { 
                PageDown();
            } 
        } 

        ///  
        /// Scroll content left via the mousewheel.
        /// 
        public void MouseWheelLeft()
        { 
            if (_canHorizontallyScroll)
            { 
                SetHorizontalOffsetInternal(HorizontalOffset - MouseWheelHorizontalScrollAmount); 
            }
            else 
            {
                PageLeft();
            }
        } 

        ///  
        /// Scroll content right via the mousewheel. 
        /// 
        public void MouseWheelRight() 
        {
            if (_canHorizontallyScroll)
            {
                SetHorizontalOffsetInternal(HorizontalOffset + MouseWheelHorizontalScrollAmount); 
            }
            else 
            { 
                PageRight();
            } 
        }

        /// 
        /// Ensures that the specified visual is made visible. 
        /// 
        ///  
        /// A rectangle in the IScrollInfo's coordinate space that has been made visible. 
        /// Other ancestors to in turn make this new rectangle visible.
        /// The rectangle should generally be a transformed version of the input rectangle.  In some cases, like 
        /// when the input rectangle cannot entirely fit in the viewport, the return value might be smaller.
        /// 
        public Rect MakeVisible(Visual v, Rect r)
        { 
            if (Content != null && v != null)
            { 
                ContentPosition cp = Content.GetObjectPosition(v); 
                MakeContentPositionVisibleAsync(new MakeVisibleData(v, cp, r));
            } 

            return r;
        }
 
        /// 
        /// Ensures that the specified object is made visible, given that the page it lives on is already known. 
        ///  
        /// 
        /// A rectangle in the IScrollInfo's coordinate space that has been made visible. 
        /// Other ancestors to in turn make this new rectangle visible.
        /// The rectangle should generally be a transformed version of the input rectangle.  In some cases, like
        /// when the input rectangle cannot entirely fit in the viewport, the return value might be smaller.
        ///  
        public Rect MakeVisible(object o, Rect r, int pageNumber)
        { 
            MakeVisibleAsync(new MakeVisibleData(o as Visual, o as ContentPosition, r), pageNumber); 
            return r;
        } 

        /// 
        /// Scrolls the current selection into view.  Requests for empty or
        /// invalid selections will do nothing. 
        /// 
        public void MakeSelectionVisible() 
        { 
            //We can only continue if we have a TextEditor attached...
            if (TextEditor != null && TextEditor.Selection != null) 
            {
                //Get the TextPointer for the start of our selection.
                ITextPointer tp = TextEditor.Selection.Start;
 
                //Ensure that the TextPointer we use has gravity set to forwards (or into
                //the selection) so that the selected text is always displayed. 
                tp = tp.CreatePointer(LogicalDirection.Forward); 

                //If the TextPointer is also a ContentPosition, we can 
                //make that ContentPosition visible.
                ContentPosition cp = tp as ContentPosition;
                MakeContentPositionVisibleAsync(new MakeVisibleData(null, cp, Rect.Empty));
            } 
        }
 
        ///  
        /// Scrolls the requested page into view.
        ///  
        /// The page to make visible.
        public void MakePageVisible(int pageNumber)
        {
            //If we're moving more than one page then this is a "page jump" 
            //and we should log the perf event.
            if (Math.Abs(pageNumber - _firstVisiblePageNumber) > 1) 
            { 
                //Perf Tracing - Mark Page Jump Start
                if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
                {
                    EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEJUMPGUID),
                                                         MS.Utility.EventType.Info,
                                                         _firstVisiblePageNumber, 
                                                         pageNumber);
                } 
            } 

            //Clip the offset into range for out-of-range page numbers 
            if (pageNumber < 0 )
            {
                 //Clip to the top-left of the document
                SetVerticalOffsetInternal(0.0d); 
                SetHorizontalOffsetInternal(0.0d);
            } 
            else if (pageNumber >= _pageCache.PageCount || _rowCache.RowCount == 0) 
            {
                //If the doc is done loading, then this page is out of range. 
                if (_pageCache.IsPaginationCompleted && _rowCache.HasValidLayout)
                {
                    //Clip to the bottom-right of the document
                    SetVerticalOffsetInternal(ExtentHeight); 
                    SetHorizontalOffsetInternal(ExtentWidth);
                } 
                else 
                {
                    //The doc is not done loading. 
                    //Wait for the page to be laid out and try again.
                    _pageJumpAfterLayout = true;
                    _pageJumpAfterLayoutPageNumber = pageNumber;
                } 

            } 
            else 
            {
                //This page is valid, so scroll to it now. 
                RowInfo scrolledRow = _rowCache.GetRowForPageNumber(pageNumber);
                SetVerticalOffsetInternal(scrolledRow.VerticalOffset);

                //Calculate the Horizontal offset of the page we're bringing into view: 
                double horizontalOffset = GetHorizontalOffsetForPage(scrolledRow, pageNumber);
                SetHorizontalOffsetInternal(horizontalOffset); 
 
            }
        } 

        /// 
        /// Scrolls the next row of pages into view.  This differs from
        /// IScrollInfo?s "PageDown" in that PageDown pages by Viewports 
        /// which may not coincide with page dimensions, whereas
        /// ScrollToNextRow takes these dimensions into account so that 
        /// precisely the next row of pages is displayed. 
        /// 
        public void ScrollToNextRow() 
        {
            //We change our vertical offset to be the offset of the next row (if there is one).
            //If there isn't, we do nothing.
            int nextRow = _firstVisibleRow + 1; 

            if (nextRow < _rowCache.RowCount) 
            { 
                //Get the next row.
                RowInfo row = _rowCache.GetRow(nextRow); 
                SetVerticalOffsetInternal(row.VerticalOffset);
            }
        }
 
        /// Scrolls the previous row of pages into view.  This differs from
        /// IScrollInfo?s "PageUp" in that PageUp pages by Viewports 
        /// which may not coincide with page dimensions, whereas 
        /// ScrollToPreviousRow takes these dimensions into account so that
        /// precisely the previously row of pages is displayed. 
        public void ScrollToPreviousRow()
        {
            //We change our vertical offset to be the offset of the previous row (if there is one).
            int previousRow = _firstVisibleRow - 1; 

            if (previousRow >= 0 && previousRow < _rowCache.RowCount) 
            { 
                //Get the previous row.
                RowInfo row = _rowCache.GetRow(previousRow); 
                SetVerticalOffsetInternal(row.VerticalOffset);
            }
        }
 
        /// 
        /// Scrolls to the top of the document. 
        ///  
        public void ScrollToHome()
        { 
            //We just set the VerticalOffset to 0.
            SetVerticalOffsetInternal(0);
        }
 
        /// 
        /// Scrolls to the bottom of the document. 
        ///  
        public void ScrollToEnd()
        { 
            //We just set the VerticalOffset to our document's extent.
            SetVerticalOffsetInternal(ExtentHeight);
        }
 
        /// 
        /// Sets the scale factor applied to pages in the document, while 
        /// keeping the "Active Focus" centered. 
        /// 
        ///  
        public void SetScale(double scale)
        {
            if (!DoubleUtil.AreClose(scale, Scale))
            { 
                if (scale <= 0.0)
                { 
                    throw new ArgumentOutOfRangeException("scale"); 
                }
 
                if (!Helper.IsDoubleValid(scale))
                {
                    throw new ArgumentOutOfRangeException("scale");
                } 

                QueueSetScale(scale); 
 
            }
        } 

        /// 
        /// Changes the view to the specified number of columns.
        ///  
        /// 
        public void SetColumns(int columns) 
        { 
            if (columns < 1)
            { 
                throw new ArgumentOutOfRangeException("columns");
            }

            //Perf Tracing - Mark Layout Change Start 
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info);
 
            QueueUpdateDocumentLayout(new DocumentLayout(columns, ViewMode.SetColumns)); 
        }
 
        /// 
        /// Changes the view to the specified number of columns.
        /// 
        ///  
        public void FitColumns(int columns)
        { 
            if (columns < 1) 
            {
                throw new ArgumentOutOfRangeException("columns"); 
            }

            //Perf Tracing - Mark Layout Change Start
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info); 

            QueueUpdateDocumentLayout(new DocumentLayout(columns, ViewMode.FitColumns)); 
        } 

        ///  
        /// Changes the view to a single page, scaled such that it is as wide as the Viewport.
        /// 
        public void FitToPageWidth()
        { 
            //Perf Tracing - Mark Layout Change Start
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info); 
 
            QueueUpdateDocumentLayout(
                new DocumentLayout(1 /* one column */, ViewMode.PageWidth)); 
        }

        /// 
        /// Changes the view to a single page, scaled such that it is as tall as the Viewport. 
        /// 
        public void FitToPageHeight() 
        { 
            //Perf Tracing - Mark Layout Change Start
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info); 

            QueueUpdateDocumentLayout(
                new DocumentLayout(1 /* one column */, ViewMode.PageHeight));
        } 

        ///  
        /// Changes the view to ?thumbnail view? which will scale the document 
        /// such that as many pages are visible at once as is possible.
        ///  
        public void ViewThumbnails()
        {
            //Perf Tracing - Mark Layout Change Start
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info); 

            QueueUpdateDocumentLayout( 
                new DocumentLayout(1 /* one column, arbitrary */, ViewMode.Thumbnails)); 
        }
 
        //-----------------------------------------------------
        //
        //  IDocumentScrollInfo Properties
        // 
        //-----------------------------------------------------
 
        ///  
        /// DocumentGrid always scrolls in both dimensions.
        ///  
        public bool CanHorizontallyScroll
        {
            get { return _canHorizontallyScroll; }
            set { _canHorizontallyScroll = value; } 
        }
 
        ///  
        /// DocumentGrid always scrolls in both dimensions.
        ///  
        public bool CanVerticallyScroll
        {
            get { return _canVerticallyScroll; }
            set { _canVerticallyScroll = value; } 
        }
 
        ///  
        /// ExtentWidth contains the full horizontal range of the scrolled content.
        ///  
        public double ExtentWidth
        {
            get
            { 
                return _rowCache.ExtentWidth;
            } 
        } 

        ///  
        /// ExtentHeight contains the full vertical range of the scrolled content.
        /// 
        public double ExtentHeight
        { 
            get
            { 
                return _rowCache.ExtentHeight; 
            }
        } 

        /// 
        /// ViewportWidth contains the currently visible horizontal range of the scrolled content.
        ///  
        public double ViewportWidth
        { 
            get 
            {
                return _viewportWidth; 
            }
        }

        ///  
        /// ViewportHeight contains the currently visible vertical range of the scrolled content.
        ///  
        public double ViewportHeight 
        {
            get 
            {
                return _viewportHeight;
            }
        } 

        ///  
        /// HorizontalOffset is the horizontal offset into the scrolled content that represents the first unit visible. 
        /// Valid values are inclusively between 0 and  less .
        ///  
        public double HorizontalOffset
        {
            get
            { 
                //Clip HorizontalOffset into range.
                double clippedHorizontalOffset = Math.Min(_horizontalOffset, ExtentWidth - ViewportWidth); 
                clippedHorizontalOffset = Math.Max(clippedHorizontalOffset, 0.0); 

                return clippedHorizontalOffset; 
            }
        }

        ///  
        /// Set the HorizontalOffset.  If there are pending layout delegates, then
        /// this will be processed by a delegate. 
        ///  
        /// 
        public void SetHorizontalOffset(double offset) 
        {
            if (!DoubleUtil.AreClose(_horizontalOffset,offset))
            {
                if (Double.IsNaN(offset)) 
                {
                    throw new ArgumentOutOfRangeException("offset"); 
                } 

                // If there aren't any pending document layout delegates, then change 
                // the HorizontalOffset immediately, otherwise schedule a delegate for it.
                if (_documentLayoutsPending == 0)
                {
                    SetHorizontalOffsetInternal(offset); 
                }
                else 
                { 
                    QueueUpdateDocumentLayout(new DocumentLayout(offset, ViewMode.SetHorizontalOffset));
                } 
            }
        }

        ///  
        /// VerticalOffset is the vertical offset into the scrolled content that represents the first unit visible.
        /// Valid values are inclusively between 0 and  less . 
        ///  
        public double VerticalOffset
        { 
            get
            {
                //Clip VerticalOffset into range.
                double clippedVerticalOffset = Math.Min(_verticalOffset, ExtentHeight - ViewportHeight); 
                clippedVerticalOffset = Math.Max(clippedVerticalOffset, 0.0);
 
                return clippedVerticalOffset; 
            }
        } 

        /// 
        /// Set the VerticalOffset.  If there are pending layout delegates, then
        /// this will be processed by a delegate. 
        /// 
        ///  
        public void SetVerticalOffset(double offset) 
        {
            if (!DoubleUtil.AreClose(_verticalOffset, offset)) 
            {
                if (Double.IsNaN(offset))
                {
                    throw new ArgumentOutOfRangeException("offset"); 
                }
 
                // If there aren't any pending document layout delegates, then change 
                // the VerticalOffset immediately, otherwise schedule a delegate for it.
                if (_documentLayoutsPending == 0) 
                {
                    SetVerticalOffsetInternal(offset);
                }
                else 
                {
                    QueueUpdateDocumentLayout(new DocumentLayout(offset, ViewMode.SetVerticalOffset)); 
                } 
            }
        } 

        /// 
        /// Provides the IDocumentScrollInfo implementer with a content
        /// tree to be paginated.  Developers are free to modify this 
        /// Content at any time (remove, add, modify pages, etc?)
        /// and the IDocumentScrollInfo implementer is responsible for 
        /// noting the changes and updating as necessary. 
        /// 
        /// The DocumentPaginator to be assigned as the content 
        public DynamicDocumentPaginator Content
        {
            get
            { 
                //_pageCache is guaranteed to be non-null as it's created in the
                //Constructor. 
                return _pageCache.Content; 
            }
            set 
            {
                //_pageCache is guaranteed to be non-null as it's created in the
                //Constructor.
                if (value != _pageCache.Content) 
                {
                    //Null out our TextContainer.  It will be created as needed. 
                    _textContainer = null; 

                    //Remove our old events from the content 
                    if (_pageCache.Content != null)
                    {
                        _pageCache.Content.GetPageNumberCompleted -= new GetPageNumberCompletedEventHandler(OnGetPageNumberCompleted);
                    } 

                    //Remove our ScrollChanged events from our ScrollViewer 
                    if (ScrollOwner != null) 
                    {
                        ScrollOwner.ScrollChanged -= new ScrollChangedEventHandler(OnScrollChanged); 
                        _scrollChangedEventAttached = false;
                    }

                    //Assign the new content 
                    _pageCache.Content = value;
 
                    if (_pageCache.Content != null) 
                    {
                        //Add our new events to the content 
                        _pageCache.Content.GetPageNumberCompleted += new GetPageNumberCompletedEventHandler(OnGetPageNumberCompleted);
                    }

                    //Clear out our visual collection so that the old pages (pointing to old content) 
                    //will be replaced with new ones on the next Measure/Arrange pass.
                    ResetVisualTree(false /*pruneOnly*/); 
                    ResetPageViewCollection(); 

                    //Reset our visible pages. 
                    _firstVisiblePageNumber = 0;
                    _lastVisiblePageNumber = 0;

                    // Perf Tracing - PageVisible Changed 
                    if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
                    { 
                        EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEVISIBLEGUID), MS.Utility.EventType.Info, _firstVisiblePageNumber, _lastVisiblePageNumber); 
                    }
 
                    _lastRowChangeExtentWidth = 0.0;
                    _lastRowChangeVerticalOffset = 0.0;

                    //Cause the new content to be laid out in the same fashion as 
                    //the previous content.
                    //If the view is Thumbnails we'll change it to SetColumns 
                    //so that the Column count will be maintained.  This is done 
                    //because we're getting new content which initially has 0
                    //pages and a Thumbnail view only gives decent results after 
                    //the entire content has been loaded; since we don't want to provide a
                    //"jarring" situation (where layout suddenly changes after the content's loaded)
                    //we use SetColumns to maintain the same exact layout as the old content.
                    //(This is consistent with DocumentViewer's overall behavior -- any view setting is 
                    //a "one time thing" and isn't recomputed if the content changes.)
                    if (_documentLayout.ViewMode == ViewMode.Thumbnails) 
                    { 
                        _documentLayout.ViewMode = ViewMode.SetColumns;
                    } 
                    QueueUpdateDocumentLayout(_documentLayout);

                    //Invalidate Measure and our IDSI so that properties changed
                    //by the content assignment will be properly updated. 
                    InvalidateMeasure();
                    InvalidateDocumentScrollInfo(); 
                } 
            }
        } 

        /// 
        /// Indicates the number of pages currently in the document.
        ///  
        /// 
        public int PageCount 
        { 
            get
            { 
                return _pageCache.PageCount;
            }
        }
 
        /// 
        /// When queried, FirstVisiblePageNumber returns the first page visible onscreen. 
        ///  
        /// 
        public int FirstVisiblePageNumber 
        {
            get
            {
                return _firstVisiblePageNumber; 
            }
        } 
 
        /// 
        /// Returns the current Scale factor applied to the pages given the current settings. 
        /// 
        /// 
        public double Scale
        { 
            get
            { 
                return _rowCache.Scale; 
            }
        } 

        /// 
        /// Returns the current number of Columns of pages displayed given the current settings.
        ///  
        /// 
        public int MaxPagesAcross 
        { 
            get
            { 
                return _maxPagesAcross;
            }
        }
 
        /// 
        /// Specifies the vertical gap between Pages when laid out, in pixel (1/96?) units. 
        ///  
        /// 
        public double VerticalPageSpacing 
        {
            get
            {
                return _rowCache.VerticalPageSpacing; 
            }
 
            set 
            {
                if (!Helper.IsDoubleValid(value)) 
                {
                    throw new ArgumentOutOfRangeException("value");
                }
 
                _rowCache.VerticalPageSpacing = value;
            } 
        } 

        ///  
        /// Specifies the horizontal gap between Pages when laid out, in pixel (1/96?) units.
        /// 
        /// 
        public double HorizontalPageSpacing 
        {
            get 
            { 
                return _rowCache.HorizontalPageSpacing;
            } 

            set
            {
                if (!Helper.IsDoubleValid(value)) 
                {
                    throw new ArgumentOutOfRangeException("value"); 
                } 

                _rowCache.HorizontalPageSpacing = value; 
            }
        }

        ///  
        /// Specifies whether each displayed page should be adorned with a ?Drop Shadow? border or not.
        ///  
        ///  
        public bool ShowPageBorders
        { 
            get
            {
                return _showPageBorders;
            } 

            set 
            { 
                if (_showPageBorders != value)
                { 
                    _showPageBorders = value;

                    //Update our pages' ShowPageBorder properties.
                    //Get the current Visual Collection, which contains our pages. 
                    int count = _childrenCollection.Count;
                    for (int i = 0; i < count; i++) 
                    { 
                        DocumentGridPage dp = _childrenCollection[i] as DocumentGridPage;
 
                        if (dp != null)
                        {
                            dp.ShowPageBorders = _showPageBorders;
                        } 
                    }
                } 
            } 
        }
 
        /// 
        /// Specifies whether the last "view mode" related property change should be locked
        /// for resizing.
        ///  
        /// 
        public bool LockViewModes 
        { 
            get
            { 
                return _lockViewModes;
            }

            set 
            {
                _lockViewModes = value; 
            } 

        } 

        /// 
        /// Returns a TextContainer for current content
        ///  
        /// The content's TextContainer, or null if there is none.
        public ITextContainer TextContainer 
        { 
            get
            { 
                if (_textContainer == null)
                {
                    if (Content != null)
                    { 
                        IServiceProvider isp = Content as IServiceProvider;
                        if (isp != null) 
                        { 
                            _textContainer = (ITextContainer)isp.GetService(typeof(ITextContainer));
                        } 
                    }
                }

                return _textContainer; 
            }
        } 
 
        /// 
        /// Returns the MultiPageTextView for the current content. 
        /// 
        /// 
        public ITextView TextView
        { 
            get
            { 
                if (TextEditor != null) 
                {
                    return TextEditor.TextView; 
                }
                else
                {
                    return null; 
                }
            } 
        } 

        ///  
        /// The collection of currently-visible DocumentPageViews.
        /// 
        public ReadOnlyCollection PageViews
        { 
            get
            { 
                return _pageViews; 
            }
        } 

        /// 
        /// ScrollOwner is the container that controls any scrollbars, headers, etc... that are dependent
        /// on this IScrollInfo's properties.  Implementers of IScrollInfo should call InvalidateScrollInfo() 
        /// on this object when related properties change.
        ///  
        public ScrollViewer ScrollOwner 
        {
            get 
            {
                return _scrollOwner;
            }
 
            set
            { 
                _scrollOwner = value; 
                InvalidateDocumentScrollInfo();
            } 
        }

        /// 
        /// DocumentViewerOwner is the DocumentViewer Control and UI that hosts the IDocumentScrollInfo object. 
        /// This control is dependent on this IDSI?s properties, so implementers of IDSI should call
        /// InvalidateDocumentScrollInfo() on this object when related properties change so that 
        /// DocumentViewer?s UI will be kept in [....].  This property is analogous to IScrollInfo?s ScrollOwner 
        /// property.
        ///  
        /// 
        public DocumentViewer DocumentViewerOwner
        {
            get 
            {
                return _documentViewerOwner; 
            } 

            set 
            {
                _documentViewerOwner = value;
            }
        } 

        #endregion IDocumentScrollInfo 
 
        #endregion Interface Implementations
 
        //-----------------------------------------------------
        //
        //  Protected Methods
        // 
        //------------------------------------------------------
        #region Protected Methods 
 
        /// 
        ///   Derived class must implement to support Visual children. The method must return 
        ///    the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1.
        ///
        ///    By default a Visual does not have any children.
        /// 
        ///  Remark:
        ///       During this virtual call it is not valid to modify the Visual tree. 
        ///  
        protected override Visual GetVisualChild(int index)
        { 
            if(_childrenCollection == null || index < 0 || index >= _childrenCollection.Count)
            {
                throw new ArgumentOutOfRangeException("index", index, SR.Get(SRID.Visual_ArgumentOutOfRange));
            } 

            return _childrenCollection[index]; 
        } 

        ///  
        ///  Derived classes override this property to enable the Visual code to enumerate
        ///  the Visual children. Derived classes need to return the number of children
        ///  from this method.
        /// 
        ///    By default a Visual does not have any children.
        /// 
        ///  Remark: During this virtual method the Visual tree must not be modified. 
        /// 
        protected override int VisualChildrenCount 
        {
            get
            {
                // _childrenCollection cannot be null since its initialized in the constructor 
                return _childrenCollection.Count;
            } 
        } 

        ///  
        /// MeasureOverride is repsonsible for measuring any visible pages to their correct sizes.
        /// 
        /// The upper bound for child sizes
        ///  
        protected override Size MeasureOverride(Size constraint)
        { 
 
            // If layoutSize is infinity, we need to return our absolute smallest size.
            // This might happen if we are inside an element which sizes-to-content. 
            // For DocumentGrid, we use a hard coded constraint.
            if (double.IsInfinity(constraint.Width) || double.IsInfinity(constraint.Height))
            {
                constraint = _defaultConstraint; 
            }
 
            //Determine which pages are visible at the current offset given the current constraint. 
            RecalculateVisualPages(VerticalOffset, constraint);
 
            //Get our visual children count...
            int count = _childrenCollection.Count;

            //Now go through our child collection and measure all the pages to their sizes. 
            for(int i = 0; i < count; i++)
            { 
                //This should be our background. 
                if (i == _backgroundVisualIndex)
                { 
                    Border background = _childrenCollection[i] as Border;

                    if (background == null)
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.DocumentGridVisualTreeContainsNonBorderAsFirstElement));
                    } 
 
                    //We measure this to the size of our constraint.
                    background.Measure(constraint); 
                }
                //Otherwise it's a page.
                else
                { 
                    //Ensure that this is actually a DocumentGridPage.  If it is not,
                    //Then someone's been mucking with our VisualTree, so we'll throw. 
                    DocumentGridPage page = _childrenCollection[i] as DocumentGridPage; 

                    if (page == null) 
                    {
                        throw new InvalidOperationException(SR.Get(SRID.DocumentGridVisualTreeContainsNonDocumentGridPage));
                    }
 
                    //Get the cached size of this page and scale it to our current scale factor.
                    Size pageSize = _pageCache.GetPageSize(page.PageNumber); 
                    pageSize.Width *= Scale; 
                    pageSize.Height *= Scale;
 
                    //Measure the page if necessary.
                    if (!page.IsMeasureValid)
                    {
                        page.Measure(pageSize); 

                        //See if the cached size has changed since we Measured. 
                        //This can happen if in the course of Measuring the page 
                        //a GetPageAsync() calls back immediately with the real page size.
                        //If this happens we need to re-measure the page before we finish here, 
                        //otherwise we'll end up with a page that's Measured to one size
                        //and Arranged to another, which looks bad.
                        Size newPageSize = _pageCache.GetPageSize(page.PageNumber);
                        if (newPageSize != Size.Empty) 
                        {
                            newPageSize.Width *= Scale; 
                            newPageSize.Height *= Scale; 
                            if (newPageSize.Width != pageSize.Width ||
                            newPageSize.Height != pageSize.Height) 
                            {
                                //Measure again.
                                page.Measure(newPageSize);
                            } 
                        }
                    } 
                } 
            }
 
            return constraint;
        }

        ///  
        /// ArrangeOverride is responsible for arranging the previously measured pages in the right order.
        ///  
        /// The final constraint, inside of which everything must live. 
        /// 
        protected override Size ArrangeOverride(Size arrangeSize) 
        {

            if (_viewportHeight != arrangeSize.Height ||
                _viewportWidth != arrangeSize.Width) 
            {
                //Update our Viewport sizes 
                _viewportWidth = arrangeSize.Width; 
                _viewportHeight = arrangeSize.Height;
 
                if( LockViewModes && IsViewLoaded())
                {
                    if (_firstVisiblePageNumber < _pageCache.PageCount && _rowCache.HasValidLayout)
                    { 
                        //If we�re locking the view modes and we have loaded content, then we need to re-apply
                        //the last mode setting since our constraint has changed 
                        ApplyViewParameters(_rowCache.GetRowForPageNumber(_firstVisiblePageNumber)); 
                        MeasureOverride(arrangeSize);
                    } 
                }

                UpdateTextView();
            } 

            //If we have a non-zero viewport size, we should execute any 
            //requests for layout that may have been made but were unable 
            //to complete due to a zero viewport size.
            if (IsViewportNonzero) 
            {

                if (ExecutePendingLayoutRequests())
                { 
                    //We need to re-do layout (RowCache has changed), so call measure here
                    //to ensure everything's updated accordingly. 
                    MeasureOverride(arrangeSize); 
                }
 
            }

            //If our constraint size has changed then we need to
            //alert our parents so they can update their ViewportWidth/Height properties. 
            if ( _previousConstraint != arrangeSize)
            { 
                _previousConstraint = arrangeSize; 
                InvalidateDocumentScrollInfo();
            } 

            //Now we go through the visible rows and arrange the pages within them.
            //Get our visual collection count
            int count = _childrenCollection.Count; 

            //If we have no visual children, there's nothing to arrange 
            //so quit now. 
            if (count == 0)
            { 
                return arrangeSize;
            }

            //Arrange the background first.  This is always child 0. 
            //The background takes up the entire constraint.
            UIElement background =_childrenCollection[_backgroundVisualIndex] as UIElement; 
            background.Arrange(new Rect(new Point(0, 0), arrangeSize)); 

            //The offsets for the current page being arranged. 
            double xOffset;
            double yOffset;

            //The current visual child (aka DocumentGridPage) we're arranging. 
            //The first child in our tree is always the background so we start at
            //1 which is our first page. 
            int visualChild = _firstPageVisualIndex; 

            //Now walk through the visible rows and arrange the pages therein. 
            for (int row = _firstVisibleRow; row < _firstVisibleRow + _visibleRowCount; row++)
            {
                //Calculate the position for this row.
                CalculateRowOffsets(row, out xOffset, out yOffset); 

                //Get the current row. 
                RowInfo currentRow = _rowCache.GetRow(row); 

                //Now we can lay out this row. 
                for (int page = currentRow.FirstPage; page < currentRow.FirstPage + currentRow.PageCount; page++)
                {
                    //This should never, ever happen so we'll throw if it does.
                    if (visualChild > _childrenCollection.Count - 1) 
                    {
                        throw new InvalidOperationException(SR.Get(SRID.DocumentGridVisualTreeOutOfSync)); 
                    } 

                    //Get the cached size of this page. 
                    Size pageSize = _pageCache.GetPageSize(page);

                    //Scale it by our scale factor
                    pageSize.Width *= Scale; 
                    pageSize.Height *= Scale;
 
                    //Arrange the page if necessary 
                    UIElement uiPage = _childrenCollection[visualChild] as UIElement;
                    if (uiPage != null) 
                    {
                        Point pageOffset;
                        //Move the page to the right place based on the FlowDirection of the content.
                        if (_pageCache.IsContentRightToLeft) 
                        {
                            pageOffset = new Point(Math.Max(ViewportWidth, ExtentWidth) - (xOffset + pageSize.Width), yOffset); 
                        } 
                        else
                        { 
                            pageOffset = new Point(xOffset, yOffset);
                        }
                        uiPage.Arrange(new Rect(pageOffset, pageSize));
                    } 
                    else
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.DocumentGridVisualTreeContainsNonUIElement)); 
                    }
 
                    //Increment our horizontal offset to point to where the next page should go.
                    xOffset += (pageSize.Width+HorizontalPageSpacing);

                    //Move to the next page. 
                    visualChild++;
                } 
            } 

            // As we scroll we need to keep the AdornerLayer up-to-date. 
            // This ensures that annotation components scroll with the content.
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);
            if (layer != null && layer.GetAdorners(this) != null)
                layer.Update(this); 

            return arrangeSize; 
        } 

        ///  
        /// Override the OnPreviewMouseLeftButtonDown method so that we can trap the
        /// keyboard+mouse events needed for Rubberband selection.
        /// Clicking the Left mouse button while holding Alt will enable the Rubberband
        /// selection "mode" until the Left mouse button is again pressed without the 
        /// Alt key held.
        ///  
        /// The MouseButtonEventArgs associated with this mouse event. 
        protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
        { 

            //Determine whether either Alt key is being held at this moment.
            bool altKeyDown = Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt);
 
            //If the Alt key is held and we aren't currently in RubberBandSelection mode,
            //we can create and attach our RubberBandSelector now. 
            //We'll stay in this mode until the mouse is clicked without the Alt key held. 
            if (altKeyDown && _rubberBandSelector == null )
            { 
                //See if our content implements IServiceProvider.
                IServiceProvider serviceProvider = Content as IServiceProvider;
                if (serviceProvider != null)
                { 
                    //See if our content supports rubber band selection.
                    _rubberBandSelector = serviceProvider.GetService(typeof(RubberbandSelector)) as RubberbandSelector; 
 
                    if (_rubberBandSelector != null)
                    { 
                        DocumentViewerOwner.Focus(); // text editor needs to be focused when cleared
                        ITextRange textRange = TextEditor.Selection;
                        textRange.Select(textRange.Start, textRange.Start); //clear selection
                        DocumentViewerOwner.IsSelectionEnabled = false; 

                        _rubberBandSelector.AttachRubberbandSelector((FrameworkElement)this); //attach the Rubber band selector. 
 
                    }
                } 
            }
            //We got a mouse-down event and the Alt key is not being held, so we revert back
            //to normal selection mode now.
            else if (!altKeyDown && _rubberBandSelector != null) 
            {
                //Detach the Rubberband Selector 
                if (_rubberBandSelector != null) 
                {
                    _rubberBandSelector.DetachRubberbandSelector(); 
                    _rubberBandSelector = null;
                }

                DocumentViewerOwner.IsSelectionEnabled = true; 
            }
 
        } 

        #endregion Protected Methods 

        //-----------------------------------------------------
        //
        //  Private Methods 
        //
        //------------------------------------------------------ 
        #region Private Methods 

        ///  
        /// Recalculates the set of pages that are currently visible and updates
        /// DocumentGrid's VisualCollection so it contains them.
        /// 
        /// The viewport to search for visible pages in. 
        /// The offset in the document to start the search.
        private void RecalculateVisualPages(double offset, Size constraint) 
        { 

            //Do we actually have any rows in the cache? 
            //If not, we can just clear our visual collection and return.
            if (_rowCache.RowCount == 0)
            {
                ResetVisualTree(false /*pruneOnly*/); 
                ResetPageViewCollection();
                _firstVisibleRow = 0; 
                _visibleRowCount = 0; 
                _firstVisiblePageNumber = 0;
                _lastVisiblePageNumber = 0; 

                // Perf Tracing - PageVisible Changed
                if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
                { 
                    EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEVISIBLEGUID), MS.Utility.EventType.Info, _firstVisiblePageNumber, _lastVisiblePageNumber);
                } 
 
                return;
            } 

            int newFirstVisibleRow = 0;
            int newVisibleRowCount = 0;
 
            //Ask the RowCache for the currently visible rows.
            _rowCache.GetVisibleRowIndices(offset, 
                                            offset + constraint.Height, 
                                            out newFirstVisibleRow,
                                            out newVisibleRowCount); 

            //Do we have no visible rows at all?  Then clear the Visual collection and return.
            if (newVisibleRowCount == 0)
            { 
                ResetVisualTree(false /*pruneOnly*/);
                ResetPageViewCollection(); 
                _firstVisibleRow = 0; 
                _visibleRowCount = 0;
                _firstVisiblePageNumber = 0; 
                _lastVisiblePageNumber = 0;

                // Perf Tracing - PageVisible Changed
                if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
                {
                    EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEVISIBLEGUID), MS.Utility.EventType.Info, _firstVisiblePageNumber, _lastVisiblePageNumber); 
                } 

                return; 
            }

            //Now walk through each visible row and compare the pages therein
            //with the current set of pages in our Visual Collection. 
            //New pages are inserted into the collection, unused pages are removed.
 
            //Get the current first and last pages visible. 
            int firstPage = -1;
            int lastPage = -1; 

            //If we have more visuals than just the background (element 0)
            //then we have pages, so get the page numbers from them.
            if (_childrenCollection.Count > _firstPageVisualIndex) 
            {
                DocumentGridPage firstDp = _childrenCollection[1] as DocumentGridPage; 
                firstPage = firstDp != null ? firstDp.PageNumber : -1; 

                DocumentGridPage lastDp = _childrenCollection[_childrenCollection.Count-1] as DocumentGridPage; 
                lastPage = lastDp != null ? lastDp.PageNumber : -1;
            }

 
            //Update our First & LastVisiblePage properties
            RowInfo firstRow = _rowCache.GetRow(newFirstVisibleRow); 
            _firstVisiblePageNumber = firstRow.FirstPage; 
            RowInfo lastRow = _rowCache.GetRow(newFirstVisibleRow + newVisibleRowCount - 1);
            _lastVisiblePageNumber = lastRow.FirstPage + lastRow.PageCount - 1; 

            // Perf Tracing - PageVisible Changed
            if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
            { 
                EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEVISIBLEGUID), MS.Utility.EventType.Info, _firstVisiblePageNumber, _lastVisiblePageNumber);
            } 
 
            //Update our cached visible row info (used by Measure/Arrange)
            _firstVisibleRow = newFirstVisibleRow; 
            _visibleRowCount = newVisibleRowCount;


            //If IDSI properties have changed (namely the First/LastVisiblePage properties) we invalidate them now. 
            if (_firstVisiblePageNumber != firstPage ||
                _lastVisiblePageNumber != lastPage) 
            { 

                //Create our temporary VisualCollection, which will hold the new list of 
                //visible pages.
                ArrayList visiblePages = new ArrayList();

                //Now walk through the visible rows and add the pages to our temporary list. 
                for (int i = _firstVisibleRow; i < _firstVisibleRow + _visibleRowCount; i++)
                { 
                    //Get the row 
                    RowInfo currentRow = _rowCache.GetRow(i);
 
                    //Walk through the row
                    for (int j = currentRow.FirstPage; j < currentRow.FirstPage + currentRow.PageCount; j++)
                    {
                        //Is this page new? 
                        if (j < firstPage || j > lastPage || _childrenCollection.Count <= _firstPageVisualIndex)
                        { 
                            //Create a new page and add it to our temporary visual collection. 
                            DocumentGridPage dp = new DocumentGridPage(Content);
                            dp.ShowPageBorders = ShowPageBorders; 
                            dp.PageNumber = j;

                            //Attach the Loaded event handler
                            dp.PageLoaded += new EventHandler(OnPageLoaded); 
                            visiblePages.Add(dp);
                        } 
                        else 
                        {
                            //This page already exists in our visual collection, so we copy that entry over 
                            //from the visual collection instead of creating a new page.
                            //(We start at 1 to skip over the background visual.)
                            visiblePages.Add(_childrenCollection[_firstPageVisualIndex + j - Math.Max(0, firstPage)]);
                        } 
                    }
                } 
 
                //Copy our new visible page collection over to the VisualCollection and update
                //the MultiPageTextView's list of visible DocumentPageViews. 
                //First, prune our visual tree so it only contains the set of pages that are visible
                //before and after the layout change.
                ResetVisualTree(true /*pruneOnly*/);
 
                Collection documentPageViews =
                    new Collection(); 
 
                //State machine for updating visual collection without removing still-visible pages
                //from the collection: 
                //We walk through the set of visible pages that we computed above.
                //We insert new pages before existing pages, and add pages after existing pages.
                //
                //We take advantage of the fact that both the current set of pages in the Visual Collection 
                //and the set of to-be-made visible pages in visiblePages are in strictly increasing order
                //with no gaps between pages. 
                //To better understand how the below works, refer to Fig. A below: 
                //
                //         +-----------------------------+ 
                //  +------+                             +------+
                //  | new  |  Pruned Visual Collection   |  new |
                //  +------+   (Existing Page Visuals)   +------+
                //         +-----------------------------+ 
                //  ^      ^                             ^
                //  |      |                             | 
                //  A      B                             C 
                //
                //                                 [Fig. A: Diagram of states] 
                //
                //- The routine starts off in state A (BeforeExisting).
                //  At this point we insert any new pages in the visiblePages collection until we
                //  find a page in the visiblePages collection that is also in the Pruned Visual Collection. 
                //  This indicates that the set of common unchanged pages has been reached.
                //  The state machine then transitions to state B (DuringExisting). 
                //- The routine stays in B merely iterating through visiblePages until it finds a page 
                //  in visiblePages that does not correspond to a page in the Visual Tree.  This indicates
                //  that the end of the set of common unchanged pages has been reached; at this point we 
                //  add the new page and transition to state C (AfterExisting).
                //- State C ends when no more pages are left in visiblePages.
                VisualTreeModificationState state = VisualTreeModificationState.BeforeExisting;
 
                //The index pointing to the first common page still in the visual tree after the above pruning.
                int vcIndex = _firstPageVisualIndex; 
 
                for (int i = 0; i < visiblePages.Count; i++)
                { 
                    Visual current = (Visual)visiblePages[i];

                    switch (state)
                    { 
                        case VisualTreeModificationState.BeforeExisting:
                            //Keep inserting until we find a page that already exists 
                            if (vcIndex < _childrenCollection.Count && _childrenCollection[vcIndex] == current) 
                            {
                                //Move to "During" state 
                                state = VisualTreeModificationState.DuringExisting;
                            }
                            else
                            { 
                                //Insert this page at the current index.
                                _childrenCollection.Insert(vcIndex, current); 
                            } 
                            //Increment the index into the Visual collection to ensure that it continues
                            //to point to the first common page. 
                            vcIndex++;
                            break;

                        case VisualTreeModificationState.DuringExisting: 
                            //Leave the visual collection alone until we find a page that isn't in the collection
                            //or run out of pages in the collection. 
                            if (vcIndex >= _childrenCollection.Count || _childrenCollection[vcIndex] != current) 
                            {
                                //Move to "After" state 
                                state = VisualTreeModificationState.AfterExisting;
                                //Append this page to the end.
                                _childrenCollection.Add(current);
                            } 
                            //Keep moving through the Visual collection...
                            vcIndex++; 
                            break; 

                        case VisualTreeModificationState.AfterExisting: 
                            //Keep going until the end.
                            _childrenCollection.Add(current);
                            break;
                    } 

                    //Add this to the collection of PageViews. 
                    documentPageViews.Add(((DocumentGridPage)visiblePages[i]).DocumentPageView); 
                }
 
                //Update our collection of PageViews with the current set.
                _pageViews = new ReadOnlyCollection(documentPageViews);

                //Tell our parent DocumentViewer that we've updated our PageView collection. 
                InvalidatePageViews();
                InvalidateDocumentScrollInfo(); 
            } 

        } 

        /// 
        /// Handles the PageLoaded event for a given page, and kicks off
        /// BringIntoView actions where necessary. 
        /// 
        ///  
        ///  
        private void OnPageLoaded(object sender, EventArgs args)
        { 
            DocumentGridPage page = sender as DocumentGridPage;
            Invariant.Assert(page != null, "Invalid sender for OnPageLoaded event.");

            //Detach the event handler, we don't need this event any longer. 
            page.PageLoaded -= new EventHandler(OnPageLoaded);
 
            //Is there a MakeVisible operation waiting for this page to be loaded? 
            //If so, invoke its dispatcher in the background.
            if (_makeVisiblePageNeeded == page.PageNumber) 
            {
                _makeVisiblePageNeeded = -1;
                _makeVisibleDispatcher.Priority = DispatcherPriority.Background;
            } 

            // Perf Tracing - PageLoaded 
            if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
            {
                EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGELOADEDGUID), MS.Utility.EventType.Info, page.PageNumber); 
            }
        }

 
        /// 
        /// Calculates the X and Y offsets of the given row based on the current 
        /// Viewport dimensions. 
        /// 
        /// The row to calculate the offsets of 
        /// The X offset of the row
        /// The Y offset of the row
        private void CalculateRowOffsets(int row, out double xOffset, out double yOffset)
        { 
            xOffset = 0.0;
            yOffset = 0.0; 
 
            //Get the current row.
            RowInfo currentRow = _rowCache.GetRow(row); 

            //Figure out the width we'll use to center the pages.
            //If the ViewportWidth is wider than the document, we use that.  Otherwise we center
            //the content based on the width of the document. 
            double centerWidth = Math.Max(ViewportWidth, ExtentWidth);
 
            //Figure out the offset of the upper left corner of this row. 

            //X Coordinate: 
            //If this is the last row in the document and we're viewing
            //uniformly-sized pages then this row is
            //always left-aligned (not centered).
            if (row == _rowCache.RowCount -1 && !_pageCache.DynamicPageSizes) 
            {
                //This is the last row, so we arrange it such that the left edge of the 
                //page is flush with the left edge of the document. 
                xOffset = (centerWidth - ExtentWidth) / 2.0 +
                    (HorizontalPageSpacing / 2.0) - HorizontalOffset; 
            }
            else
            {
                //Otherwise we center this page inside the document. 
                xOffset = (centerWidth - currentRow.RowSize.Width) / 2.0 +
                    (HorizontalPageSpacing / 2.0) - HorizontalOffset; 
            } 

            //Y Coordinate: 
            if (ExtentHeight > ViewportHeight)
            {
                //The document is taller than the viewport, so we just display
                //the content at the current offset. 
                yOffset = currentRow.VerticalOffset +
                    (VerticalPageSpacing / 2.0) - VerticalOffset; 
            } 
            else
            { 
                //If the document is shorter than the Viewport we're showing it in,
                //we center it vertically within the viewport.  We do not need to factor in
                //VerticalOffset as it is always 0.0 in this scenario.
                yOffset = currentRow.VerticalOffset + 
                    (ViewportHeight - ExtentHeight) / 2.0 + (VerticalPageSpacing / 2.0);
            } 
        } 

        ///  
        /// Resets DocumentGrid's visual tree to its initial state or prunes non-visible pages.
        /// This is empty except for a border which acts as a background.
        /// 
        /// Whether to clear all pages, or only those that are not visible. 
        private void ResetVisualTree(bool pruneOnly)
        { 
 
            //We need to dispose and remove any pages that will no longer be in the visual tree.
            for (int i = _childrenCollection.Count - 1; i >= _firstPageVisualIndex; i--) 
            {
                DocumentGridPage dp = _childrenCollection[i] as DocumentGridPage;
                if (dp != null &&
                        (!pruneOnly || 
                        _rowCache.RowCount == 0 ||
                        dp.PageNumber < _firstVisiblePageNumber || 
                        dp.PageNumber > _lastVisiblePageNumber)) 
                {
                    //This page will not be visible any longer, so get rid of it. 
                    //Remove this page from the Visual tree.
                    _childrenCollection.Remove(dp);

                    //Remove any PageLoaded event handlers 
                    dp.PageLoaded -= new EventHandler(OnPageLoaded);
 
                    //Dispose of the page. 
                    ((IDisposable)dp).Dispose();
                } 
            }

            //Create the background if it does not exist.
            if (_documentGridBackground == null) 
            {
                //We create a Border with a transparent background so that it can 
                //participate in Hit-Testing (which allows click events like those 
                //for our Context Menu to work).
                _documentGridBackground = new Border(); 
                _documentGridBackground.Background = Brushes.Transparent;

                //Add the background in.
                _childrenCollection.Add(_documentGridBackground); 
            }
 
        } 

        ///  
        /// Nulls out the PageViews collection and notifies DocumentViewer of the change.
        /// 
        private void ResetPageViewCollection()
        { 
            //Null out our collection of PageViews.
            _pageViews = null; 
 
            //Tell our parent DocumentViewer that we've updated our PageView collection.
            InvalidatePageViews(); 
        }

        #region MakeVisible Helpers
 
        /// 
        /// Handles the GetPageNumberCompleted event fired as a result of a MakeContentVisibleAsync 
        /// call.  At this point we know the page number corresponding to the ContentPosition we need 
        /// to make visible, so we invoke MakeVisibleAsync() to bring it into view.
        ///  
        /// The sender of this event
        /// The args associated with this event.
        /// We expect e.UserState to be a MakeVisibleData.
        private void OnGetPageNumberCompleted(object sender, GetPageNumberCompletedEventArgs e) 
        {
            if (e == null) 
            { 
                throw new ArgumentNullException("e");
            } 
            //Ensure that the UserState passed with this event contains an
            //MakeVisibleData object. If not, we ignore it as this event
            //could have originated from someone else calling GetPageNumberAsync.
            if (e.UserState is MakeVisibleData) 
            {
                MakeVisibleData data = (MakeVisibleData)e.UserState; 
                MakeVisibleAsync(data, e.PageNumber); 
            }
        } 


        /// 
        /// Makes the specified object on the specified page visible, which may be an 
        /// asynchronous operation if the page is not already in view.
        ///  
        /// Data corresponding to the object to be made visible. 
        /// The page number the object is on.
        private void MakeVisibleAsync(MakeVisibleData data, int pageNumber) 
        {

            //This page may not be currently visible.
            //First we need to make the page visible, if necessary. 
            //This will be done at background priority to allow currently-loading pages time to
            //finish.  If we do not do this, then in the corner case of: 
            // 1) Document has just been loaded (for example, just after a hyperlink navigation to the doc) 
            // 2) Document has non 8.5x11-sized pages at the beginning of the document
            // 3) Navigation is to a page past the initially visible pages. 
            //In this case, the order of operations is something like this:
            // 1) Initially visible pages start loading
            // 2) MakeVisible is invoked, and MakePageVisible is called, which uses cached
            //    page info to decide where to scroll to.  (8.5x11 is assumed until a page is loaded) 
            // 3) Document is scrolled to position computed in #2
            // 4) Pages loading in #1 finish loading, GetPageCompleted is called, PageCache is updated, 
            //    and the document layout changes.  This will shift the page we actually want to see up or down, 
            //    potentially by a substantial amount.
            // 5) User is now looking at the wrong page, and if the page that the hyperlink target is on isn't 
            //    visible at this point then the MakeVisible operation fails in MakeVisibleImpl since the target
            //    isn't in the visual tree.
            // Everyone got that?  So, doing the initial "MakePageVisible" operation at Background priority
            // allows step #4 above to finish _before_ we do steps 2 and 3, so the right page will be visible 
            // in 5 and the MakeVisible operation will succeed.
            Dispatcher.BeginInvoke(DispatcherPriority.Background, 
                   new BringPageIntoViewCallback( BringPageIntoViewDelegate ), data, pageNumber); 

        } 

        /// 
        /// Delegate method used to bring a specified page into view.
        ///  
        /// 
        ///  
        private void BringPageIntoViewDelegate(MakeVisibleData data, int pageNumber) 
        {
 
            //Make the page visible if necessary:
            // - If our layout is not yet valid
            // - If the visual being made visible is a FixedPage and
            //   the bring-into-view rect is the entire page 
            //   (in which case we always want to move it as close to the top of the
            //   viewport as possible even if it is already partially visible) 
            // - If the page isn't currently visible. 
            if (!_rowCache.HasValidLayout ||
                (data.Visual is FixedPage && 
                 data.Visual.VisualContentBounds == data.Rect) ||
                pageNumber < _firstVisiblePageNumber ||
                pageNumber > _lastVisiblePageNumber)
            { 
                MakePageVisible(pageNumber);
            } 
 
            //The page's contents have already been loaded, we can bring the object into view immediately.
            if (IsPageLoaded(pageNumber)) 
            {
                MakeVisibleImpl(data);
            }
            else 
            {
                //Now we have to wait for the page to be loaded so that we can 
                //ensure that the object itself is visible. 
                //As pages are loaded, this page will be checked for, and the dispatcher below
                //executed as appropriate. 
                _makeVisiblePageNeeded = pageNumber;
                _makeVisibleDispatcher = Dispatcher.BeginInvoke(DispatcherPriority.Inactive,
                    (DispatcherOperationCallback)delegate(object arg)
                    { 
                        MakeVisibleImpl((MakeVisibleData)arg);
                        return null; 
                    }, data); 

            } 
        }

        /// 
        /// Implementation of MakeVisible logic, the final step in a MakeVisible operation. 
        /// 
        ///  
        private void MakeVisibleImpl(MakeVisibleData data) 
        {
            if (data.Visual != null) 
            {
                //Ensure that the passed-in visual is a descendant of DocumentGrid.
                if (((Visual)this).IsAncestorOf(data.Visual))
                { 
                    //Now we can determine where this visual is relative to the upper left
                    //corner of the DocumentGrid and thus make it visible. 
                    GeneralTransform transform = data.Visual.TransformToAncestor(this); 
                    Rect boundingRect = (data.Rect != Rect.Empty) ? data.Rect : data.Visual.VisualContentBounds;
 
                    Rect offsetRect = transform.TransformBounds(boundingRect);
                    MakeRectVisible(offsetRect, false /* alwaysCenter */);
                }
            } 
            else if (data.ContentPosition != null)
            { 
                ITextPointer tp = data.ContentPosition as ITextPointer; 

                //If we have a valid TextView and the TextPointer is in that TextView 
                //we can make the TextPointer's Rect visible...
                if (TextViewContains(tp))
                {
                    MakeRectVisible(TextView.GetRectangleFromTextPosition(tp), false /* alwaysCenter */); 
                }
            } 
            else 
            {
                Invariant.Assert(false, "Invalid object brought into view."); 
            }
        }

 
        /// 
        /// Moves the specified rectangle into view, if it isn't already visible. 
        ///  
        /// A rectangle relative to the upper-left corner of the Viewport
        /// Whether to center the rect at all times or only when necessary. 
        private void MakeRectVisible(Rect r, bool alwaysCenter)
        {
            if (r != Rect.Empty)
            { 
                //Calculate the real position of the rectangle in the document.
                Rect translatedRect = new Rect(HorizontalOffset + r.X, VerticalOffset + r.Y, 
                                               r.Width, r.Height); 

                Rect viewportRect = new Rect(HorizontalOffset, VerticalOffset, 
                                             ViewportWidth,
                                             ViewportHeight);

                //Unless the alwaysCenter flag is set, if the new position is already 
                //visible we don't need to shift the viewport. Otherwise we shift
                //the offsets so the rect is visible, centering if possible. 
                if (alwaysCenter || !translatedRect.IntersectsWith(viewportRect)) 
                {
                    SetHorizontalOffsetInternal(translatedRect.X - (ViewportWidth / 2.0)); 
                    SetVerticalOffsetInternal(translatedRect.Y - (ViewportHeight / 2.0));
                }
            }
        } 

        ///  
        /// Moves the specified IP into view, if it isn't already visible. 
        /// 
        /// A rectangle relative to the upper-left corner of the Viewport which represents 
        /// an IP (Insertion Point)
        private void MakeIPVisible(Rect r)
        {
            if (r != Rect.Empty && TextEditor != null) 
            {
                Rect viewportRect = new Rect(HorizontalOffset, VerticalOffset, 
                                             ViewportWidth, 
                                             ViewportHeight);
 
                //If the new position is already fully visible, we don't need to shift the viewport,
                //otherwise we shift the offsets so the rect is visible, moving as minimally as possible.
                if (!viewportRect.Contains(r))
                { 
                    //Scroll left/right if the IP is off the screen Horizontally.
                    if (r.X < HorizontalOffset) 
                    { 
                        SetHorizontalOffsetInternal(HorizontalOffset - (HorizontalOffset - r.X));
                    } 
                    else if (r.X > HorizontalOffset + ViewportWidth)
                    {
                        SetHorizontalOffsetInternal(HorizontalOffset + (r.X - (HorizontalOffset + ViewportWidth)));
                    } 

                    //Scroll up/down if part of the IP is off the screen Vertically. 
                    if (r.Y < VerticalOffset) 
                    {
                        SetVerticalOffsetInternal(VerticalOffset - (VerticalOffset - r.Y)); 
                    }
                    else if (r.Y + r.Height > VerticalOffset + ViewportHeight)
                    {
                        SetVerticalOffsetInternal(VerticalOffset + ((r.Y + r.Height) - (VerticalOffset + ViewportHeight))); 
                    }
                } 
            } 
        }
 
        /// 
        /// Invokes GetPageNumberAsync on the passed in ContentPosition.
        /// The handler for GetPageNumberAsync will bring that ContentPosition into view.
        ///  
        /// The MakeVisibleData to be made visible
        private void MakeContentPositionVisibleAsync(MakeVisibleData data) 
        { 
            //If the ContentPosition is valid, we can make it visible now.
            if (data.ContentPosition != null && data.ContentPosition != ContentPosition.Missing) 
            {
                Content.GetPageNumberAsync(data.ContentPosition, data);
            }
        } 

        #endregion MakeVisible Helpers 
 
        /// 
        /// Places a delegate for an SetScale call on the queue.  We do this for 
        /// performance reasons, as changing the document scale takes significant time.
        /// 
        /// 
        private void QueueSetScale(double scale) 
        {
            //If there's a SetScale operation in the Pending state, then we'll 
            //abort it (we only care that the last operation invoked completes.) 
            if (_setScaleOperation != null &&
                _setScaleOperation.Status == DispatcherOperationStatus.Pending) 
            {
                _setScaleOperation.Abort();
            }
            _setScaleOperation = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, 
                   new DispatcherOperationCallback(SetScaleDelegate),
                   scale); 
        } 

        private object SetScaleDelegate(object scale) 
        {
            if (!(scale is double))
            {
                return null; 
            }
 
            double newScale = (double)scale; 
            _documentLayout.ViewMode = ViewMode.Zoom;
 
            //Get the current visible selection, if any.
            //The results of this will determine how we handle the
            //zoom operation.
            ITextPointer selection = GetVisibleSelection(); 

            if (selection != null) 
            { 
                //The visible-IP case:
                //First, we find out what page the IP is on: 
                int selectionPage = GetPageNumberForVisibleSelection(selection);

                //Then we scale the document:
                UpdateLayoutScale(newScale); 

                //Now we ensure that the selection page is still 
                //visible: 
                MakePageVisible(selectionPage);
 
                //The rest of this process is done asynchronously --
                //We wait for LayoutUpdated (which happens after layout
                //but before rendering) and then make the IP visible.
                //This will cause the IP to be centered without any 
                //visible flicker.
 
                //Attach a LayoutUpdated handler. 
                LayoutUpdated += new EventHandler(OnZoomLayoutUpdated);
 
            }
            else
            {
                //The non-visible-IP case: 
                //This is considerably easier.  The expected behavior is that
                //we zoom in on the upper-left corner of the currently-visible 
                //content.  This is accomplished by scaling the Vertical and 
                //Horizontal offsets in tandem with the document scale which will
                //put us approximately where we were before. 

                //Scale the document:
                UpdateLayoutScale(newScale);
            } 

            return null; 
        } 

        ///  
        /// Updates the Scale applied to our RowCache.
        /// 
        /// 
        private void UpdateLayoutScale(double scale) 
        {
            if (!DoubleUtil.AreClose(scale, Scale)) 
            { 
                double oldExtentHeight = ExtentHeight;
                double oldExtentWidth = ExtentWidth; 

                //Tell our RowCache to rescale the layout.
                _rowCache.Scale = scale;
 
                //Rescale our offsets
                //Divide the old extents by the new to determine the amount to 
                //scale the offsets 
                double verticalScale = oldExtentHeight == 0.0 ? 1.0 : ExtentHeight / oldExtentHeight;
                double horizontalScale = oldExtentWidth == 0.0 ? 1.0 : ExtentWidth / oldExtentWidth; 

                //Now we scale the offsets.
                SetVerticalOffsetInternal(_verticalOffset * verticalScale);
                SetHorizontalOffsetInternal(_horizontalOffset * horizontalScale); 

                InvalidateMeasure(); 
 
                //Invalidate the measure of our visual children so that they can
                //be resized. 
                InvalidateChildMeasure();

                //Invalidate our parents' properties
                InvalidateDocumentScrollInfo(); 
            }
        } 
 
        /// 
        /// Places a delegate for an UpdateDocumentLayout call on the queue.  We do this for 
        /// performance reasons, as changing the document layout takes significant time.
        /// 
        /// 
        private void QueueUpdateDocumentLayout(DocumentLayout layout) 
        {
            // Increase the count of pending DocumentLayout delegates 
            _documentLayoutsPending++; 
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input,
                   new DispatcherOperationCallback(UpdateDocumentLayoutDelegate), 
                   layout);
        }

        ///  
        /// Asynchronously invokes UpdateDocumentLayout.
        ///  
        ///  
        /// 
        private object UpdateDocumentLayoutDelegate(object layout) 
        {
            if (layout is DocumentLayout)
            {
                UpdateDocumentLayout((DocumentLayout)layout); 
            }
            // Decrease the count of pending DocumentLayout delegates 
            _documentLayoutsPending--; 

            return null; 
        }

        /// 
        /// Updates the current layout of our RowCache to the specified number of 
        /// columns.
        ///  
        ///  
        private void UpdateDocumentLayout(DocumentLayout layout)
        { 
            // Check if the layout is for a Vertical or Horizontal offset update,
            // in which case the value can be changed immediately.
            if (layout.ViewMode == ViewMode.SetHorizontalOffset)
            { 
                SetHorizontalOffsetInternal(layout.Offset);
                return; 
            } 
            else if (layout.ViewMode == ViewMode.SetVerticalOffset)
            { 
                SetVerticalOffsetInternal(layout.Offset);
                return;
            }
 
            //Store off the layout in case we need it later.
            //(For example, if our viewport is (0,0). 
            _documentLayout = layout; 

            //Update MaxPagesAcross 
            _maxPagesAcross = _documentLayout.Columns;

            //If we have a non (0,0) Viewport then we can calculate a new layout.
            //Otherwise we set our "Layout Requested" flag so that when we get 
            //a non-zero Viewport size we'll compute the requested layout.
            if (IsViewportNonzero) 
            { 
                //If this is a Thumbnails layout, we need to calculate how many
                //columns we should fit on the pivotRow. 
                if (_documentLayout.ViewMode == ViewMode.Thumbnails)
                {
                    _maxPagesAcross = _documentLayout.Columns = CalculateThumbnailColumns();
                } 

                //We need to determine the page that has the active focus so we know what page 
                //to keep visible in the new layout. 
                int pivotPage = GetActiveFocusPage();
 
                //Ask the RowCache to recalculate the layout based
                //on the specified pivot page and the number of columns
                //requested.
                //The RowCache will call us back with a 
                //RowLayoutCompleted event when the layout is complete
                //and we'll update the scale and invalidate our layout there. 
                _rowCache.RecalcRows(pivotPage, _documentLayout.Columns); 

                _isLayoutRequested = false; 
            }
            else
            {
                _isLayoutRequested = true; 
            }
        } 
 
        /// 
        /// Calls UpdateLayout with the saved column and view mode parameters, 
        /// if there's a previously requested layout to perform.
        /// Used when we get a non-zero Viewport size and we've previously requested
        /// a new Row layout.
        ///  
        /// A bool indicating whether a new layout was calculated.
        private bool ExecutePendingLayoutRequests() 
        { 
            if (_isLayoutRequested)
            { 
                UpdateDocumentLayout( _documentLayout );
                return true;
            }
 
            return false;
        } 
 
        /// 
        /// Set the HorizontalOffset to the value provided, the value will be set immediately. 
        /// 
        /// 
        private void SetHorizontalOffsetInternal(double offset)
        { 
            if (!DoubleUtil.AreClose(_horizontalOffset, offset))
            { 
                if (Double.IsNaN(offset)) 
                {
                    throw new ArgumentOutOfRangeException("offset"); 
                }

                _horizontalOffset = offset;
                InvalidateMeasure(); 
                InvalidateDocumentScrollInfo();
                UpdateTextView(); 
            } 
        }
 
        /// 
        /// Set the VerticalOffset to the value provided, the value will be set immediately.
        /// 
        ///  
        private void SetVerticalOffsetInternal(double offset)
        { 
            if (!DoubleUtil.AreClose(_verticalOffset, offset)) 
            {
                if (Double.IsNaN(offset)) 
                {
                    throw new ArgumentOutOfRangeException("offset");
                }
 
                _verticalOffset = offset;
                InvalidateMeasure(); 
                InvalidateDocumentScrollInfo(); 
                UpdateTextView();
            } 
        }

        /// 
        /// Updates the TextView so that it knows about size and position changes. 
        /// 
        private void UpdateTextView() 
        { 
            MultiPageTextView tv = TextView as MultiPageTextView;
            if (tv != null) 
            {
                tv.OnPageLayoutChanged();
            }
        } 

        ///  
        /// Calculates the number of columns to fit on one row so that the resultant 
        /// view will approximate a "thumbnail" view.
        /// The basic idea is that we attempt to fit (and scale) a number of pages on the row 
        /// such that the resultant view given the current Viewport will show as many
        /// pages as possible, with a lower bound of a 5% zoom.
        /// We attempt to optimize to minimize wasted space, where possible.  This may mean
        /// that not all pages will be completely displayed, but we favor a better looking 
        /// layout (less dead space) over being able to see every page.
        ///  
        /// The number of pages to show on the first row. 
        private int CalculateThumbnailColumns()
        { 
            //If our current Viewport size is zero, we'll just return 1.
            //(because there always needs to be at least 1 page on a row regardless.)
            if (!IsViewportNonzero)
            { 
                return 1;
            } 
 
            //If we have no pages, we'll just return 1
            //(because there always needs to be at least 1 page on a row regardless.) 
            if (_pageCache.PageCount == 0)
            {
                return 1;
            } 

            //We use the first page of the document as our basis for our calculations. 
            //This means that documents with varying page sizes can potentially have 
            //sub-optimal thumbnail views.
            Size pageSize = _pageCache.GetPageSize(0); 

            //Calculate the viewport's aspect ratio.
            double viewportAspect = ViewportWidth / ViewportHeight;
 
            //Calculate the maximum number of columns we can lay out on a single row
            //without needing to scale below our floor of 12.5%. 
            int maxColumns = 
                (int)Math.Floor( ViewportWidth /
                    (CurrentMinimumScale * pageSize.Width + HorizontalPageSpacing)); 

            //Ensure this value isn't greater than the number of pages in the document,
            //since we can't possibly lay out a row with more than that number of pages in it.
            maxColumns = Math.Min(maxColumns, _pageCache.PageCount); 
            maxColumns = Math.Min(maxColumns, DocumentViewerConstants.MaximumMaxPagesAcross);
 
            //Now we do the following: 
            //We iterate through the possible permutations of row and column
            //combinations and choose the arrangement of columns that best fits the current 
            //viewport's aspect ratio.
            int minAspectColumns = 1;   //The current optimal number of columns found.
            double minAspectDiff = Double.MaxValue; //The current optimal aspect ratio match
            for (int columns = 1; columns <= maxColumns; columns++) 
            {
                //Calculate the number of rows for this arrangment given the current column count 
                int rows = (int)Math.Floor((double)(_pageCache.PageCount / columns)); 

                //Calculate the approximate dimensions that this layout would 
                //have.
                double width = pageSize.Width * columns;
                double height = pageSize.Height * rows;
 
                //Determine the aspect ratio of this layout.
                double layoutAspect = width / height; 
 
                //See if the aspect ratio of this layout is a closer match for our Viewport
                //than previous attempts. 
                double aspectDiff = Math.Abs(layoutAspect - viewportAspect);
                if ( aspectDiff < minAspectDiff)
                {
                    //It is, so save it. 
                    minAspectDiff = aspectDiff;
                    minAspectColumns = columns; 
                } 
            }
 
            return minAspectColumns;

        }
 
        /// 
        /// Calls InvalidateMeasure() on our visual children in order to force them 
        /// to be re-measured and arranged on the next layout pass.  This is called 
        /// whenever the Scale is changed, as it is only then when re-measuring is
        /// required.  We do this to avoid unnecessary layout/measure passes on our 
        /// pages.
        /// 
        private void InvalidateChildMeasure()
        { 
            //Get the current Visual Collection, which contains our pages.
            int count = _childrenCollection.Count; 
 
            for (int i = 0; i < count; i++)
            { 
                UIElement page = _childrenCollection[i] as UIElement;

                if (page != null)
                { 
                    page.InvalidateMeasure();
                } 
            } 
        }
 
        /// 
        /// Helper function that indicates whether all the pages on the specified
        /// row point to clean cache entries.
        ///  
        /// 
        ///  
        private bool RowIsClean(RowInfo row) 
        {
            bool clean = true; 

            for (int i = row.FirstPage; i < row.FirstPage + row.PageCount; i++)
            {
                if (_pageCache.IsPageDirty(i)) 
                {
                    clean = false; 
                    break; 
                }
            } 

            return clean;
        }
 
        /// 
        /// Checks that the current scale factor is optimal for the passed in row. 
        ///  
        /// The Row to pass to the Delegate
        private void EnsureFit(RowInfo pivotRow) 
        {
            //Get the scale factor necessary to fit this row into view.
            //If the result is not 1.0 (within a certain margin of error)
            //then we need to re-layout, alas. 
            double neededScaleFactor = CalculateScaleFactor(pivotRow);
            double newScale = neededScaleFactor * _rowCache.Scale; 
 
            //If the neededScaleFactor would require DocumentGrid scale the pages
            //below the minimum allowed zoom, or above the maximum, then we won't 
            //do anything here.
            if (newScale < CurrentMinimumScale ||
                newScale > DocumentViewerConstants.MaximumScale)
            { 
                return;
            } 
 
            if (!DoubleUtil.AreClose(1.0, neededScaleFactor))
            { 
                //Rescale the row.
                ApplyViewParameters( pivotRow );

                //Make the row visible again -- the offsets may have 
                //changed due to the above rescaling.
                SetVerticalOffsetInternal(pivotRow.VerticalOffset); 
            } 

        } 

        /// 
        /// Given a pivot row and a previously set ViewMode, the scale is adjusted so as
        /// to cause the pivot row to be fit based on the specified ViewMode. 
        /// 
        ///  
        private void ApplyViewParameters(RowInfo pivotRow) 
        {
            //Update our MaxPagesAcross property to the number of rows on the pivot row 
            //if page sizes vary.  (If page sizes are uniform, this value will not change as a result of
            //a layout change)
            if (_pageCache.DynamicPageSizes)
            { 
                _maxPagesAcross = pivotRow.PageCount;
            } 
 
            //Get the scale factor necessary to fit the given row into the Viewport.
            double scaleFactor = CalculateScaleFactor(pivotRow); 

            //Calculate the new scale. We multiply our scale factor by the old scale factor to cancel out any
            //previously applied scale.
            double newScale = scaleFactor * _rowCache.Scale; 

            //Clip the value into the acceptable range 
            newScale = Math.Max(newScale, CurrentMinimumScale); 
            newScale = Math.Min(newScale, DocumentViewerConstants.MaximumScale);
 
            //Update the Row Layout's scale.
            UpdateLayoutScale(newScale);
        }
 
        private double CalculateScaleFactor(RowInfo pivotRow)
        { 
            //Determine the dimensions of this row minus any spacing between the pages. 
            //We use this as the baseline for our scale factor as page spacing does not scale.
            double rowWidth; 

            //If the page sizes vary, we use the width of the pivot row,
            //otherwise we use the overall width of the document (ExtentWidth).
            //(For uniform page sizes, we always use the width of the document, even 
            //for the last row which may not have the same width as the rest of the document).
            if (_pageCache.DynamicPageSizes) 
            { 
                rowWidth = pivotRow.RowSize.Width - pivotRow.PageCount * HorizontalPageSpacing;
            } 
            else
            {
                rowWidth = ExtentWidth - MaxPagesAcross * HorizontalPageSpacing;
            } 

            double rowHeight = pivotRow.RowSize.Height - VerticalPageSpacing; 
 
            //If we have row dimensions of zero or less, there's no reason to scale anything.
            //So just return 1.0 to indicate no change. 
            if (rowWidth <= 0.0 || rowHeight <= 0.0)
            {
                return 1.0;
            } 

            //The dimensions of our Viewport minus any spacing.  We use this as the baseline for our 
            //scale factor as page spacing does not scale. 
            double compensatedViewportWidth;
 
            if (_pageCache.DynamicPageSizes)
            {
                compensatedViewportWidth = ViewportWidth - pivotRow.PageCount * HorizontalPageSpacing;
            } 
            else
            { 
                compensatedViewportWidth = ViewportWidth - MaxPagesAcross * HorizontalPageSpacing; 
            }
 
            double compensatedViewportHeight = ViewportHeight - VerticalPageSpacing;

            //If we have no space to display pages, there's nothing to scale.
            //So just return 1.0 to indicate no change. 
            if (compensatedViewportWidth <= 0.0 ||
                compensatedViewportHeight <= 0.0) 
            { 
                return 1.0;
            } 

            double scaleFactor = 1.0;

            //Based on the previously determined ViewMode (set in SetColumns(), FitToWidth(), etc.. 
            //scale the pages appropriately.
            switch (_documentLayout.ViewMode) 
            { 
                case ViewMode.SetColumns:
                    //We leave the scale factor as is -- this is not a "page-fit" mode. 
                    break;

                case ViewMode.FitColumns:
                    //Update the scale factor so that the pivot row is completely visible. 
                    scaleFactor = Math.Min(compensatedViewportWidth / rowWidth, compensatedViewportHeight / rowHeight);
                    break; 
 
                case ViewMode.PageWidth:
                    //Update the scale factor so that the pivot row is as wide as the viewport. 
                    scaleFactor = compensatedViewportWidth / rowWidth;
                    break;

                case ViewMode.PageHeight: 
                    //Update the scale factor so that the pivot row is as tall as the viewport.
                    scaleFactor = compensatedViewportHeight / rowHeight; 
                    break; 

                case ViewMode.Thumbnails: 
                    //Update the scale factor so that the _entire layout_ is completely visible.  As in previous
                    //cases we must compensate for the fact that the spacing between pages does not scale.
                    //However, unlike in previous cases, we must exclude the space between all rows rather
                    //merely one space, so we must recalculate the compensated values.  Furthermore we must 
                    //also compensate for the ExtentHeight as well since it includes the spaces.
                    double thumbnailCompensatedExtentHeight = ExtentHeight - VerticalPageSpacing * _rowCache.RowCount; 
                    double thumbnailCompensatedViewportHeight = ViewportHeight - VerticalPageSpacing * _rowCache.RowCount; 
                    //If we have no space to display pages, there's nothing to scale.
                    //So just return 1.0 to indicate no change. 
                    if (thumbnailCompensatedViewportHeight <= 0.0)
                    {
                        scaleFactor = 1.0;
                    } 
                    else
                    { 
                        scaleFactor = Math.Min(compensatedViewportWidth / rowWidth, 
                            thumbnailCompensatedViewportHeight / thumbnailCompensatedExtentHeight);
                    } 
                    break;

                case ViewMode.Zoom:
                    //We will not change the scale here, as this is not a "page-fit" mode. 
                    break;
 
                default: 
                    throw new InvalidOperationException(SR.Get(SRID.DocumentGridInvalidViewMode));
            } 

            return scaleFactor;
        }
 
        /// 
        /// Creates the caches used by DocumentGrid, and sets default property values. 
        ///  
        private void Initialize()
        { 
            //Create our caches
            _pageCache = new PageCache();
            _childrenCollection = new VisualCollection(this);
 
            _rowCache = new RowCache();
            _rowCache.PageCache = _pageCache; 
            _rowCache.RowCacheChanged += new RowCacheChangedEventHandler(OnRowCacheChanged); 
            _rowCache.RowLayoutCompleted += new RowLayoutCompletedEventHandler(OnRowLayoutCompleted);
        } 

        /// 
        /// Updates Scrolling-related properties and calls
        /// InvalidateScrollInfo and InvalidateDocumentScrollInfo on the 
        /// ScrollOwner and DocumentScrollOwner, respectively.
        ///  
        private void InvalidateDocumentScrollInfo() 
        {
            if (ScrollOwner != null) 
            {
                ScrollOwner.InvalidateScrollInfo();
            }
 
            if (DocumentViewerOwner != null)
            { 
                DocumentViewerOwner.InvalidateDocumentScrollInfo(); 
            }
        } 

        /// 
        /// Calls InvalidatePageViews and ApplyTemplate on our DocumentViewer owner
        /// so that the base implementation can keep its collection up to date. 
        /// 
        private void InvalidatePageViews() 
        { 
            Invariant.Assert(DocumentViewerOwner != null, "DocumentViewerOwner cannot be null.");
 
            if (DocumentViewerOwner != null)
            {
                DocumentViewerOwner.InvalidatePageViewsInternal();
                DocumentViewerOwner.ApplyTemplate(); 
            }
 
            //Perf Tracing - InvalidatePageViews 
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXINVALIDATEVIEWGUID, MS.Utility.EventType.Info);
        } 

        /// 
        /// Returns an ITextPointer to the current visible selection, if there is one.
        ///  
        /// An ITextPointer to the current selection, or null if none exists.
        private ITextPointer GetVisibleSelection() 
        { 
            ITextPointer selection = null;
 
            if (HasSelection())
            {
                ITextPointer tp = TextEditor.Selection.Start;
 
                //If the TextView contains the selection
                //then the selection is on a visible page. 
                if (TextViewContains(tp)) 
                {
                    selection = tp; 
                }
            }

            return selection; 
        }
 
        ///  
        /// Indicates whether a selection (visible or not) has been made.
        ///  
        /// true if a selection has been made, false otherwise.
        private bool HasSelection()
        {
            return (TextEditor != null && TextEditor.Selection != null); 
        }
 
        ///  
        /// Gets the page number that the specified ITextPointer to a visible selection
        /// is on. 
        /// 
        /// The TextPointer to find the page number for.
        /// 
        private int GetPageNumberForVisibleSelection(ITextPointer selection) 
        {
            Invariant.Assert(TextViewContains(selection)); 
 
            //Walk through the current DocumentPageViews and see which one contains the selection.
            foreach (DocumentPageView pageView in _pageViews) 
            {
                //Get the TextView for this page.
                DocumentPageTextView textView =
                    ((IServiceProvider)pageView).GetService(typeof(ITextView)) as DocumentPageTextView; 

                //If this TextView contains the selection, return the page's number. 
                if (textView != null && 
                    textView.IsValid &&
                    textView.Contains(selection)) 
                {
                    return pageView.PageNumber;
                }
            } 

            Invariant.Assert(false, "Selection was in TextView, but not found in any visible page!"); 
            return 0; 
        }
 
        /// 
        /// Finds the "Active Focus" point:
        /// Either the page that has a Selection/Insertion Point on it,
        /// or lacking that, the center of the viewport. 
        /// 
        ///  
        private Point GetActiveFocusPoint() 
        {
 
            ITextPointer tp = GetVisibleSelection();

            if (tp != null && tp.HasValidLayout)
            { 
                Rect selectionRect = TextView.GetRectangleFromTextPosition(tp);
 
                //If the selection rectangle is not empty, then we have a selection or an IP 
                if (selectionRect != Rect.Empty)
                { 
                    //Return the upper-left corner of the selection.
                    return new Point(selectionRect.Left, selectionRect.Top);
                }
            } 

            //No selection, so we default to the upper-left of the viewport. 
            return new Point(0.0, 0.0); 
        }
 
        /// 
        /// Returns the page that has "Active Focus," or
        /// the first visible page if there is none.
        ///  
        /// 
        private int GetActiveFocusPage() 
        { 
            DocumentPageView dp = GetDocumentPageViewFromPoint(GetActiveFocusPoint());
 
            if (dp != null)
            {
                return dp.PageNumber;
            } 

            //No selection, we default to the first visible page. 
            return _firstVisiblePageNumber; 
        }
 
        /// 
        /// Given a point onscreen, returns a DocumentPageView that occupies that point,
        /// if any.
        ///  
        /// The point at which to search for a DocumentPageView.
        ///  
        private DocumentPageView GetDocumentPageViewFromPoint(Point point) 
        {
            //Hit test to find the DocumentPageView 
            HitTestResult result = VisualTreeHelper.HitTest(this, point);
            DependencyObject currentVisual = (result != null) ? result.VisualHit : null;

            DocumentPageView page = null; 

            // Traverse the visual parent chain until we encounter a DocumentPageView. 
            while (currentVisual != null) 
            {
                page = currentVisual as DocumentPageView; 
                if (page != null)
                {
                    //We found the DocumentPageView.
                    return page; 
                }
                currentVisual = VisualTreeHelper.GetParent(currentVisual); 
            } 

            //Didn't find one at this point. 
            return null;
        }

        ///  
        /// Helper function to safely verify that the TextView contains a given TextPointer.
        ///  
        /// The TextPointer to check 
        /// 
        private bool TextViewContains( ITextPointer tp ) 
        {
            return (TextView != null &&
                TextView.IsValid &&
                TextView.Contains(tp)); 
        }
 
        ///  
        /// Helper function that calculates the Horizontal offset of the given page.
        ///  
        /// The row which the desired page lives on
        /// The page to find the offset for
        /// The Horizontal offset of the page in the document.
        private double GetHorizontalOffsetForPage( RowInfo row, int pageNumber ) 
        {
            if (row == null) 
            { 
                throw new ArgumentNullException("row");
            } 

            if (pageNumber < row.FirstPage ||
                pageNumber > row.FirstPage + row.PageCount)
            { 
                throw new ArgumentOutOfRangeException("pageNumber");
            } 
 
            //Rows are centered if the content has varying page sizes,
            //Left-aligned otherwise. 
            double horizontalOffset = _pageCache.DynamicPageSizes ?
                Math.Max(0.0, (ExtentWidth - row.RowSize.Width) / 2.0) : 0.0;

            //Add the widths of the pages (and spacing) prior to this one on the row 
            for (int i = row.FirstPage; i < pageNumber; i++)
            { 
                Size pageSize = _pageCache.GetPageSize(i); 
                horizontalOffset += pageSize.Width * Scale + HorizontalPageSpacing;
            } 

            return horizontalOffset;

        } 

        ///  
        /// Helper method that determines whether a given RowCacheChange will have 
        /// an impact on currently-visible rows.
        ///  
        /// 
        /// 
        private bool RowCacheChangeIsVisible(RowCacheChange change)
        { 
            int firstVisibleRow = _firstVisibleRow;
            int lastVisibleRow = _firstVisibleRow + _visibleRowCount; 
 
            int firstChangedRow = change.Start;
            int lastChangedRow = change.Start + change.Count; 

            //If the first changed row (and hence following changes) are visible OR
            //The last changed row (and hence prior changes) are visible OR
            //if the changes are a super-set of the visible range, then the change is visible. 
            if ((firstChangedRow >= firstVisibleRow && firstChangedRow <= lastVisibleRow) ||
                (lastChangedRow >= firstVisibleRow && lastChangedRow <= lastVisibleRow) || 
                (firstChangedRow < firstVisibleRow && lastChangedRow > lastVisibleRow)) 
            {
                return true; 
            }

            return false;
        } 

        ///  
        /// Determines whether the given page's contents have been loaded into the visual tree. 
        /// 
        /// The number of the page to check 
        /// 
        private bool IsPageLoaded(int pageNumber)
        {
            DocumentGridPage page = GetDocumentGridPageForPageNumber(pageNumber); 

            if (page != null) 
            { 
                return page.IsPageLoaded;
            } 
            else
            {
                return false;
            } 
        }
 
        ///  
        /// Determines whether every page currently visible has been loaded into the visual tree.
        ///  
        /// 
        private bool IsViewLoaded()
        {
            bool viewIsLoaded = true; 

            for (int i = _firstPageVisualIndex; i < _childrenCollection.Count; i++) 
            { 
                DocumentGridPage dp = _childrenCollection[i] as DocumentGridPage;
 
                // Check that this page has been loaded; break if not.
                if (dp != null && !dp.IsPageLoaded)
                {
                    viewIsLoaded = false; 
                    break;
                } 
            } 

            return viewIsLoaded; 
        }

        /// 
        /// Retrieves a DocumentGridPage from our Visual Tree that has the given page number (if one exists). 
        /// 
        /// The number of the page to get 
        ///  
        private DocumentGridPage GetDocumentGridPageForPageNumber(int pageNumber)
        { 
            for (int i = _firstPageVisualIndex; i < _childrenCollection.Count; i++)
            {
                DocumentGridPage dp = _childrenCollection[i] as DocumentGridPage;
 
                if (dp != null && dp.PageNumber == pageNumber)
                { 
                    return dp; 
                }
            } 

            return null;
        }
 
        #region Event Handlers
 
        ///  
        /// Handles the RequestBringIntoView routed event in the case where the element to be
        /// brought into view is DocumentGrid itself, as is the case when the TextEditor's IP moves. 
        /// In this case we use the incoming target rectangle
        /// and ensure that rect is made visible inside of DocumentGrid.
        /// 
        /// The sender of this routed event, expected to be a DocumentGrid. 
        /// The RequestBringIntoView event args associated with this event.
        private static void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs args) 
        { 
            //We only handle this here if the sender and the target of this event are both the same
            //DocumentGrid. 
            DocumentGrid senderGrid = sender as DocumentGrid;
            DocumentGrid targetGrid = args.TargetObject as DocumentGrid;
            if (senderGrid != null && targetGrid != null && senderGrid == targetGrid)
            { 
                //Bring the IP into view and mark the event as handled.
                args.Handled = true; 
                targetGrid.MakeIPVisible(args.TargetRect); 
            }
            else 
            {
                args.Handled = false;
            }
        } 

        ///  
        /// This event is fired when our parent ScrollViewer's layout has changed(before render). 
        /// If we need to ensure that a given row has been properly fit -- if ScrollBars have been hidden/
        /// made visible due to this change then we may need to resize.  We call EnsureFit from here 
        /// to make sure that's done.
        /// 
        /// 
        ///  
        private void OnScrollChanged(object sender, EventArgs args)
        { 
            //Remove our handler. 
            if (ScrollOwner != null)
            { 
                _scrollChangedEventAttached = false;
                ScrollOwner.ScrollChanged -= new ScrollChangedEventHandler(OnScrollChanged);
            }
 
            //Ensure that our fit is good for the currently displayed row if we have any.
            if (_rowCache.HasValidLayout) 
            { 
                EnsureFit(_rowCache.GetRowForPageNumber(FirstVisiblePageNumber));
            } 
        }

        /// 
        /// This event is fired after a Zoom change when Layout has completed (but before render). 
        /// We make sure the current visible selection is centered onscreen.
        ///  
        ///  
        /// 
        private void OnZoomLayoutUpdated(object sender, EventArgs args) 
        {
            //Remove the event handler so we don't get called again.
            LayoutUpdated -= new EventHandler(OnZoomLayoutUpdated);
 
            ITextPointer selection = GetVisibleSelection();
 
            if (selection != null) 
            {
                //Now we make the selection visible. 
                MakeRectVisible(TextView.GetRectangleFromTextPosition(
                    selection), true /* alwaysCenter */);
            }
        } 

        ///  
        /// When the RowCache is changed for any reason (due to a new layout, or if pages change, etc...) 
        /// then we need to invalidate our Measure (so we can pick up any changes to visible pages)
        /// and invalidate our IDocumentScrollInfo parents so they know that something's changed. 
        /// 
        /// 
        /// 
        private void OnRowCacheChanged(object source, RowCacheChangedEventArgs args) 
        {
 
            //If: 
            //1) We have a saved pivot row from a previous RowCacheCompleted event,
            //and 
            //2) We've been told to do a "Page-Fit" operation (that is, a non-zoom viewing preference)
            //and
            //3) The pivot row is now "clean" (that is, we know the actual dimensions of all the pages
            //   on the row and we aren't just guessing) 
            //Then we can now officially calculate the scale needed in order to fit the given row in the manner
            //chosen. 
            if (_savedPivotRow != null && 
                RowIsClean(_savedPivotRow))
            { 
                if (_documentLayout.ViewMode != ViewMode.Zoom &&
                    _documentLayout.ViewMode != ViewMode.SetColumns
                    )
                { 
                    if (_savedPivotRow.FirstPage < _rowCache.RowCount)
                    { 
                        RowInfo newRow = _rowCache.GetRowForPageNumber(_savedPivotRow.FirstPage); 

                        //If the new row's dimensions differ, then we need to rescale, otherwise we do nothing. 
                        if (newRow.RowSize.Width != _savedPivotRow.RowSize.Width ||
                            newRow.RowSize.Height != _savedPivotRow.RowSize.Height)
                        {
                            //Rescale. 
                            ApplyViewParameters(newRow);
                        } 
 
                        //Null out the saved Pivot Row -- we've scaled this row properly now
                        //so we don't need to be concerned with it any longer. 
                        _savedPivotRow = null;
                    }
                }
                else 
                {
                    // The view is already correct; null out the saved Pivot Row. 
                    _savedPivotRow = null; 
                }
            } 

            //If we're viewing a document with varying page size, we've scrolled since the last layout change
            //and the Width of the document has increased
            //then this means that new, wider pages have just been scrolled into view. 
            //If we do nothing here, then the content prior to these new pages will appear to "jump"
            //to the right (because we center the pages within the width of the document.) 
            //This jump is jarring and not a good user experience. 
            //To prevent this, we adjust the HorizontalOffset such that the content that was previously visible
            //appears at the same position when it is rendered. 
            if (_pageCache.DynamicPageSizes &&
                _lastRowChangeVerticalOffset != VerticalOffset &&
                _lastRowChangeExtentWidth < ExtentWidth)
            { 
                if (_lastRowChangeExtentWidth != 0.0)
                { 
                    //Tweak the HorizontalOffset so that the content does not appear to move. 
                    SetHorizontalOffsetInternal(HorizontalOffset + (ExtentWidth - _lastRowChangeExtentWidth) / 2.0);
                } 

                _lastRowChangeExtentWidth = ExtentWidth;
            }
 
            _lastRowChangeVerticalOffset = VerticalOffset;
 
            //The row cache has been changed. 
            //If we're displaying rows that were affected,
            //we need to invalidate our measure so they'll be 
            //redrawn.
            for (int i = 0; i < args.Changes.Count; i++)
            {
                RowCacheChange change = args.Changes[i]; 

                if( RowCacheChangeIsVisible( change )) 
                { 
                    InvalidateMeasure();
                    InvalidateChildMeasure(); 
                }
            }

            InvalidateDocumentScrollInfo(); 
        }
 
        ///  
        /// When a new RowLayout has finished being computed we scale the layout such that it
        /// fits within our window. 
        /// 
        /// 
        /// 
        private void OnRowLayoutCompleted(object source, RowLayoutCompletedEventArgs args) 
        {
            if (args == null) 
            { 
                return;
            } 
            if (args.PivotRowIndex >= _rowCache.RowCount)
            {
                throw new ArgumentOutOfRangeException("args");
            } 

            //Get the pivot row 
            RowInfo pivotRow = _rowCache.GetRow(args.PivotRowIndex); 

            //If this row is not clean, and we're not applying a 
            //Zoom to the content then we need to rescale the layout when the
            //pages on this row are retrieved.
            if (!RowIsClean(pivotRow) && _documentLayout.ViewMode != ViewMode.Zoom)
            { 
                //Save off this row in case we need to rescale due to
                //dirty cache entries becoming clean (i.e. page sizes changing 
                //due to the cached size being an inaccurate guess.) 
                //OnRowCacheChanged will check this row to ensure that it gets scaled
                //properly when all the pages on the row become available. 
                _savedPivotRow = pivotRow;
            }
            else
            { 
                _savedPivotRow = null;
            } 
 
            //Now rescale.  We do this after checking the cleanliness of the row
            //so that _savedPivotRow is properly set before we apply our view parameters. 
            //Otherwise the code that relies on it in OnRowCacheChanged (which may be called
            //as a result of calling ApplyViewParameters) may use the wrong row.
            ApplyViewParameters(pivotRow);
 
            //Now that we've recalculated the row layout, it's time to make the previously-visible
            //content visible again. 
            //We do not do this the first time the content is assigned, for two reasons, 
            //(similar to the ones described in DocumentViewer.OnDocumentChanged()):
            //  1) If this is the first assignment, then we're already there by default. 
            //  2) The user may have specified vertical or horizontal offsets in markup or
            //     otherwise () and we need to honor
            //     those settings.
            if (!_firstRowLayout && !_pageJumpAfterLayout) 
            {
                MakePageVisible(pivotRow.FirstPage); 
            } 
            else if (_pageJumpAfterLayout)
            { 
                MakePageVisible(_pageJumpAfterLayoutPageNumber);
                _pageJumpAfterLayout = false;
            }
 
            _firstRowLayout = false;
 
            //If our view was of a "Fit" type, we need to ensure that the fit is 
            //correct -- if the status of Vertical/Horizontal Scrollbars has changed
            //as a result of our view selection then the Viewport size may have changed. 
            //If so, our current fit is probably wrong.  We'll attach a ScrollChanged handler
            //to our ScrollOwner and when the event is invoked (after layout, but before rendering)
            //we'll check.
            //This is "Step 2" of the two-pass layout necessary to do fit properly inside of a 
            //ScrollViewer.
            if (!_scrollChangedEventAttached && 
                ScrollOwner != null && 
                _documentLayout.ViewMode != ViewMode.Zoom &&
                _documentLayout.ViewMode != ViewMode.SetColumns) 
            {
                _scrollChangedEventAttached = true;
                ScrollOwner.ScrollChanged += new ScrollChangedEventHandler(OnScrollChanged);
            } 
        }
 
        #endregion Event Handlers 

        #endregion Private Methods 

        //------------------------------------------------------
        //
        //  Private Properties 
        //
        //----------------------------------------------------- 
        #region Private Properties 

        ///  
        /// Indicates that our Viewport is or is not exactly (0,0).
        /// 
        /// 
        private bool IsViewportNonzero 
        {
            get 
            { 
                return (ViewportWidth != 0.0 && ViewportHeight != 0.0);
            } 
        }

        /// 
        /// Provides access to DocumentViewer's TextEditor. 
        /// 
        private TextEditor TextEditor 
        { 
            get
            { 
                if (DocumentViewerOwner != null)
                {
                    return DocumentViewerOwner.TextEditor;
                } 
                else
                { 
                    return null; 
                }
            } 
        }

        /// 
        /// Represents the number of pixels to scroll by when using the 
        /// Mouse Wheel; based on System.Parameters.WheelScrollLines.
        ///  
        private double MouseWheelVerticalScrollAmount 
        {
            get 
            {
                //SystemParameters.WheelScrollLines indicates the number of lines to
                //scroll when the wheel is moved one "click," we multiply this by
                //our scroll amount to get the number of pixels to move. 
                return _verticalLineScrollAmount * SystemParameters.WheelScrollLines;
            } 
        } 

        ///  
        /// Represents the number of pixels to scroll by when using the
        /// Mouse Wheel; based on System.Parameters.WheelScrollLines.
        /// 
        private double MouseWheelHorizontalScrollAmount 
        {
            get 
            { 
                //SystemParameters.WheelScrollLines indicates the number of lines to
                //scroll when the wheel is moved one "click," we multiply this by 
                //our scroll amount to get the number of pixels to move.
                return _horizontalLineScrollAmount * SystemParameters.WheelScrollLines;
            }
        } 

        ///  
        /// Returns the minimum allowed scale based on the current view mode. 
        /// Thumbnails mode has a higher minimum than other views.
        ///  
        private double CurrentMinimumScale
        {
            get
            { 
                return _documentLayout.ViewMode == ViewMode.Thumbnails ?
                  DocumentViewerConstants.MinimumThumbnailsScale : 
                  DocumentViewerConstants.MinimumScale; 
            }
        } 

        #endregion Private Properties

        //------------------------------------------------------ 
        //
        //  Private Fields 
        // 
        //-----------------------------------------------------
 
        #region Private Fields

        // Our Caches
        private PageCache       _pageCache; 
        private RowCache        _rowCache;
 
        // Our collection of currently-displayed pages. 
        private ReadOnlyCollection _pageViews;
 
        // Data for Properties
        private bool            _canHorizontallyScroll;
        private bool            _canVerticallyScroll;
        private double          _verticalOffset; 
        private double          _horizontalOffset;
        private double          _viewportHeight; 
        private double          _viewportWidth; 
        private int             _firstVisibleRow;
        private int             _visibleRowCount; 
        private int             _firstVisiblePageNumber;
        private int             _lastVisiblePageNumber;
        private ScrollViewer    _scrollOwner;
        private DocumentViewer  _documentViewerOwner; 
        private bool            _showPageBorders = true;
        private bool            _lockViewModes; 
        private int             _maxPagesAcross = 1; 

        // The previous constraint passed to ArrangeCore. 
        private Size            _previousConstraint;

        // The viewing mode (columns, fit, etc...) last used to request a layout.
        private DocumentLayout  _documentLayout = 
            new DocumentLayout(1, ViewMode.SetColumns);
        private int             _documentLayoutsPending; 
 
        // The pivot rows last used to form the basis of a layout.
        private RowInfo         _savedPivotRow; 

        // The last ExtentWidth and VerticalOffsets encountered in
        // OnRowCacheChanged, used to determine whether to tweak the HorizontalOffset.
        private double          _lastRowChangeExtentWidth; 
        private double          _lastRowChangeVerticalOffset;
 
        // Editing 
        private ITextContainer  _textContainer;
 
        // RubberBand selector used for rubberband selection.
        private RubberbandSelector _rubberBandSelector;

        // Flags 
        private bool            _isLayoutRequested;  //Whether we have requested a layout from the RowCache.
        private bool            _pageJumpAfterLayout;   //Whether we need to bring a page into view after layout 
        private int             _pageJumpAfterLayoutPageNumber; //The page to jump to after layout 
        private bool            _firstRowLayout = true;
        private bool            _scrollChangedEventAttached; //Whether or not we've attached a ScrollChanged event to our ScrollViewer. 

        // We create a Border with a transparent background so that it can
        // participate in Hit-Testing (which allows click events like those
        // for our Context Menu to work).  This border is displayed behind 
        // the displayed pages so that "dead space" surrounding the pages can
        // be clicked on. 
        private Border          _documentGridBackground; 
        private const int       _backgroundVisualIndex = 0;
        private const int       _firstPageVisualIndex = 1; 

        //Constants for MeasureCore constraints
        //We use this size if we're placed inside a "Size-To-Parent" container like
        //ScrollViewer or StackPanel and are given Infinite constraints. 
        private readonly Size _defaultConstraint = new Size(250.0, 250.0);
 
        //Store all our visual children (pages) here 
        private VisualCollection _childrenCollection;
 
        //Information for MakeVisible operations involving pages that are not
        //yet visible.
        private int _makeVisiblePageNeeded = -1;
        private DispatcherOperation _makeVisibleDispatcher; 

        //DispatcherOperations used for executing time-consuming property changes in the background. 
        private DispatcherOperation _setScaleOperation; 

        //Delegate used for BringPageIntoView. 
        private delegate void BringPageIntoViewCallback(MakeVisibleData data, int pageNumber);

        /// 
        /// Represents a state in the Visual tree merging state machine 
        /// used in RecalcVisiblePages.
        ///  
        private enum VisualTreeModificationState 
        {
            ///  
            /// Inserting pages before existing
            /// 
            BeforeExisting = 0,
 
            /// 
            /// Scanning through existing pages 
            ///  
            DuringExisting,
 
            /// 
            /// Adding pages after existing
            /// 
            AfterExisting 
        }
 
        ///  
        /// Represents a layout mode specified by SetColumns,
        /// FitToPage, FitToWidth, etc... 
        /// 
        private enum ViewMode
        {
            ///  
            /// A request to lay out a specified number of columns was made.
            ///  
            SetColumns = 0, 

            ///  
            /// A request to make the specified number of columns visible was made.
            /// 
            FitColumns,
 
            /// 
            /// A request for a fit-to-page-width view was made. 
            ///  
            PageWidth,
 
            /// 
            /// A request for a fit-to-page-height view was made.
            /// 
            PageHeight, 

            ///  
            /// A request for a thumbnail view was made. 
            /// 
            Thumbnails, 

            /// 
            /// A request for a non "page-fit" view was made.
            ///  
            Zoom,
 
            ///  
            /// A request for the HorizontalOffset to be updated.
            ///  
            SetHorizontalOffset,

            /// 
            /// A request for the VerticalOffset to be updated. 
            /// 
            SetVerticalOffset 
        } 

        ///  
        /// Represents a particular document layout --
        /// includes the number of Columns to view and the
        /// mode to view them in.
        ///  
        private class DocumentLayout
        { 
            public DocumentLayout(int columns, ViewMode viewMode) 
                : this(columns, 0.0 /* default */, viewMode) { }
 
            public DocumentLayout(double offset, ViewMode viewMode)
                : this(1 /* default */, offset, viewMode) { }

            public DocumentLayout(int columns, double offset, ViewMode viewMode) 
            {
                _columns = columns; 
                _offset = offset; 
                _viewMode = viewMode;
            } 

            /// 
            /// The ViewMode to apply to the layout.
            ///  
            public ViewMode ViewMode
            { 
                set { _viewMode = value; } 
                get { return _viewMode; }
            } 

            /// 
            /// The number of columns for the layout.
            ///  
            public int Columns
            { 
                set { _columns = value; } 
                get { return _columns; }
            } 

            /// 
            /// The offset (horizontal of vertical) for the layout.
            ///  
            public double Offset
            { 
                // Set not currently used. 
                // set { _offset = value; }
                get { return _offset; } 
            }

            private ViewMode _viewMode;
            private int      _columns; 
            private double   _offset;
        } 
 
        /// 
        /// An MakeVisibleData object contains data and operation information 
        /// related to asynchronous MakeVisible operations.
        /// 
        private struct MakeVisibleData
        { 
            /// 
            /// Constructs a new MakeVisibleData object 
            ///  
            /// A visual to be made visible.
            /// A ContentPosition to be made visible. 
            /// Any bounding rect to be made visible.
            public MakeVisibleData(Visual visual, ContentPosition contentPosition, Rect rect)
            {
                _visual = visual; 
                _contentPosition = contentPosition;
                _rect = rect; 
            } 

            ///  
            /// The Visual to be made visible
            /// 
            public Visual Visual
            { 
                get { return _visual; }
            } 
 
            /// 
            /// The ContentPosition to be made Visible 
            /// 
            public ContentPosition ContentPosition
            {
                get { return _contentPosition; } 
            }
 
            ///  
            /// The bounding rectangle to be made visible
            ///  
            public Rect Rect
            {
                get { return _rect; }
            } 

            private Visual _visual; 
            private ContentPosition _contentPosition; 
            private Rect _rect;
 
        }

        //Constants for line scrolling amounts
        private const double _verticalLineScrollAmount = 16.0; 
        private const double _horizontalLineScrollAmount = 16.0;
 
        #endregion Private Fields 
    }
} 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// 
// Description: DocumentGrid displays DocumentPaginator content in a grid-like 
//              arrangement and is used by DocumentViewer to display documents.
// 
// History:

// 10/21/04 - jdersch created for complete and utter overhaul for the new
//            DocumentViewer control. 
//
//--------------------------------------------------------------------------- 
using MS.Internal; 
using MS.Internal.Media;
using MS.Utility; 
using MS.Win32;
using System.Threading;
using System.Windows;
using System.Windows.Controls; 
using System.Windows.Controls.Primitives;
using System.Windows.Documents; 
using System.Windows.Input; 
using System.Windows.Media;
using System.Windows.Threading; 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using System.Diagnostics;
 
namespace MS.Internal.Documents 
{
 
    /// 
    /// DocumentGrid is an internal Avalon FrameworkElement that executes all the
    /// "heavy lifting" involved in loading and displaying an DocumentPaginator-based
    /// document inside of a DocumentViewer control. 
    /// 
    /// http://d2/DRX/default.aspx 
    internal class DocumentGrid : FrameworkElement, IDocumentScrollInfo 
    {
        //----------------------------------------------------- 
        //
        //  Constructors
        //
        //----------------------------------------------------- 
        #region Constructors
 
        ///  
        /// Static constructor
        ///  
        static DocumentGrid()
        {
            //Register for the RequestBringIntoView event so we can get BIV events for
            //TextEditor IP movements. 
            EventManager.RegisterClassHandler(typeof(DocumentGrid),
                RequestBringIntoViewEvent, 
                new RequestBringIntoViewEventHandler(OnRequestBringIntoView)); 
            //Register the default ContextMenu
            DocumentGridContextMenu.RegisterClassHandler(); 
        }

        /// 
        /// The constructor 
        /// 
        public DocumentGrid() : base() 
        { 
            Initialize();
        } 

        #endregion Constructors

        //------------------------------------------------------ 
        //
        //  Internal Methods 
        // 
        //-----------------------------------------------------
        #region Internal Methods 
        /// 
        /// Hit-Test on the multi-page UI scope to return a DocumentPage
        /// that contains this point
        ///  
        /// Point in pixel unit, relative to the UI Scope's coordinates
        /// A DocumentPage that is hit or null if no page is hit 
        internal DocumentPage GetDocumentPageFromPoint(Point point) 
        {
            DocumentPageView dp = GetDocumentPageViewFromPoint(point); 

            // if we hit a DocumentPageView we can return its DocumentPage.
            if (dp != null)
            { 
                return dp.DocumentPage;
            } 
 
            //Nothing hit, return null.
            return null; 
        }

        #endregion Internal Methods
 

        //------------------------------------------------------ 
        // 
        //  Public Interfaces
        // 
        //------------------------------------------------------
        #region Interface Implementations

 
        #region IDocumentScrollInfo
        //----------------------------------------------------- 
        // 
        //  IDocumentScrollInfo Methods
        // 
        //------------------------------------------------------

        /// 
        /// Scroll content by one line to the top. 
        /// 
        public void LineUp() 
        { 
            if (_canVerticallyScroll)
            { 
                SetVerticalOffsetInternal(VerticalOffset - _verticalLineScrollAmount);
            }
        }
 
        /// 
        /// Scroll content by one line to the bottom. 
        ///  
        public void LineDown()
        { 
            if (_canVerticallyScroll)
            {
                SetVerticalOffsetInternal(VerticalOffset + _verticalLineScrollAmount);
 
                //Perf Tracing - Mark LineDown Start
                if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
                { 
                    EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXLINEDOWNGUID),
                                                         MS.Utility.EventType.Info, 
                                                         (int)VerticalOffset
                                                         );
                }
            } 
        }
 
        ///  
        /// Scroll content by one line to the left.
        ///  
        public void LineLeft()
        {
            if (_canHorizontallyScroll)
            { 
                SetHorizontalOffsetInternal(HorizontalOffset - _horizontalLineScrollAmount);
            } 
        } 

        ///  
        /// Scroll content by one line to the right.
        /// 
        public void LineRight()
        { 
            if (_canHorizontallyScroll)
            { 
                SetHorizontalOffsetInternal(HorizontalOffset + _horizontalLineScrollAmount); 
            }
        } 

        /// 
        /// Scroll content by one viewport to the top.
        ///  
        public void PageUp()
        { 
            SetVerticalOffsetInternal(VerticalOffset - ViewportHeight); 
        }
 
        /// 
        /// Scroll content by one viewport to the bottom.
        /// 
        public void PageDown() 
        {
            SetVerticalOffsetInternal(VerticalOffset + ViewportHeight); 
 
            //Perf Tracing - Mark PageDown Start
            if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
            {
                EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEDOWNGUID),
                                                     MS.Utility.EventType.Info,
                                                     (int)VerticalOffset); 
            }
        } 
 
        /// 
        /// Scroll content by one viewport to the left. 
        /// 
        public void PageLeft()
        {
            SetHorizontalOffsetInternal(HorizontalOffset - ViewportWidth); 
        }
 
        ///  
        /// Scroll content by one viewport to the right.
        ///  
        public void PageRight()
        {
            SetHorizontalOffsetInternal(HorizontalOffset + ViewportWidth);
        } 

        ///  
        /// Scroll content up via the mousewheel. 
        /// 
        public void MouseWheelUp() 
        {
            if (_canVerticallyScroll)
            {
                SetVerticalOffsetInternal(VerticalOffset - MouseWheelVerticalScrollAmount); 
            }
            else 
            { 
                PageUp();
            } 
        }

        /// 
        /// Scroll content down via the mousewheel. 
        /// 
        public void MouseWheelDown() 
        { 
            if (_canVerticallyScroll)
            { 
                SetVerticalOffsetInternal(VerticalOffset + MouseWheelVerticalScrollAmount);
            }
            else
            { 
                PageDown();
            } 
        } 

        ///  
        /// Scroll content left via the mousewheel.
        /// 
        public void MouseWheelLeft()
        { 
            if (_canHorizontallyScroll)
            { 
                SetHorizontalOffsetInternal(HorizontalOffset - MouseWheelHorizontalScrollAmount); 
            }
            else 
            {
                PageLeft();
            }
        } 

        ///  
        /// Scroll content right via the mousewheel. 
        /// 
        public void MouseWheelRight() 
        {
            if (_canHorizontallyScroll)
            {
                SetHorizontalOffsetInternal(HorizontalOffset + MouseWheelHorizontalScrollAmount); 
            }
            else 
            { 
                PageRight();
            } 
        }

        /// 
        /// Ensures that the specified visual is made visible. 
        /// 
        ///  
        /// A rectangle in the IScrollInfo's coordinate space that has been made visible. 
        /// Other ancestors to in turn make this new rectangle visible.
        /// The rectangle should generally be a transformed version of the input rectangle.  In some cases, like 
        /// when the input rectangle cannot entirely fit in the viewport, the return value might be smaller.
        /// 
        public Rect MakeVisible(Visual v, Rect r)
        { 
            if (Content != null && v != null)
            { 
                ContentPosition cp = Content.GetObjectPosition(v); 
                MakeContentPositionVisibleAsync(new MakeVisibleData(v, cp, r));
            } 

            return r;
        }
 
        /// 
        /// Ensures that the specified object is made visible, given that the page it lives on is already known. 
        ///  
        /// 
        /// A rectangle in the IScrollInfo's coordinate space that has been made visible. 
        /// Other ancestors to in turn make this new rectangle visible.
        /// The rectangle should generally be a transformed version of the input rectangle.  In some cases, like
        /// when the input rectangle cannot entirely fit in the viewport, the return value might be smaller.
        ///  
        public Rect MakeVisible(object o, Rect r, int pageNumber)
        { 
            MakeVisibleAsync(new MakeVisibleData(o as Visual, o as ContentPosition, r), pageNumber); 
            return r;
        } 

        /// 
        /// Scrolls the current selection into view.  Requests for empty or
        /// invalid selections will do nothing. 
        /// 
        public void MakeSelectionVisible() 
        { 
            //We can only continue if we have a TextEditor attached...
            if (TextEditor != null && TextEditor.Selection != null) 
            {
                //Get the TextPointer for the start of our selection.
                ITextPointer tp = TextEditor.Selection.Start;
 
                //Ensure that the TextPointer we use has gravity set to forwards (or into
                //the selection) so that the selected text is always displayed. 
                tp = tp.CreatePointer(LogicalDirection.Forward); 

                //If the TextPointer is also a ContentPosition, we can 
                //make that ContentPosition visible.
                ContentPosition cp = tp as ContentPosition;
                MakeContentPositionVisibleAsync(new MakeVisibleData(null, cp, Rect.Empty));
            } 
        }
 
        ///  
        /// Scrolls the requested page into view.
        ///  
        /// The page to make visible.
        public void MakePageVisible(int pageNumber)
        {
            //If we're moving more than one page then this is a "page jump" 
            //and we should log the perf event.
            if (Math.Abs(pageNumber - _firstVisiblePageNumber) > 1) 
            { 
                //Perf Tracing - Mark Page Jump Start
                if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
                {
                    EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEJUMPGUID),
                                                         MS.Utility.EventType.Info,
                                                         _firstVisiblePageNumber, 
                                                         pageNumber);
                } 
            } 

            //Clip the offset into range for out-of-range page numbers 
            if (pageNumber < 0 )
            {
                 //Clip to the top-left of the document
                SetVerticalOffsetInternal(0.0d); 
                SetHorizontalOffsetInternal(0.0d);
            } 
            else if (pageNumber >= _pageCache.PageCount || _rowCache.RowCount == 0) 
            {
                //If the doc is done loading, then this page is out of range. 
                if (_pageCache.IsPaginationCompleted && _rowCache.HasValidLayout)
                {
                    //Clip to the bottom-right of the document
                    SetVerticalOffsetInternal(ExtentHeight); 
                    SetHorizontalOffsetInternal(ExtentWidth);
                } 
                else 
                {
                    //The doc is not done loading. 
                    //Wait for the page to be laid out and try again.
                    _pageJumpAfterLayout = true;
                    _pageJumpAfterLayoutPageNumber = pageNumber;
                } 

            } 
            else 
            {
                //This page is valid, so scroll to it now. 
                RowInfo scrolledRow = _rowCache.GetRowForPageNumber(pageNumber);
                SetVerticalOffsetInternal(scrolledRow.VerticalOffset);

                //Calculate the Horizontal offset of the page we're bringing into view: 
                double horizontalOffset = GetHorizontalOffsetForPage(scrolledRow, pageNumber);
                SetHorizontalOffsetInternal(horizontalOffset); 
 
            }
        } 

        /// 
        /// Scrolls the next row of pages into view.  This differs from
        /// IScrollInfo?s "PageDown" in that PageDown pages by Viewports 
        /// which may not coincide with page dimensions, whereas
        /// ScrollToNextRow takes these dimensions into account so that 
        /// precisely the next row of pages is displayed. 
        /// 
        public void ScrollToNextRow() 
        {
            //We change our vertical offset to be the offset of the next row (if there is one).
            //If there isn't, we do nothing.
            int nextRow = _firstVisibleRow + 1; 

            if (nextRow < _rowCache.RowCount) 
            { 
                //Get the next row.
                RowInfo row = _rowCache.GetRow(nextRow); 
                SetVerticalOffsetInternal(row.VerticalOffset);
            }
        }
 
        /// Scrolls the previous row of pages into view.  This differs from
        /// IScrollInfo?s "PageUp" in that PageUp pages by Viewports 
        /// which may not coincide with page dimensions, whereas 
        /// ScrollToPreviousRow takes these dimensions into account so that
        /// precisely the previously row of pages is displayed. 
        public void ScrollToPreviousRow()
        {
            //We change our vertical offset to be the offset of the previous row (if there is one).
            int previousRow = _firstVisibleRow - 1; 

            if (previousRow >= 0 && previousRow < _rowCache.RowCount) 
            { 
                //Get the previous row.
                RowInfo row = _rowCache.GetRow(previousRow); 
                SetVerticalOffsetInternal(row.VerticalOffset);
            }
        }
 
        /// 
        /// Scrolls to the top of the document. 
        ///  
        public void ScrollToHome()
        { 
            //We just set the VerticalOffset to 0.
            SetVerticalOffsetInternal(0);
        }
 
        /// 
        /// Scrolls to the bottom of the document. 
        ///  
        public void ScrollToEnd()
        { 
            //We just set the VerticalOffset to our document's extent.
            SetVerticalOffsetInternal(ExtentHeight);
        }
 
        /// 
        /// Sets the scale factor applied to pages in the document, while 
        /// keeping the "Active Focus" centered. 
        /// 
        ///  
        public void SetScale(double scale)
        {
            if (!DoubleUtil.AreClose(scale, Scale))
            { 
                if (scale <= 0.0)
                { 
                    throw new ArgumentOutOfRangeException("scale"); 
                }
 
                if (!Helper.IsDoubleValid(scale))
                {
                    throw new ArgumentOutOfRangeException("scale");
                } 

                QueueSetScale(scale); 
 
            }
        } 

        /// 
        /// Changes the view to the specified number of columns.
        ///  
        /// 
        public void SetColumns(int columns) 
        { 
            if (columns < 1)
            { 
                throw new ArgumentOutOfRangeException("columns");
            }

            //Perf Tracing - Mark Layout Change Start 
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info);
 
            QueueUpdateDocumentLayout(new DocumentLayout(columns, ViewMode.SetColumns)); 
        }
 
        /// 
        /// Changes the view to the specified number of columns.
        /// 
        ///  
        public void FitColumns(int columns)
        { 
            if (columns < 1) 
            {
                throw new ArgumentOutOfRangeException("columns"); 
            }

            //Perf Tracing - Mark Layout Change Start
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info); 

            QueueUpdateDocumentLayout(new DocumentLayout(columns, ViewMode.FitColumns)); 
        } 

        ///  
        /// Changes the view to a single page, scaled such that it is as wide as the Viewport.
        /// 
        public void FitToPageWidth()
        { 
            //Perf Tracing - Mark Layout Change Start
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info); 
 
            QueueUpdateDocumentLayout(
                new DocumentLayout(1 /* one column */, ViewMode.PageWidth)); 
        }

        /// 
        /// Changes the view to a single page, scaled such that it is as tall as the Viewport. 
        /// 
        public void FitToPageHeight() 
        { 
            //Perf Tracing - Mark Layout Change Start
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info); 

            QueueUpdateDocumentLayout(
                new DocumentLayout(1 /* one column */, ViewMode.PageHeight));
        } 

        ///  
        /// Changes the view to ?thumbnail view? which will scale the document 
        /// such that as many pages are visible at once as is possible.
        ///  
        public void ViewThumbnails()
        {
            //Perf Tracing - Mark Layout Change Start
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXLAYOUTGUID, MS.Utility.EventType.Info); 

            QueueUpdateDocumentLayout( 
                new DocumentLayout(1 /* one column, arbitrary */, ViewMode.Thumbnails)); 
        }
 
        //-----------------------------------------------------
        //
        //  IDocumentScrollInfo Properties
        // 
        //-----------------------------------------------------
 
        ///  
        /// DocumentGrid always scrolls in both dimensions.
        ///  
        public bool CanHorizontallyScroll
        {
            get { return _canHorizontallyScroll; }
            set { _canHorizontallyScroll = value; } 
        }
 
        ///  
        /// DocumentGrid always scrolls in both dimensions.
        ///  
        public bool CanVerticallyScroll
        {
            get { return _canVerticallyScroll; }
            set { _canVerticallyScroll = value; } 
        }
 
        ///  
        /// ExtentWidth contains the full horizontal range of the scrolled content.
        ///  
        public double ExtentWidth
        {
            get
            { 
                return _rowCache.ExtentWidth;
            } 
        } 

        ///  
        /// ExtentHeight contains the full vertical range of the scrolled content.
        /// 
        public double ExtentHeight
        { 
            get
            { 
                return _rowCache.ExtentHeight; 
            }
        } 

        /// 
        /// ViewportWidth contains the currently visible horizontal range of the scrolled content.
        ///  
        public double ViewportWidth
        { 
            get 
            {
                return _viewportWidth; 
            }
        }

        ///  
        /// ViewportHeight contains the currently visible vertical range of the scrolled content.
        ///  
        public double ViewportHeight 
        {
            get 
            {
                return _viewportHeight;
            }
        } 

        ///  
        /// HorizontalOffset is the horizontal offset into the scrolled content that represents the first unit visible. 
        /// Valid values are inclusively between 0 and  less .
        ///  
        public double HorizontalOffset
        {
            get
            { 
                //Clip HorizontalOffset into range.
                double clippedHorizontalOffset = Math.Min(_horizontalOffset, ExtentWidth - ViewportWidth); 
                clippedHorizontalOffset = Math.Max(clippedHorizontalOffset, 0.0); 

                return clippedHorizontalOffset; 
            }
        }

        ///  
        /// Set the HorizontalOffset.  If there are pending layout delegates, then
        /// this will be processed by a delegate. 
        ///  
        /// 
        public void SetHorizontalOffset(double offset) 
        {
            if (!DoubleUtil.AreClose(_horizontalOffset,offset))
            {
                if (Double.IsNaN(offset)) 
                {
                    throw new ArgumentOutOfRangeException("offset"); 
                } 

                // If there aren't any pending document layout delegates, then change 
                // the HorizontalOffset immediately, otherwise schedule a delegate for it.
                if (_documentLayoutsPending == 0)
                {
                    SetHorizontalOffsetInternal(offset); 
                }
                else 
                { 
                    QueueUpdateDocumentLayout(new DocumentLayout(offset, ViewMode.SetHorizontalOffset));
                } 
            }
        }

        ///  
        /// VerticalOffset is the vertical offset into the scrolled content that represents the first unit visible.
        /// Valid values are inclusively between 0 and  less . 
        ///  
        public double VerticalOffset
        { 
            get
            {
                //Clip VerticalOffset into range.
                double clippedVerticalOffset = Math.Min(_verticalOffset, ExtentHeight - ViewportHeight); 
                clippedVerticalOffset = Math.Max(clippedVerticalOffset, 0.0);
 
                return clippedVerticalOffset; 
            }
        } 

        /// 
        /// Set the VerticalOffset.  If there are pending layout delegates, then
        /// this will be processed by a delegate. 
        /// 
        ///  
        public void SetVerticalOffset(double offset) 
        {
            if (!DoubleUtil.AreClose(_verticalOffset, offset)) 
            {
                if (Double.IsNaN(offset))
                {
                    throw new ArgumentOutOfRangeException("offset"); 
                }
 
                // If there aren't any pending document layout delegates, then change 
                // the VerticalOffset immediately, otherwise schedule a delegate for it.
                if (_documentLayoutsPending == 0) 
                {
                    SetVerticalOffsetInternal(offset);
                }
                else 
                {
                    QueueUpdateDocumentLayout(new DocumentLayout(offset, ViewMode.SetVerticalOffset)); 
                } 
            }
        } 

        /// 
        /// Provides the IDocumentScrollInfo implementer with a content
        /// tree to be paginated.  Developers are free to modify this 
        /// Content at any time (remove, add, modify pages, etc?)
        /// and the IDocumentScrollInfo implementer is responsible for 
        /// noting the changes and updating as necessary. 
        /// 
        /// The DocumentPaginator to be assigned as the content 
        public DynamicDocumentPaginator Content
        {
            get
            { 
                //_pageCache is guaranteed to be non-null as it's created in the
                //Constructor. 
                return _pageCache.Content; 
            }
            set 
            {
                //_pageCache is guaranteed to be non-null as it's created in the
                //Constructor.
                if (value != _pageCache.Content) 
                {
                    //Null out our TextContainer.  It will be created as needed. 
                    _textContainer = null; 

                    //Remove our old events from the content 
                    if (_pageCache.Content != null)
                    {
                        _pageCache.Content.GetPageNumberCompleted -= new GetPageNumberCompletedEventHandler(OnGetPageNumberCompleted);
                    } 

                    //Remove our ScrollChanged events from our ScrollViewer 
                    if (ScrollOwner != null) 
                    {
                        ScrollOwner.ScrollChanged -= new ScrollChangedEventHandler(OnScrollChanged); 
                        _scrollChangedEventAttached = false;
                    }

                    //Assign the new content 
                    _pageCache.Content = value;
 
                    if (_pageCache.Content != null) 
                    {
                        //Add our new events to the content 
                        _pageCache.Content.GetPageNumberCompleted += new GetPageNumberCompletedEventHandler(OnGetPageNumberCompleted);
                    }

                    //Clear out our visual collection so that the old pages (pointing to old content) 
                    //will be replaced with new ones on the next Measure/Arrange pass.
                    ResetVisualTree(false /*pruneOnly*/); 
                    ResetPageViewCollection(); 

                    //Reset our visible pages. 
                    _firstVisiblePageNumber = 0;
                    _lastVisiblePageNumber = 0;

                    // Perf Tracing - PageVisible Changed 
                    if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
                    { 
                        EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEVISIBLEGUID), MS.Utility.EventType.Info, _firstVisiblePageNumber, _lastVisiblePageNumber); 
                    }
 
                    _lastRowChangeExtentWidth = 0.0;
                    _lastRowChangeVerticalOffset = 0.0;

                    //Cause the new content to be laid out in the same fashion as 
                    //the previous content.
                    //If the view is Thumbnails we'll change it to SetColumns 
                    //so that the Column count will be maintained.  This is done 
                    //because we're getting new content which initially has 0
                    //pages and a Thumbnail view only gives decent results after 
                    //the entire content has been loaded; since we don't want to provide a
                    //"jarring" situation (where layout suddenly changes after the content's loaded)
                    //we use SetColumns to maintain the same exact layout as the old content.
                    //(This is consistent with DocumentViewer's overall behavior -- any view setting is 
                    //a "one time thing" and isn't recomputed if the content changes.)
                    if (_documentLayout.ViewMode == ViewMode.Thumbnails) 
                    { 
                        _documentLayout.ViewMode = ViewMode.SetColumns;
                    } 
                    QueueUpdateDocumentLayout(_documentLayout);

                    //Invalidate Measure and our IDSI so that properties changed
                    //by the content assignment will be properly updated. 
                    InvalidateMeasure();
                    InvalidateDocumentScrollInfo(); 
                } 
            }
        } 

        /// 
        /// Indicates the number of pages currently in the document.
        ///  
        /// 
        public int PageCount 
        { 
            get
            { 
                return _pageCache.PageCount;
            }
        }
 
        /// 
        /// When queried, FirstVisiblePageNumber returns the first page visible onscreen. 
        ///  
        /// 
        public int FirstVisiblePageNumber 
        {
            get
            {
                return _firstVisiblePageNumber; 
            }
        } 
 
        /// 
        /// Returns the current Scale factor applied to the pages given the current settings. 
        /// 
        /// 
        public double Scale
        { 
            get
            { 
                return _rowCache.Scale; 
            }
        } 

        /// 
        /// Returns the current number of Columns of pages displayed given the current settings.
        ///  
        /// 
        public int MaxPagesAcross 
        { 
            get
            { 
                return _maxPagesAcross;
            }
        }
 
        /// 
        /// Specifies the vertical gap between Pages when laid out, in pixel (1/96?) units. 
        ///  
        /// 
        public double VerticalPageSpacing 
        {
            get
            {
                return _rowCache.VerticalPageSpacing; 
            }
 
            set 
            {
                if (!Helper.IsDoubleValid(value)) 
                {
                    throw new ArgumentOutOfRangeException("value");
                }
 
                _rowCache.VerticalPageSpacing = value;
            } 
        } 

        ///  
        /// Specifies the horizontal gap between Pages when laid out, in pixel (1/96?) units.
        /// 
        /// 
        public double HorizontalPageSpacing 
        {
            get 
            { 
                return _rowCache.HorizontalPageSpacing;
            } 

            set
            {
                if (!Helper.IsDoubleValid(value)) 
                {
                    throw new ArgumentOutOfRangeException("value"); 
                } 

                _rowCache.HorizontalPageSpacing = value; 
            }
        }

        ///  
        /// Specifies whether each displayed page should be adorned with a ?Drop Shadow? border or not.
        ///  
        ///  
        public bool ShowPageBorders
        { 
            get
            {
                return _showPageBorders;
            } 

            set 
            { 
                if (_showPageBorders != value)
                { 
                    _showPageBorders = value;

                    //Update our pages' ShowPageBorder properties.
                    //Get the current Visual Collection, which contains our pages. 
                    int count = _childrenCollection.Count;
                    for (int i = 0; i < count; i++) 
                    { 
                        DocumentGridPage dp = _childrenCollection[i] as DocumentGridPage;
 
                        if (dp != null)
                        {
                            dp.ShowPageBorders = _showPageBorders;
                        } 
                    }
                } 
            } 
        }
 
        /// 
        /// Specifies whether the last "view mode" related property change should be locked
        /// for resizing.
        ///  
        /// 
        public bool LockViewModes 
        { 
            get
            { 
                return _lockViewModes;
            }

            set 
            {
                _lockViewModes = value; 
            } 

        } 

        /// 
        /// Returns a TextContainer for current content
        ///  
        /// The content's TextContainer, or null if there is none.
        public ITextContainer TextContainer 
        { 
            get
            { 
                if (_textContainer == null)
                {
                    if (Content != null)
                    { 
                        IServiceProvider isp = Content as IServiceProvider;
                        if (isp != null) 
                        { 
                            _textContainer = (ITextContainer)isp.GetService(typeof(ITextContainer));
                        } 
                    }
                }

                return _textContainer; 
            }
        } 
 
        /// 
        /// Returns the MultiPageTextView for the current content. 
        /// 
        /// 
        public ITextView TextView
        { 
            get
            { 
                if (TextEditor != null) 
                {
                    return TextEditor.TextView; 
                }
                else
                {
                    return null; 
                }
            } 
        } 

        ///  
        /// The collection of currently-visible DocumentPageViews.
        /// 
        public ReadOnlyCollection PageViews
        { 
            get
            { 
                return _pageViews; 
            }
        } 

        /// 
        /// ScrollOwner is the container that controls any scrollbars, headers, etc... that are dependent
        /// on this IScrollInfo's properties.  Implementers of IScrollInfo should call InvalidateScrollInfo() 
        /// on this object when related properties change.
        ///  
        public ScrollViewer ScrollOwner 
        {
            get 
            {
                return _scrollOwner;
            }
 
            set
            { 
                _scrollOwner = value; 
                InvalidateDocumentScrollInfo();
            } 
        }

        /// 
        /// DocumentViewerOwner is the DocumentViewer Control and UI that hosts the IDocumentScrollInfo object. 
        /// This control is dependent on this IDSI?s properties, so implementers of IDSI should call
        /// InvalidateDocumentScrollInfo() on this object when related properties change so that 
        /// DocumentViewer?s UI will be kept in [....].  This property is analogous to IScrollInfo?s ScrollOwner 
        /// property.
        ///  
        /// 
        public DocumentViewer DocumentViewerOwner
        {
            get 
            {
                return _documentViewerOwner; 
            } 

            set 
            {
                _documentViewerOwner = value;
            }
        } 

        #endregion IDocumentScrollInfo 
 
        #endregion Interface Implementations
 
        //-----------------------------------------------------
        //
        //  Protected Methods
        // 
        //------------------------------------------------------
        #region Protected Methods 
 
        /// 
        ///   Derived class must implement to support Visual children. The method must return 
        ///    the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1.
        ///
        ///    By default a Visual does not have any children.
        /// 
        ///  Remark:
        ///       During this virtual call it is not valid to modify the Visual tree. 
        ///  
        protected override Visual GetVisualChild(int index)
        { 
            if(_childrenCollection == null || index < 0 || index >= _childrenCollection.Count)
            {
                throw new ArgumentOutOfRangeException("index", index, SR.Get(SRID.Visual_ArgumentOutOfRange));
            } 

            return _childrenCollection[index]; 
        } 

        ///  
        ///  Derived classes override this property to enable the Visual code to enumerate
        ///  the Visual children. Derived classes need to return the number of children
        ///  from this method.
        /// 
        ///    By default a Visual does not have any children.
        /// 
        ///  Remark: During this virtual method the Visual tree must not be modified. 
        /// 
        protected override int VisualChildrenCount 
        {
            get
            {
                // _childrenCollection cannot be null since its initialized in the constructor 
                return _childrenCollection.Count;
            } 
        } 

        ///  
        /// MeasureOverride is repsonsible for measuring any visible pages to their correct sizes.
        /// 
        /// The upper bound for child sizes
        ///  
        protected override Size MeasureOverride(Size constraint)
        { 
 
            // If layoutSize is infinity, we need to return our absolute smallest size.
            // This might happen if we are inside an element which sizes-to-content. 
            // For DocumentGrid, we use a hard coded constraint.
            if (double.IsInfinity(constraint.Width) || double.IsInfinity(constraint.Height))
            {
                constraint = _defaultConstraint; 
            }
 
            //Determine which pages are visible at the current offset given the current constraint. 
            RecalculateVisualPages(VerticalOffset, constraint);
 
            //Get our visual children count...
            int count = _childrenCollection.Count;

            //Now go through our child collection and measure all the pages to their sizes. 
            for(int i = 0; i < count; i++)
            { 
                //This should be our background. 
                if (i == _backgroundVisualIndex)
                { 
                    Border background = _childrenCollection[i] as Border;

                    if (background == null)
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.DocumentGridVisualTreeContainsNonBorderAsFirstElement));
                    } 
 
                    //We measure this to the size of our constraint.
                    background.Measure(constraint); 
                }
                //Otherwise it's a page.
                else
                { 
                    //Ensure that this is actually a DocumentGridPage.  If it is not,
                    //Then someone's been mucking with our VisualTree, so we'll throw. 
                    DocumentGridPage page = _childrenCollection[i] as DocumentGridPage; 

                    if (page == null) 
                    {
                        throw new InvalidOperationException(SR.Get(SRID.DocumentGridVisualTreeContainsNonDocumentGridPage));
                    }
 
                    //Get the cached size of this page and scale it to our current scale factor.
                    Size pageSize = _pageCache.GetPageSize(page.PageNumber); 
                    pageSize.Width *= Scale; 
                    pageSize.Height *= Scale;
 
                    //Measure the page if necessary.
                    if (!page.IsMeasureValid)
                    {
                        page.Measure(pageSize); 

                        //See if the cached size has changed since we Measured. 
                        //This can happen if in the course of Measuring the page 
                        //a GetPageAsync() calls back immediately with the real page size.
                        //If this happens we need to re-measure the page before we finish here, 
                        //otherwise we'll end up with a page that's Measured to one size
                        //and Arranged to another, which looks bad.
                        Size newPageSize = _pageCache.GetPageSize(page.PageNumber);
                        if (newPageSize != Size.Empty) 
                        {
                            newPageSize.Width *= Scale; 
                            newPageSize.Height *= Scale; 
                            if (newPageSize.Width != pageSize.Width ||
                            newPageSize.Height != pageSize.Height) 
                            {
                                //Measure again.
                                page.Measure(newPageSize);
                            } 
                        }
                    } 
                } 
            }
 
            return constraint;
        }

        ///  
        /// ArrangeOverride is responsible for arranging the previously measured pages in the right order.
        ///  
        /// The final constraint, inside of which everything must live. 
        /// 
        protected override Size ArrangeOverride(Size arrangeSize) 
        {

            if (_viewportHeight != arrangeSize.Height ||
                _viewportWidth != arrangeSize.Width) 
            {
                //Update our Viewport sizes 
                _viewportWidth = arrangeSize.Width; 
                _viewportHeight = arrangeSize.Height;
 
                if( LockViewModes && IsViewLoaded())
                {
                    if (_firstVisiblePageNumber < _pageCache.PageCount && _rowCache.HasValidLayout)
                    { 
                        //If we�re locking the view modes and we have loaded content, then we need to re-apply
                        //the last mode setting since our constraint has changed 
                        ApplyViewParameters(_rowCache.GetRowForPageNumber(_firstVisiblePageNumber)); 
                        MeasureOverride(arrangeSize);
                    } 
                }

                UpdateTextView();
            } 

            //If we have a non-zero viewport size, we should execute any 
            //requests for layout that may have been made but were unable 
            //to complete due to a zero viewport size.
            if (IsViewportNonzero) 
            {

                if (ExecutePendingLayoutRequests())
                { 
                    //We need to re-do layout (RowCache has changed), so call measure here
                    //to ensure everything's updated accordingly. 
                    MeasureOverride(arrangeSize); 
                }
 
            }

            //If our constraint size has changed then we need to
            //alert our parents so they can update their ViewportWidth/Height properties. 
            if ( _previousConstraint != arrangeSize)
            { 
                _previousConstraint = arrangeSize; 
                InvalidateDocumentScrollInfo();
            } 

            //Now we go through the visible rows and arrange the pages within them.
            //Get our visual collection count
            int count = _childrenCollection.Count; 

            //If we have no visual children, there's nothing to arrange 
            //so quit now. 
            if (count == 0)
            { 
                return arrangeSize;
            }

            //Arrange the background first.  This is always child 0. 
            //The background takes up the entire constraint.
            UIElement background =_childrenCollection[_backgroundVisualIndex] as UIElement; 
            background.Arrange(new Rect(new Point(0, 0), arrangeSize)); 

            //The offsets for the current page being arranged. 
            double xOffset;
            double yOffset;

            //The current visual child (aka DocumentGridPage) we're arranging. 
            //The first child in our tree is always the background so we start at
            //1 which is our first page. 
            int visualChild = _firstPageVisualIndex; 

            //Now walk through the visible rows and arrange the pages therein. 
            for (int row = _firstVisibleRow; row < _firstVisibleRow + _visibleRowCount; row++)
            {
                //Calculate the position for this row.
                CalculateRowOffsets(row, out xOffset, out yOffset); 

                //Get the current row. 
                RowInfo currentRow = _rowCache.GetRow(row); 

                //Now we can lay out this row. 
                for (int page = currentRow.FirstPage; page < currentRow.FirstPage + currentRow.PageCount; page++)
                {
                    //This should never, ever happen so we'll throw if it does.
                    if (visualChild > _childrenCollection.Count - 1) 
                    {
                        throw new InvalidOperationException(SR.Get(SRID.DocumentGridVisualTreeOutOfSync)); 
                    } 

                    //Get the cached size of this page. 
                    Size pageSize = _pageCache.GetPageSize(page);

                    //Scale it by our scale factor
                    pageSize.Width *= Scale; 
                    pageSize.Height *= Scale;
 
                    //Arrange the page if necessary 
                    UIElement uiPage = _childrenCollection[visualChild] as UIElement;
                    if (uiPage != null) 
                    {
                        Point pageOffset;
                        //Move the page to the right place based on the FlowDirection of the content.
                        if (_pageCache.IsContentRightToLeft) 
                        {
                            pageOffset = new Point(Math.Max(ViewportWidth, ExtentWidth) - (xOffset + pageSize.Width), yOffset); 
                        } 
                        else
                        { 
                            pageOffset = new Point(xOffset, yOffset);
                        }
                        uiPage.Arrange(new Rect(pageOffset, pageSize));
                    } 
                    else
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.DocumentGridVisualTreeContainsNonUIElement)); 
                    }
 
                    //Increment our horizontal offset to point to where the next page should go.
                    xOffset += (pageSize.Width+HorizontalPageSpacing);

                    //Move to the next page. 
                    visualChild++;
                } 
            } 

            // As we scroll we need to keep the AdornerLayer up-to-date. 
            // This ensures that annotation components scroll with the content.
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);
            if (layer != null && layer.GetAdorners(this) != null)
                layer.Update(this); 

            return arrangeSize; 
        } 

        ///  
        /// Override the OnPreviewMouseLeftButtonDown method so that we can trap the
        /// keyboard+mouse events needed for Rubberband selection.
        /// Clicking the Left mouse button while holding Alt will enable the Rubberband
        /// selection "mode" until the Left mouse button is again pressed without the 
        /// Alt key held.
        ///  
        /// The MouseButtonEventArgs associated with this mouse event. 
        protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
        { 

            //Determine whether either Alt key is being held at this moment.
            bool altKeyDown = Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt);
 
            //If the Alt key is held and we aren't currently in RubberBandSelection mode,
            //we can create and attach our RubberBandSelector now. 
            //We'll stay in this mode until the mouse is clicked without the Alt key held. 
            if (altKeyDown && _rubberBandSelector == null )
            { 
                //See if our content implements IServiceProvider.
                IServiceProvider serviceProvider = Content as IServiceProvider;
                if (serviceProvider != null)
                { 
                    //See if our content supports rubber band selection.
                    _rubberBandSelector = serviceProvider.GetService(typeof(RubberbandSelector)) as RubberbandSelector; 
 
                    if (_rubberBandSelector != null)
                    { 
                        DocumentViewerOwner.Focus(); // text editor needs to be focused when cleared
                        ITextRange textRange = TextEditor.Selection;
                        textRange.Select(textRange.Start, textRange.Start); //clear selection
                        DocumentViewerOwner.IsSelectionEnabled = false; 

                        _rubberBandSelector.AttachRubberbandSelector((FrameworkElement)this); //attach the Rubber band selector. 
 
                    }
                } 
            }
            //We got a mouse-down event and the Alt key is not being held, so we revert back
            //to normal selection mode now.
            else if (!altKeyDown && _rubberBandSelector != null) 
            {
                //Detach the Rubberband Selector 
                if (_rubberBandSelector != null) 
                {
                    _rubberBandSelector.DetachRubberbandSelector(); 
                    _rubberBandSelector = null;
                }

                DocumentViewerOwner.IsSelectionEnabled = true; 
            }
 
        } 

        #endregion Protected Methods 

        //-----------------------------------------------------
        //
        //  Private Methods 
        //
        //------------------------------------------------------ 
        #region Private Methods 

        ///  
        /// Recalculates the set of pages that are currently visible and updates
        /// DocumentGrid's VisualCollection so it contains them.
        /// 
        /// The viewport to search for visible pages in. 
        /// The offset in the document to start the search.
        private void RecalculateVisualPages(double offset, Size constraint) 
        { 

            //Do we actually have any rows in the cache? 
            //If not, we can just clear our visual collection and return.
            if (_rowCache.RowCount == 0)
            {
                ResetVisualTree(false /*pruneOnly*/); 
                ResetPageViewCollection();
                _firstVisibleRow = 0; 
                _visibleRowCount = 0; 
                _firstVisiblePageNumber = 0;
                _lastVisiblePageNumber = 0; 

                // Perf Tracing - PageVisible Changed
                if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
                { 
                    EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEVISIBLEGUID), MS.Utility.EventType.Info, _firstVisiblePageNumber, _lastVisiblePageNumber);
                } 
 
                return;
            } 

            int newFirstVisibleRow = 0;
            int newVisibleRowCount = 0;
 
            //Ask the RowCache for the currently visible rows.
            _rowCache.GetVisibleRowIndices(offset, 
                                            offset + constraint.Height, 
                                            out newFirstVisibleRow,
                                            out newVisibleRowCount); 

            //Do we have no visible rows at all?  Then clear the Visual collection and return.
            if (newVisibleRowCount == 0)
            { 
                ResetVisualTree(false /*pruneOnly*/);
                ResetPageViewCollection(); 
                _firstVisibleRow = 0; 
                _visibleRowCount = 0;
                _firstVisiblePageNumber = 0; 
                _lastVisiblePageNumber = 0;

                // Perf Tracing - PageVisible Changed
                if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
                {
                    EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEVISIBLEGUID), MS.Utility.EventType.Info, _firstVisiblePageNumber, _lastVisiblePageNumber); 
                } 

                return; 
            }

            //Now walk through each visible row and compare the pages therein
            //with the current set of pages in our Visual Collection. 
            //New pages are inserted into the collection, unused pages are removed.
 
            //Get the current first and last pages visible. 
            int firstPage = -1;
            int lastPage = -1; 

            //If we have more visuals than just the background (element 0)
            //then we have pages, so get the page numbers from them.
            if (_childrenCollection.Count > _firstPageVisualIndex) 
            {
                DocumentGridPage firstDp = _childrenCollection[1] as DocumentGridPage; 
                firstPage = firstDp != null ? firstDp.PageNumber : -1; 

                DocumentGridPage lastDp = _childrenCollection[_childrenCollection.Count-1] as DocumentGridPage; 
                lastPage = lastDp != null ? lastDp.PageNumber : -1;
            }

 
            //Update our First & LastVisiblePage properties
            RowInfo firstRow = _rowCache.GetRow(newFirstVisibleRow); 
            _firstVisiblePageNumber = firstRow.FirstPage; 
            RowInfo lastRow = _rowCache.GetRow(newFirstVisibleRow + newVisibleRowCount - 1);
            _lastVisiblePageNumber = lastRow.FirstPage + lastRow.PageCount - 1; 

            // Perf Tracing - PageVisible Changed
            if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal))
            { 
                EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGEVISIBLEGUID), MS.Utility.EventType.Info, _firstVisiblePageNumber, _lastVisiblePageNumber);
            } 
 
            //Update our cached visible row info (used by Measure/Arrange)
            _firstVisibleRow = newFirstVisibleRow; 
            _visibleRowCount = newVisibleRowCount;


            //If IDSI properties have changed (namely the First/LastVisiblePage properties) we invalidate them now. 
            if (_firstVisiblePageNumber != firstPage ||
                _lastVisiblePageNumber != lastPage) 
            { 

                //Create our temporary VisualCollection, which will hold the new list of 
                //visible pages.
                ArrayList visiblePages = new ArrayList();

                //Now walk through the visible rows and add the pages to our temporary list. 
                for (int i = _firstVisibleRow; i < _firstVisibleRow + _visibleRowCount; i++)
                { 
                    //Get the row 
                    RowInfo currentRow = _rowCache.GetRow(i);
 
                    //Walk through the row
                    for (int j = currentRow.FirstPage; j < currentRow.FirstPage + currentRow.PageCount; j++)
                    {
                        //Is this page new? 
                        if (j < firstPage || j > lastPage || _childrenCollection.Count <= _firstPageVisualIndex)
                        { 
                            //Create a new page and add it to our temporary visual collection. 
                            DocumentGridPage dp = new DocumentGridPage(Content);
                            dp.ShowPageBorders = ShowPageBorders; 
                            dp.PageNumber = j;

                            //Attach the Loaded event handler
                            dp.PageLoaded += new EventHandler(OnPageLoaded); 
                            visiblePages.Add(dp);
                        } 
                        else 
                        {
                            //This page already exists in our visual collection, so we copy that entry over 
                            //from the visual collection instead of creating a new page.
                            //(We start at 1 to skip over the background visual.)
                            visiblePages.Add(_childrenCollection[_firstPageVisualIndex + j - Math.Max(0, firstPage)]);
                        } 
                    }
                } 
 
                //Copy our new visible page collection over to the VisualCollection and update
                //the MultiPageTextView's list of visible DocumentPageViews. 
                //First, prune our visual tree so it only contains the set of pages that are visible
                //before and after the layout change.
                ResetVisualTree(true /*pruneOnly*/);
 
                Collection documentPageViews =
                    new Collection(); 
 
                //State machine for updating visual collection without removing still-visible pages
                //from the collection: 
                //We walk through the set of visible pages that we computed above.
                //We insert new pages before existing pages, and add pages after existing pages.
                //
                //We take advantage of the fact that both the current set of pages in the Visual Collection 
                //and the set of to-be-made visible pages in visiblePages are in strictly increasing order
                //with no gaps between pages. 
                //To better understand how the below works, refer to Fig. A below: 
                //
                //         +-----------------------------+ 
                //  +------+                             +------+
                //  | new  |  Pruned Visual Collection   |  new |
                //  +------+   (Existing Page Visuals)   +------+
                //         +-----------------------------+ 
                //  ^      ^                             ^
                //  |      |                             | 
                //  A      B                             C 
                //
                //                                 [Fig. A: Diagram of states] 
                //
                //- The routine starts off in state A (BeforeExisting).
                //  At this point we insert any new pages in the visiblePages collection until we
                //  find a page in the visiblePages collection that is also in the Pruned Visual Collection. 
                //  This indicates that the set of common unchanged pages has been reached.
                //  The state machine then transitions to state B (DuringExisting). 
                //- The routine stays in B merely iterating through visiblePages until it finds a page 
                //  in visiblePages that does not correspond to a page in the Visual Tree.  This indicates
                //  that the end of the set of common unchanged pages has been reached; at this point we 
                //  add the new page and transition to state C (AfterExisting).
                //- State C ends when no more pages are left in visiblePages.
                VisualTreeModificationState state = VisualTreeModificationState.BeforeExisting;
 
                //The index pointing to the first common page still in the visual tree after the above pruning.
                int vcIndex = _firstPageVisualIndex; 
 
                for (int i = 0; i < visiblePages.Count; i++)
                { 
                    Visual current = (Visual)visiblePages[i];

                    switch (state)
                    { 
                        case VisualTreeModificationState.BeforeExisting:
                            //Keep inserting until we find a page that already exists 
                            if (vcIndex < _childrenCollection.Count && _childrenCollection[vcIndex] == current) 
                            {
                                //Move to "During" state 
                                state = VisualTreeModificationState.DuringExisting;
                            }
                            else
                            { 
                                //Insert this page at the current index.
                                _childrenCollection.Insert(vcIndex, current); 
                            } 
                            //Increment the index into the Visual collection to ensure that it continues
                            //to point to the first common page. 
                            vcIndex++;
                            break;

                        case VisualTreeModificationState.DuringExisting: 
                            //Leave the visual collection alone until we find a page that isn't in the collection
                            //or run out of pages in the collection. 
                            if (vcIndex >= _childrenCollection.Count || _childrenCollection[vcIndex] != current) 
                            {
                                //Move to "After" state 
                                state = VisualTreeModificationState.AfterExisting;
                                //Append this page to the end.
                                _childrenCollection.Add(current);
                            } 
                            //Keep moving through the Visual collection...
                            vcIndex++; 
                            break; 

                        case VisualTreeModificationState.AfterExisting: 
                            //Keep going until the end.
                            _childrenCollection.Add(current);
                            break;
                    } 

                    //Add this to the collection of PageViews. 
                    documentPageViews.Add(((DocumentGridPage)visiblePages[i]).DocumentPageView); 
                }
 
                //Update our collection of PageViews with the current set.
                _pageViews = new ReadOnlyCollection(documentPageViews);

                //Tell our parent DocumentViewer that we've updated our PageView collection. 
                InvalidatePageViews();
                InvalidateDocumentScrollInfo(); 
            } 

        } 

        /// 
        /// Handles the PageLoaded event for a given page, and kicks off
        /// BringIntoView actions where necessary. 
        /// 
        ///  
        ///  
        private void OnPageLoaded(object sender, EventArgs args)
        { 
            DocumentGridPage page = sender as DocumentGridPage;
            Invariant.Assert(page != null, "Invalid sender for OnPageLoaded event.");

            //Detach the event handler, we don't need this event any longer. 
            page.PageLoaded -= new EventHandler(OnPageLoaded);
 
            //Is there a MakeVisible operation waiting for this page to be loaded? 
            //If so, invoke its dispatcher in the background.
            if (_makeVisiblePageNeeded == page.PageNumber) 
            {
                _makeVisiblePageNeeded = -1;
                _makeVisibleDispatcher.Priority = DispatcherPriority.Background;
            } 

            // Perf Tracing - PageLoaded 
            if (EventTrace.IsEnabled(EventTrace.Flags.performance, EventTrace.Level.normal)) 
            {
                EventTrace.EventProvider.TraceEvent(EventTrace.GuidFromId(EventTraceGuidId.DRXPAGELOADEDGUID), MS.Utility.EventType.Info, page.PageNumber); 
            }
        }

 
        /// 
        /// Calculates the X and Y offsets of the given row based on the current 
        /// Viewport dimensions. 
        /// 
        /// The row to calculate the offsets of 
        /// The X offset of the row
        /// The Y offset of the row
        private void CalculateRowOffsets(int row, out double xOffset, out double yOffset)
        { 
            xOffset = 0.0;
            yOffset = 0.0; 
 
            //Get the current row.
            RowInfo currentRow = _rowCache.GetRow(row); 

            //Figure out the width we'll use to center the pages.
            //If the ViewportWidth is wider than the document, we use that.  Otherwise we center
            //the content based on the width of the document. 
            double centerWidth = Math.Max(ViewportWidth, ExtentWidth);
 
            //Figure out the offset of the upper left corner of this row. 

            //X Coordinate: 
            //If this is the last row in the document and we're viewing
            //uniformly-sized pages then this row is
            //always left-aligned (not centered).
            if (row == _rowCache.RowCount -1 && !_pageCache.DynamicPageSizes) 
            {
                //This is the last row, so we arrange it such that the left edge of the 
                //page is flush with the left edge of the document. 
                xOffset = (centerWidth - ExtentWidth) / 2.0 +
                    (HorizontalPageSpacing / 2.0) - HorizontalOffset; 
            }
            else
            {
                //Otherwise we center this page inside the document. 
                xOffset = (centerWidth - currentRow.RowSize.Width) / 2.0 +
                    (HorizontalPageSpacing / 2.0) - HorizontalOffset; 
            } 

            //Y Coordinate: 
            if (ExtentHeight > ViewportHeight)
            {
                //The document is taller than the viewport, so we just display
                //the content at the current offset. 
                yOffset = currentRow.VerticalOffset +
                    (VerticalPageSpacing / 2.0) - VerticalOffset; 
            } 
            else
            { 
                //If the document is shorter than the Viewport we're showing it in,
                //we center it vertically within the viewport.  We do not need to factor in
                //VerticalOffset as it is always 0.0 in this scenario.
                yOffset = currentRow.VerticalOffset + 
                    (ViewportHeight - ExtentHeight) / 2.0 + (VerticalPageSpacing / 2.0);
            } 
        } 

        ///  
        /// Resets DocumentGrid's visual tree to its initial state or prunes non-visible pages.
        /// This is empty except for a border which acts as a background.
        /// 
        /// Whether to clear all pages, or only those that are not visible. 
        private void ResetVisualTree(bool pruneOnly)
        { 
 
            //We need to dispose and remove any pages that will no longer be in the visual tree.
            for (int i = _childrenCollection.Count - 1; i >= _firstPageVisualIndex; i--) 
            {
                DocumentGridPage dp = _childrenCollection[i] as DocumentGridPage;
                if (dp != null &&
                        (!pruneOnly || 
                        _rowCache.RowCount == 0 ||
                        dp.PageNumber < _firstVisiblePageNumber || 
                        dp.PageNumber > _lastVisiblePageNumber)) 
                {
                    //This page will not be visible any longer, so get rid of it. 
                    //Remove this page from the Visual tree.
                    _childrenCollection.Remove(dp);

                    //Remove any PageLoaded event handlers 
                    dp.PageLoaded -= new EventHandler(OnPageLoaded);
 
                    //Dispose of the page. 
                    ((IDisposable)dp).Dispose();
                } 
            }

            //Create the background if it does not exist.
            if (_documentGridBackground == null) 
            {
                //We create a Border with a transparent background so that it can 
                //participate in Hit-Testing (which allows click events like those 
                //for our Context Menu to work).
                _documentGridBackground = new Border(); 
                _documentGridBackground.Background = Brushes.Transparent;

                //Add the background in.
                _childrenCollection.Add(_documentGridBackground); 
            }
 
        } 

        ///  
        /// Nulls out the PageViews collection and notifies DocumentViewer of the change.
        /// 
        private void ResetPageViewCollection()
        { 
            //Null out our collection of PageViews.
            _pageViews = null; 
 
            //Tell our parent DocumentViewer that we've updated our PageView collection.
            InvalidatePageViews(); 
        }

        #region MakeVisible Helpers
 
        /// 
        /// Handles the GetPageNumberCompleted event fired as a result of a MakeContentVisibleAsync 
        /// call.  At this point we know the page number corresponding to the ContentPosition we need 
        /// to make visible, so we invoke MakeVisibleAsync() to bring it into view.
        ///  
        /// The sender of this event
        /// The args associated with this event.
        /// We expect e.UserState to be a MakeVisibleData.
        private void OnGetPageNumberCompleted(object sender, GetPageNumberCompletedEventArgs e) 
        {
            if (e == null) 
            { 
                throw new ArgumentNullException("e");
            } 
            //Ensure that the UserState passed with this event contains an
            //MakeVisibleData object. If not, we ignore it as this event
            //could have originated from someone else calling GetPageNumberAsync.
            if (e.UserState is MakeVisibleData) 
            {
                MakeVisibleData data = (MakeVisibleData)e.UserState; 
                MakeVisibleAsync(data, e.PageNumber); 
            }
        } 


        /// 
        /// Makes the specified object on the specified page visible, which may be an 
        /// asynchronous operation if the page is not already in view.
        ///  
        /// Data corresponding to the object to be made visible. 
        /// The page number the object is on.
        private void MakeVisibleAsync(MakeVisibleData data, int pageNumber) 
        {

            //This page may not be currently visible.
            //First we need to make the page visible, if necessary. 
            //This will be done at background priority to allow currently-loading pages time to
            //finish.  If we do not do this, then in the corner case of: 
            // 1) Document has just been loaded (for example, just after a hyperlink navigation to the doc) 
            // 2) Document has non 8.5x11-sized pages at the beginning of the document
            // 3) Navigation is to a page past the initially visible pages. 
            //In this case, the order of operations is something like this:
            // 1) Initially visible pages start loading
            // 2) MakeVisible is invoked, and MakePageVisible is called, which uses cached
            //    page info to decide where to scroll to.  (8.5x11 is assumed until a page is loaded) 
            // 3) Document is scrolled to position computed in #2
            // 4) Pages loading in #1 finish loading, GetPageCompleted is called, PageCache is updated, 
            //    and the document layout changes.  This will shift the page we actually want to see up or down, 
            //    potentially by a substantial amount.
            // 5) User is now looking at the wrong page, and if the page that the hyperlink target is on isn't 
            //    visible at this point then the MakeVisible operation fails in MakeVisibleImpl since the target
            //    isn't in the visual tree.
            // Everyone got that?  So, doing the initial "MakePageVisible" operation at Background priority
            // allows step #4 above to finish _before_ we do steps 2 and 3, so the right page will be visible 
            // in 5 and the MakeVisible operation will succeed.
            Dispatcher.BeginInvoke(DispatcherPriority.Background, 
                   new BringPageIntoViewCallback( BringPageIntoViewDelegate ), data, pageNumber); 

        } 

        /// 
        /// Delegate method used to bring a specified page into view.
        ///  
        /// 
        ///  
        private void BringPageIntoViewDelegate(MakeVisibleData data, int pageNumber) 
        {
 
            //Make the page visible if necessary:
            // - If our layout is not yet valid
            // - If the visual being made visible is a FixedPage and
            //   the bring-into-view rect is the entire page 
            //   (in which case we always want to move it as close to the top of the
            //   viewport as possible even if it is already partially visible) 
            // - If the page isn't currently visible. 
            if (!_rowCache.HasValidLayout ||
                (data.Visual is FixedPage && 
                 data.Visual.VisualContentBounds == data.Rect) ||
                pageNumber < _firstVisiblePageNumber ||
                pageNumber > _lastVisiblePageNumber)
            { 
                MakePageVisible(pageNumber);
            } 
 
            //The page's contents have already been loaded, we can bring the object into view immediately.
            if (IsPageLoaded(pageNumber)) 
            {
                MakeVisibleImpl(data);
            }
            else 
            {
                //Now we have to wait for the page to be loaded so that we can 
                //ensure that the object itself is visible. 
                //As pages are loaded, this page will be checked for, and the dispatcher below
                //executed as appropriate. 
                _makeVisiblePageNeeded = pageNumber;
                _makeVisibleDispatcher = Dispatcher.BeginInvoke(DispatcherPriority.Inactive,
                    (DispatcherOperationCallback)delegate(object arg)
                    { 
                        MakeVisibleImpl((MakeVisibleData)arg);
                        return null; 
                    }, data); 

            } 
        }

        /// 
        /// Implementation of MakeVisible logic, the final step in a MakeVisible operation. 
        /// 
        ///  
        private void MakeVisibleImpl(MakeVisibleData data) 
        {
            if (data.Visual != null) 
            {
                //Ensure that the passed-in visual is a descendant of DocumentGrid.
                if (((Visual)this).IsAncestorOf(data.Visual))
                { 
                    //Now we can determine where this visual is relative to the upper left
                    //corner of the DocumentGrid and thus make it visible. 
                    GeneralTransform transform = data.Visual.TransformToAncestor(this); 
                    Rect boundingRect = (data.Rect != Rect.Empty) ? data.Rect : data.Visual.VisualContentBounds;
 
                    Rect offsetRect = transform.TransformBounds(boundingRect);
                    MakeRectVisible(offsetRect, false /* alwaysCenter */);
                }
            } 
            else if (data.ContentPosition != null)
            { 
                ITextPointer tp = data.ContentPosition as ITextPointer; 

                //If we have a valid TextView and the TextPointer is in that TextView 
                //we can make the TextPointer's Rect visible...
                if (TextViewContains(tp))
                {
                    MakeRectVisible(TextView.GetRectangleFromTextPosition(tp), false /* alwaysCenter */); 
                }
            } 
            else 
            {
                Invariant.Assert(false, "Invalid object brought into view."); 
            }
        }

 
        /// 
        /// Moves the specified rectangle into view, if it isn't already visible. 
        ///  
        /// A rectangle relative to the upper-left corner of the Viewport
        /// Whether to center the rect at all times or only when necessary. 
        private void MakeRectVisible(Rect r, bool alwaysCenter)
        {
            if (r != Rect.Empty)
            { 
                //Calculate the real position of the rectangle in the document.
                Rect translatedRect = new Rect(HorizontalOffset + r.X, VerticalOffset + r.Y, 
                                               r.Width, r.Height); 

                Rect viewportRect = new Rect(HorizontalOffset, VerticalOffset, 
                                             ViewportWidth,
                                             ViewportHeight);

                //Unless the alwaysCenter flag is set, if the new position is already 
                //visible we don't need to shift the viewport. Otherwise we shift
                //the offsets so the rect is visible, centering if possible. 
                if (alwaysCenter || !translatedRect.IntersectsWith(viewportRect)) 
                {
                    SetHorizontalOffsetInternal(translatedRect.X - (ViewportWidth / 2.0)); 
                    SetVerticalOffsetInternal(translatedRect.Y - (ViewportHeight / 2.0));
                }
            }
        } 

        ///  
        /// Moves the specified IP into view, if it isn't already visible. 
        /// 
        /// A rectangle relative to the upper-left corner of the Viewport which represents 
        /// an IP (Insertion Point)
        private void MakeIPVisible(Rect r)
        {
            if (r != Rect.Empty && TextEditor != null) 
            {
                Rect viewportRect = new Rect(HorizontalOffset, VerticalOffset, 
                                             ViewportWidth, 
                                             ViewportHeight);
 
                //If the new position is already fully visible, we don't need to shift the viewport,
                //otherwise we shift the offsets so the rect is visible, moving as minimally as possible.
                if (!viewportRect.Contains(r))
                { 
                    //Scroll left/right if the IP is off the screen Horizontally.
                    if (r.X < HorizontalOffset) 
                    { 
                        SetHorizontalOffsetInternal(HorizontalOffset - (HorizontalOffset - r.X));
                    } 
                    else if (r.X > HorizontalOffset + ViewportWidth)
                    {
                        SetHorizontalOffsetInternal(HorizontalOffset + (r.X - (HorizontalOffset + ViewportWidth)));
                    } 

                    //Scroll up/down if part of the IP is off the screen Vertically. 
                    if (r.Y < VerticalOffset) 
                    {
                        SetVerticalOffsetInternal(VerticalOffset - (VerticalOffset - r.Y)); 
                    }
                    else if (r.Y + r.Height > VerticalOffset + ViewportHeight)
                    {
                        SetVerticalOffsetInternal(VerticalOffset + ((r.Y + r.Height) - (VerticalOffset + ViewportHeight))); 
                    }
                } 
            } 
        }
 
        /// 
        /// Invokes GetPageNumberAsync on the passed in ContentPosition.
        /// The handler for GetPageNumberAsync will bring that ContentPosition into view.
        ///  
        /// The MakeVisibleData to be made visible
        private void MakeContentPositionVisibleAsync(MakeVisibleData data) 
        { 
            //If the ContentPosition is valid, we can make it visible now.
            if (data.ContentPosition != null && data.ContentPosition != ContentPosition.Missing) 
            {
                Content.GetPageNumberAsync(data.ContentPosition, data);
            }
        } 

        #endregion MakeVisible Helpers 
 
        /// 
        /// Places a delegate for an SetScale call on the queue.  We do this for 
        /// performance reasons, as changing the document scale takes significant time.
        /// 
        /// 
        private void QueueSetScale(double scale) 
        {
            //If there's a SetScale operation in the Pending state, then we'll 
            //abort it (we only care that the last operation invoked completes.) 
            if (_setScaleOperation != null &&
                _setScaleOperation.Status == DispatcherOperationStatus.Pending) 
            {
                _setScaleOperation.Abort();
            }
            _setScaleOperation = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, 
                   new DispatcherOperationCallback(SetScaleDelegate),
                   scale); 
        } 

        private object SetScaleDelegate(object scale) 
        {
            if (!(scale is double))
            {
                return null; 
            }
 
            double newScale = (double)scale; 
            _documentLayout.ViewMode = ViewMode.Zoom;
 
            //Get the current visible selection, if any.
            //The results of this will determine how we handle the
            //zoom operation.
            ITextPointer selection = GetVisibleSelection(); 

            if (selection != null) 
            { 
                //The visible-IP case:
                //First, we find out what page the IP is on: 
                int selectionPage = GetPageNumberForVisibleSelection(selection);

                //Then we scale the document:
                UpdateLayoutScale(newScale); 

                //Now we ensure that the selection page is still 
                //visible: 
                MakePageVisible(selectionPage);
 
                //The rest of this process is done asynchronously --
                //We wait for LayoutUpdated (which happens after layout
                //but before rendering) and then make the IP visible.
                //This will cause the IP to be centered without any 
                //visible flicker.
 
                //Attach a LayoutUpdated handler. 
                LayoutUpdated += new EventHandler(OnZoomLayoutUpdated);
 
            }
            else
            {
                //The non-visible-IP case: 
                //This is considerably easier.  The expected behavior is that
                //we zoom in on the upper-left corner of the currently-visible 
                //content.  This is accomplished by scaling the Vertical and 
                //Horizontal offsets in tandem with the document scale which will
                //put us approximately where we were before. 

                //Scale the document:
                UpdateLayoutScale(newScale);
            } 

            return null; 
        } 

        ///  
        /// Updates the Scale applied to our RowCache.
        /// 
        /// 
        private void UpdateLayoutScale(double scale) 
        {
            if (!DoubleUtil.AreClose(scale, Scale)) 
            { 
                double oldExtentHeight = ExtentHeight;
                double oldExtentWidth = ExtentWidth; 

                //Tell our RowCache to rescale the layout.
                _rowCache.Scale = scale;
 
                //Rescale our offsets
                //Divide the old extents by the new to determine the amount to 
                //scale the offsets 
                double verticalScale = oldExtentHeight == 0.0 ? 1.0 : ExtentHeight / oldExtentHeight;
                double horizontalScale = oldExtentWidth == 0.0 ? 1.0 : ExtentWidth / oldExtentWidth; 

                //Now we scale the offsets.
                SetVerticalOffsetInternal(_verticalOffset * verticalScale);
                SetHorizontalOffsetInternal(_horizontalOffset * horizontalScale); 

                InvalidateMeasure(); 
 
                //Invalidate the measure of our visual children so that they can
                //be resized. 
                InvalidateChildMeasure();

                //Invalidate our parents' properties
                InvalidateDocumentScrollInfo(); 
            }
        } 
 
        /// 
        /// Places a delegate for an UpdateDocumentLayout call on the queue.  We do this for 
        /// performance reasons, as changing the document layout takes significant time.
        /// 
        /// 
        private void QueueUpdateDocumentLayout(DocumentLayout layout) 
        {
            // Increase the count of pending DocumentLayout delegates 
            _documentLayoutsPending++; 
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input,
                   new DispatcherOperationCallback(UpdateDocumentLayoutDelegate), 
                   layout);
        }

        ///  
        /// Asynchronously invokes UpdateDocumentLayout.
        ///  
        ///  
        /// 
        private object UpdateDocumentLayoutDelegate(object layout) 
        {
            if (layout is DocumentLayout)
            {
                UpdateDocumentLayout((DocumentLayout)layout); 
            }
            // Decrease the count of pending DocumentLayout delegates 
            _documentLayoutsPending--; 

            return null; 
        }

        /// 
        /// Updates the current layout of our RowCache to the specified number of 
        /// columns.
        ///  
        ///  
        private void UpdateDocumentLayout(DocumentLayout layout)
        { 
            // Check if the layout is for a Vertical or Horizontal offset update,
            // in which case the value can be changed immediately.
            if (layout.ViewMode == ViewMode.SetHorizontalOffset)
            { 
                SetHorizontalOffsetInternal(layout.Offset);
                return; 
            } 
            else if (layout.ViewMode == ViewMode.SetVerticalOffset)
            { 
                SetVerticalOffsetInternal(layout.Offset);
                return;
            }
 
            //Store off the layout in case we need it later.
            //(For example, if our viewport is (0,0). 
            _documentLayout = layout; 

            //Update MaxPagesAcross 
            _maxPagesAcross = _documentLayout.Columns;

            //If we have a non (0,0) Viewport then we can calculate a new layout.
            //Otherwise we set our "Layout Requested" flag so that when we get 
            //a non-zero Viewport size we'll compute the requested layout.
            if (IsViewportNonzero) 
            { 
                //If this is a Thumbnails layout, we need to calculate how many
                //columns we should fit on the pivotRow. 
                if (_documentLayout.ViewMode == ViewMode.Thumbnails)
                {
                    _maxPagesAcross = _documentLayout.Columns = CalculateThumbnailColumns();
                } 

                //We need to determine the page that has the active focus so we know what page 
                //to keep visible in the new layout. 
                int pivotPage = GetActiveFocusPage();
 
                //Ask the RowCache to recalculate the layout based
                //on the specified pivot page and the number of columns
                //requested.
                //The RowCache will call us back with a 
                //RowLayoutCompleted event when the layout is complete
                //and we'll update the scale and invalidate our layout there. 
                _rowCache.RecalcRows(pivotPage, _documentLayout.Columns); 

                _isLayoutRequested = false; 
            }
            else
            {
                _isLayoutRequested = true; 
            }
        } 
 
        /// 
        /// Calls UpdateLayout with the saved column and view mode parameters, 
        /// if there's a previously requested layout to perform.
        /// Used when we get a non-zero Viewport size and we've previously requested
        /// a new Row layout.
        ///  
        /// A bool indicating whether a new layout was calculated.
        private bool ExecutePendingLayoutRequests() 
        { 
            if (_isLayoutRequested)
            { 
                UpdateDocumentLayout( _documentLayout );
                return true;
            }
 
            return false;
        } 
 
        /// 
        /// Set the HorizontalOffset to the value provided, the value will be set immediately. 
        /// 
        /// 
        private void SetHorizontalOffsetInternal(double offset)
        { 
            if (!DoubleUtil.AreClose(_horizontalOffset, offset))
            { 
                if (Double.IsNaN(offset)) 
                {
                    throw new ArgumentOutOfRangeException("offset"); 
                }

                _horizontalOffset = offset;
                InvalidateMeasure(); 
                InvalidateDocumentScrollInfo();
                UpdateTextView(); 
            } 
        }
 
        /// 
        /// Set the VerticalOffset to the value provided, the value will be set immediately.
        /// 
        ///  
        private void SetVerticalOffsetInternal(double offset)
        { 
            if (!DoubleUtil.AreClose(_verticalOffset, offset)) 
            {
                if (Double.IsNaN(offset)) 
                {
                    throw new ArgumentOutOfRangeException("offset");
                }
 
                _verticalOffset = offset;
                InvalidateMeasure(); 
                InvalidateDocumentScrollInfo(); 
                UpdateTextView();
            } 
        }

        /// 
        /// Updates the TextView so that it knows about size and position changes. 
        /// 
        private void UpdateTextView() 
        { 
            MultiPageTextView tv = TextView as MultiPageTextView;
            if (tv != null) 
            {
                tv.OnPageLayoutChanged();
            }
        } 

        ///  
        /// Calculates the number of columns to fit on one row so that the resultant 
        /// view will approximate a "thumbnail" view.
        /// The basic idea is that we attempt to fit (and scale) a number of pages on the row 
        /// such that the resultant view given the current Viewport will show as many
        /// pages as possible, with a lower bound of a 5% zoom.
        /// We attempt to optimize to minimize wasted space, where possible.  This may mean
        /// that not all pages will be completely displayed, but we favor a better looking 
        /// layout (less dead space) over being able to see every page.
        ///  
        /// The number of pages to show on the first row. 
        private int CalculateThumbnailColumns()
        { 
            //If our current Viewport size is zero, we'll just return 1.
            //(because there always needs to be at least 1 page on a row regardless.)
            if (!IsViewportNonzero)
            { 
                return 1;
            } 
 
            //If we have no pages, we'll just return 1
            //(because there always needs to be at least 1 page on a row regardless.) 
            if (_pageCache.PageCount == 0)
            {
                return 1;
            } 

            //We use the first page of the document as our basis for our calculations. 
            //This means that documents with varying page sizes can potentially have 
            //sub-optimal thumbnail views.
            Size pageSize = _pageCache.GetPageSize(0); 

            //Calculate the viewport's aspect ratio.
            double viewportAspect = ViewportWidth / ViewportHeight;
 
            //Calculate the maximum number of columns we can lay out on a single row
            //without needing to scale below our floor of 12.5%. 
            int maxColumns = 
                (int)Math.Floor( ViewportWidth /
                    (CurrentMinimumScale * pageSize.Width + HorizontalPageSpacing)); 

            //Ensure this value isn't greater than the number of pages in the document,
            //since we can't possibly lay out a row with more than that number of pages in it.
            maxColumns = Math.Min(maxColumns, _pageCache.PageCount); 
            maxColumns = Math.Min(maxColumns, DocumentViewerConstants.MaximumMaxPagesAcross);
 
            //Now we do the following: 
            //We iterate through the possible permutations of row and column
            //combinations and choose the arrangement of columns that best fits the current 
            //viewport's aspect ratio.
            int minAspectColumns = 1;   //The current optimal number of columns found.
            double minAspectDiff = Double.MaxValue; //The current optimal aspect ratio match
            for (int columns = 1; columns <= maxColumns; columns++) 
            {
                //Calculate the number of rows for this arrangment given the current column count 
                int rows = (int)Math.Floor((double)(_pageCache.PageCount / columns)); 

                //Calculate the approximate dimensions that this layout would 
                //have.
                double width = pageSize.Width * columns;
                double height = pageSize.Height * rows;
 
                //Determine the aspect ratio of this layout.
                double layoutAspect = width / height; 
 
                //See if the aspect ratio of this layout is a closer match for our Viewport
                //than previous attempts. 
                double aspectDiff = Math.Abs(layoutAspect - viewportAspect);
                if ( aspectDiff < minAspectDiff)
                {
                    //It is, so save it. 
                    minAspectDiff = aspectDiff;
                    minAspectColumns = columns; 
                } 
            }
 
            return minAspectColumns;

        }
 
        /// 
        /// Calls InvalidateMeasure() on our visual children in order to force them 
        /// to be re-measured and arranged on the next layout pass.  This is called 
        /// whenever the Scale is changed, as it is only then when re-measuring is
        /// required.  We do this to avoid unnecessary layout/measure passes on our 
        /// pages.
        /// 
        private void InvalidateChildMeasure()
        { 
            //Get the current Visual Collection, which contains our pages.
            int count = _childrenCollection.Count; 
 
            for (int i = 0; i < count; i++)
            { 
                UIElement page = _childrenCollection[i] as UIElement;

                if (page != null)
                { 
                    page.InvalidateMeasure();
                } 
            } 
        }
 
        /// 
        /// Helper function that indicates whether all the pages on the specified
        /// row point to clean cache entries.
        ///  
        /// 
        ///  
        private bool RowIsClean(RowInfo row) 
        {
            bool clean = true; 

            for (int i = row.FirstPage; i < row.FirstPage + row.PageCount; i++)
            {
                if (_pageCache.IsPageDirty(i)) 
                {
                    clean = false; 
                    break; 
                }
            } 

            return clean;
        }
 
        /// 
        /// Checks that the current scale factor is optimal for the passed in row. 
        ///  
        /// The Row to pass to the Delegate
        private void EnsureFit(RowInfo pivotRow) 
        {
            //Get the scale factor necessary to fit this row into view.
            //If the result is not 1.0 (within a certain margin of error)
            //then we need to re-layout, alas. 
            double neededScaleFactor = CalculateScaleFactor(pivotRow);
            double newScale = neededScaleFactor * _rowCache.Scale; 
 
            //If the neededScaleFactor would require DocumentGrid scale the pages
            //below the minimum allowed zoom, or above the maximum, then we won't 
            //do anything here.
            if (newScale < CurrentMinimumScale ||
                newScale > DocumentViewerConstants.MaximumScale)
            { 
                return;
            } 
 
            if (!DoubleUtil.AreClose(1.0, neededScaleFactor))
            { 
                //Rescale the row.
                ApplyViewParameters( pivotRow );

                //Make the row visible again -- the offsets may have 
                //changed due to the above rescaling.
                SetVerticalOffsetInternal(pivotRow.VerticalOffset); 
            } 

        } 

        /// 
        /// Given a pivot row and a previously set ViewMode, the scale is adjusted so as
        /// to cause the pivot row to be fit based on the specified ViewMode. 
        /// 
        ///  
        private void ApplyViewParameters(RowInfo pivotRow) 
        {
            //Update our MaxPagesAcross property to the number of rows on the pivot row 
            //if page sizes vary.  (If page sizes are uniform, this value will not change as a result of
            //a layout change)
            if (_pageCache.DynamicPageSizes)
            { 
                _maxPagesAcross = pivotRow.PageCount;
            } 
 
            //Get the scale factor necessary to fit the given row into the Viewport.
            double scaleFactor = CalculateScaleFactor(pivotRow); 

            //Calculate the new scale. We multiply our scale factor by the old scale factor to cancel out any
            //previously applied scale.
            double newScale = scaleFactor * _rowCache.Scale; 

            //Clip the value into the acceptable range 
            newScale = Math.Max(newScale, CurrentMinimumScale); 
            newScale = Math.Min(newScale, DocumentViewerConstants.MaximumScale);
 
            //Update the Row Layout's scale.
            UpdateLayoutScale(newScale);
        }
 
        private double CalculateScaleFactor(RowInfo pivotRow)
        { 
            //Determine the dimensions of this row minus any spacing between the pages. 
            //We use this as the baseline for our scale factor as page spacing does not scale.
            double rowWidth; 

            //If the page sizes vary, we use the width of the pivot row,
            //otherwise we use the overall width of the document (ExtentWidth).
            //(For uniform page sizes, we always use the width of the document, even 
            //for the last row which may not have the same width as the rest of the document).
            if (_pageCache.DynamicPageSizes) 
            { 
                rowWidth = pivotRow.RowSize.Width - pivotRow.PageCount * HorizontalPageSpacing;
            } 
            else
            {
                rowWidth = ExtentWidth - MaxPagesAcross * HorizontalPageSpacing;
            } 

            double rowHeight = pivotRow.RowSize.Height - VerticalPageSpacing; 
 
            //If we have row dimensions of zero or less, there's no reason to scale anything.
            //So just return 1.0 to indicate no change. 
            if (rowWidth <= 0.0 || rowHeight <= 0.0)
            {
                return 1.0;
            } 

            //The dimensions of our Viewport minus any spacing.  We use this as the baseline for our 
            //scale factor as page spacing does not scale. 
            double compensatedViewportWidth;
 
            if (_pageCache.DynamicPageSizes)
            {
                compensatedViewportWidth = ViewportWidth - pivotRow.PageCount * HorizontalPageSpacing;
            } 
            else
            { 
                compensatedViewportWidth = ViewportWidth - MaxPagesAcross * HorizontalPageSpacing; 
            }
 
            double compensatedViewportHeight = ViewportHeight - VerticalPageSpacing;

            //If we have no space to display pages, there's nothing to scale.
            //So just return 1.0 to indicate no change. 
            if (compensatedViewportWidth <= 0.0 ||
                compensatedViewportHeight <= 0.0) 
            { 
                return 1.0;
            } 

            double scaleFactor = 1.0;

            //Based on the previously determined ViewMode (set in SetColumns(), FitToWidth(), etc.. 
            //scale the pages appropriately.
            switch (_documentLayout.ViewMode) 
            { 
                case ViewMode.SetColumns:
                    //We leave the scale factor as is -- this is not a "page-fit" mode. 
                    break;

                case ViewMode.FitColumns:
                    //Update the scale factor so that the pivot row is completely visible. 
                    scaleFactor = Math.Min(compensatedViewportWidth / rowWidth, compensatedViewportHeight / rowHeight);
                    break; 
 
                case ViewMode.PageWidth:
                    //Update the scale factor so that the pivot row is as wide as the viewport. 
                    scaleFactor = compensatedViewportWidth / rowWidth;
                    break;

                case ViewMode.PageHeight: 
                    //Update the scale factor so that the pivot row is as tall as the viewport.
                    scaleFactor = compensatedViewportHeight / rowHeight; 
                    break; 

                case ViewMode.Thumbnails: 
                    //Update the scale factor so that the _entire layout_ is completely visible.  As in previous
                    //cases we must compensate for the fact that the spacing between pages does not scale.
                    //However, unlike in previous cases, we must exclude the space between all rows rather
                    //merely one space, so we must recalculate the compensated values.  Furthermore we must 
                    //also compensate for the ExtentHeight as well since it includes the spaces.
                    double thumbnailCompensatedExtentHeight = ExtentHeight - VerticalPageSpacing * _rowCache.RowCount; 
                    double thumbnailCompensatedViewportHeight = ViewportHeight - VerticalPageSpacing * _rowCache.RowCount; 
                    //If we have no space to display pages, there's nothing to scale.
                    //So just return 1.0 to indicate no change. 
                    if (thumbnailCompensatedViewportHeight <= 0.0)
                    {
                        scaleFactor = 1.0;
                    } 
                    else
                    { 
                        scaleFactor = Math.Min(compensatedViewportWidth / rowWidth, 
                            thumbnailCompensatedViewportHeight / thumbnailCompensatedExtentHeight);
                    } 
                    break;

                case ViewMode.Zoom:
                    //We will not change the scale here, as this is not a "page-fit" mode. 
                    break;
 
                default: 
                    throw new InvalidOperationException(SR.Get(SRID.DocumentGridInvalidViewMode));
            } 

            return scaleFactor;
        }
 
        /// 
        /// Creates the caches used by DocumentGrid, and sets default property values. 
        ///  
        private void Initialize()
        { 
            //Create our caches
            _pageCache = new PageCache();
            _childrenCollection = new VisualCollection(this);
 
            _rowCache = new RowCache();
            _rowCache.PageCache = _pageCache; 
            _rowCache.RowCacheChanged += new RowCacheChangedEventHandler(OnRowCacheChanged); 
            _rowCache.RowLayoutCompleted += new RowLayoutCompletedEventHandler(OnRowLayoutCompleted);
        } 

        /// 
        /// Updates Scrolling-related properties and calls
        /// InvalidateScrollInfo and InvalidateDocumentScrollInfo on the 
        /// ScrollOwner and DocumentScrollOwner, respectively.
        ///  
        private void InvalidateDocumentScrollInfo() 
        {
            if (ScrollOwner != null) 
            {
                ScrollOwner.InvalidateScrollInfo();
            }
 
            if (DocumentViewerOwner != null)
            { 
                DocumentViewerOwner.InvalidateDocumentScrollInfo(); 
            }
        } 

        /// 
        /// Calls InvalidatePageViews and ApplyTemplate on our DocumentViewer owner
        /// so that the base implementation can keep its collection up to date. 
        /// 
        private void InvalidatePageViews() 
        { 
            Invariant.Assert(DocumentViewerOwner != null, "DocumentViewerOwner cannot be null.");
 
            if (DocumentViewerOwner != null)
            {
                DocumentViewerOwner.InvalidatePageViewsInternal();
                DocumentViewerOwner.ApplyTemplate(); 
            }
 
            //Perf Tracing - InvalidatePageViews 
            EventTrace.NormalTraceEvent(EventTraceGuidId.DRXINVALIDATEVIEWGUID, MS.Utility.EventType.Info);
        } 

        /// 
        /// Returns an ITextPointer to the current visible selection, if there is one.
        ///  
        /// An ITextPointer to the current selection, or null if none exists.
        private ITextPointer GetVisibleSelection() 
        { 
            ITextPointer selection = null;
 
            if (HasSelection())
            {
                ITextPointer tp = TextEditor.Selection.Start;
 
                //If the TextView contains the selection
                //then the selection is on a visible page. 
                if (TextViewContains(tp)) 
                {
                    selection = tp; 
                }
            }

            return selection; 
        }
 
        ///  
        /// Indicates whether a selection (visible or not) has been made.
        ///  
        /// true if a selection has been made, false otherwise.
        private bool HasSelection()
        {
            return (TextEditor != null && TextEditor.Selection != null); 
        }
 
        ///  
        /// Gets the page number that the specified ITextPointer to a visible selection
        /// is on. 
        /// 
        /// The TextPointer to find the page number for.
        /// 
        private int GetPageNumberForVisibleSelection(ITextPointer selection) 
        {
            Invariant.Assert(TextViewContains(selection)); 
 
            //Walk through the current DocumentPageViews and see which one contains the selection.
            foreach (DocumentPageView pageView in _pageViews) 
            {
                //Get the TextView for this page.
                DocumentPageTextView textView =
                    ((IServiceProvider)pageView).GetService(typeof(ITextView)) as DocumentPageTextView; 

                //If this TextView contains the selection, return the page's number. 
                if (textView != null && 
                    textView.IsValid &&
                    textView.Contains(selection)) 
                {
                    return pageView.PageNumber;
                }
            } 

            Invariant.Assert(false, "Selection was in TextView, but not found in any visible page!"); 
            return 0; 
        }
 
        /// 
        /// Finds the "Active Focus" point:
        /// Either the page that has a Selection/Insertion Point on it,
        /// or lacking that, the center of the viewport. 
        /// 
        ///  
        private Point GetActiveFocusPoint() 
        {
 
            ITextPointer tp = GetVisibleSelection();

            if (tp != null && tp.HasValidLayout)
            { 
                Rect selectionRect = TextView.GetRectangleFromTextPosition(tp);
 
                //If the selection rectangle is not empty, then we have a selection or an IP 
                if (selectionRect != Rect.Empty)
                { 
                    //Return the upper-left corner of the selection.
                    return new Point(selectionRect.Left, selectionRect.Top);
                }
            } 

            //No selection, so we default to the upper-left of the viewport. 
            return new Point(0.0, 0.0); 
        }
 
        /// 
        /// Returns the page that has "Active Focus," or
        /// the first visible page if there is none.
        ///  
        /// 
        private int GetActiveFocusPage() 
        { 
            DocumentPageView dp = GetDocumentPageViewFromPoint(GetActiveFocusPoint());
 
            if (dp != null)
            {
                return dp.PageNumber;
            } 

            //No selection, we default to the first visible page. 
            return _firstVisiblePageNumber; 
        }
 
        /// 
        /// Given a point onscreen, returns a DocumentPageView that occupies that point,
        /// if any.
        ///  
        /// The point at which to search for a DocumentPageView.
        ///  
        private DocumentPageView GetDocumentPageViewFromPoint(Point point) 
        {
            //Hit test to find the DocumentPageView 
            HitTestResult result = VisualTreeHelper.HitTest(this, point);
            DependencyObject currentVisual = (result != null) ? result.VisualHit : null;

            DocumentPageView page = null; 

            // Traverse the visual parent chain until we encounter a DocumentPageView. 
            while (currentVisual != null) 
            {
                page = currentVisual as DocumentPageView; 
                if (page != null)
                {
                    //We found the DocumentPageView.
                    return page; 
                }
                currentVisual = VisualTreeHelper.GetParent(currentVisual); 
            } 

            //Didn't find one at this point. 
            return null;
        }

        ///  
        /// Helper function to safely verify that the TextView contains a given TextPointer.
        ///  
        /// The TextPointer to check 
        /// 
        private bool TextViewContains( ITextPointer tp ) 
        {
            return (TextView != null &&
                TextView.IsValid &&
                TextView.Contains(tp)); 
        }
 
        ///  
        /// Helper function that calculates the Horizontal offset of the given page.
        ///  
        /// The row which the desired page lives on
        /// The page to find the offset for
        /// The Horizontal offset of the page in the document.
        private double GetHorizontalOffsetForPage( RowInfo row, int pageNumber ) 
        {
            if (row == null) 
            { 
                throw new ArgumentNullException("row");
            } 

            if (pageNumber < row.FirstPage ||
                pageNumber > row.FirstPage + row.PageCount)
            { 
                throw new ArgumentOutOfRangeException("pageNumber");
            } 
 
            //Rows are centered if the content has varying page sizes,
            //Left-aligned otherwise. 
            double horizontalOffset = _pageCache.DynamicPageSizes ?
                Math.Max(0.0, (ExtentWidth - row.RowSize.Width) / 2.0) : 0.0;

            //Add the widths of the pages (and spacing) prior to this one on the row 
            for (int i = row.FirstPage; i < pageNumber; i++)
            { 
                Size pageSize = _pageCache.GetPageSize(i); 
                horizontalOffset += pageSize.Width * Scale + HorizontalPageSpacing;
            } 

            return horizontalOffset;

        } 

        ///  
        /// Helper method that determines whether a given RowCacheChange will have 
        /// an impact on currently-visible rows.
        ///  
        /// 
        /// 
        private bool RowCacheChangeIsVisible(RowCacheChange change)
        { 
            int firstVisibleRow = _firstVisibleRow;
            int lastVisibleRow = _firstVisibleRow + _visibleRowCount; 
 
            int firstChangedRow = change.Start;
            int lastChangedRow = change.Start + change.Count; 

            //If the first changed row (and hence following changes) are visible OR
            //The last changed row (and hence prior changes) are visible OR
            //if the changes are a super-set of the visible range, then the change is visible. 
            if ((firstChangedRow >= firstVisibleRow && firstChangedRow <= lastVisibleRow) ||
                (lastChangedRow >= firstVisibleRow && lastChangedRow <= lastVisibleRow) || 
                (firstChangedRow < firstVisibleRow && lastChangedRow > lastVisibleRow)) 
            {
                return true; 
            }

            return false;
        } 

        ///  
        /// Determines whether the given page's contents have been loaded into the visual tree. 
        /// 
        /// The number of the page to check 
        /// 
        private bool IsPageLoaded(int pageNumber)
        {
            DocumentGridPage page = GetDocumentGridPageForPageNumber(pageNumber); 

            if (page != null) 
            { 
                return page.IsPageLoaded;
            } 
            else
            {
                return false;
            } 
        }
 
        ///  
        /// Determines whether every page currently visible has been loaded into the visual tree.
        ///  
        /// 
        private bool IsViewLoaded()
        {
            bool viewIsLoaded = true; 

            for (int i = _firstPageVisualIndex; i < _childrenCollection.Count; i++) 
            { 
                DocumentGridPage dp = _childrenCollection[i] as DocumentGridPage;
 
                // Check that this page has been loaded; break if not.
                if (dp != null && !dp.IsPageLoaded)
                {
                    viewIsLoaded = false; 
                    break;
                } 
            } 

            return viewIsLoaded; 
        }

        /// 
        /// Retrieves a DocumentGridPage from our Visual Tree that has the given page number (if one exists). 
        /// 
        /// The number of the page to get 
        ///  
        private DocumentGridPage GetDocumentGridPageForPageNumber(int pageNumber)
        { 
            for (int i = _firstPageVisualIndex; i < _childrenCollection.Count; i++)
            {
                DocumentGridPage dp = _childrenCollection[i] as DocumentGridPage;
 
                if (dp != null && dp.PageNumber == pageNumber)
                { 
                    return dp; 
                }
            } 

            return null;
        }
 
        #region Event Handlers
 
        ///  
        /// Handles the RequestBringIntoView routed event in the case where the element to be
        /// brought into view is DocumentGrid itself, as is the case when the TextEditor's IP moves. 
        /// In this case we use the incoming target rectangle
        /// and ensure that rect is made visible inside of DocumentGrid.
        /// 
        /// The sender of this routed event, expected to be a DocumentGrid. 
        /// The RequestBringIntoView event args associated with this event.
        private static void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs args) 
        { 
            //We only handle this here if the sender and the target of this event are both the same
            //DocumentGrid. 
            DocumentGrid senderGrid = sender as DocumentGrid;
            DocumentGrid targetGrid = args.TargetObject as DocumentGrid;
            if (senderGrid != null && targetGrid != null && senderGrid == targetGrid)
            { 
                //Bring the IP into view and mark the event as handled.
                args.Handled = true; 
                targetGrid.MakeIPVisible(args.TargetRect); 
            }
            else 
            {
                args.Handled = false;
            }
        } 

        ///  
        /// This event is fired when our parent ScrollViewer's layout has changed(before render). 
        /// If we need to ensure that a given row has been properly fit -- if ScrollBars have been hidden/
        /// made visible due to this change then we may need to resize.  We call EnsureFit from here 
        /// to make sure that's done.
        /// 
        /// 
        ///  
        private void OnScrollChanged(object sender, EventArgs args)
        { 
            //Remove our handler. 
            if (ScrollOwner != null)
            { 
                _scrollChangedEventAttached = false;
                ScrollOwner.ScrollChanged -= new ScrollChangedEventHandler(OnScrollChanged);
            }
 
            //Ensure that our fit is good for the currently displayed row if we have any.
            if (_rowCache.HasValidLayout) 
            { 
                EnsureFit(_rowCache.GetRowForPageNumber(FirstVisiblePageNumber));
            } 
        }

        /// 
        /// This event is fired after a Zoom change when Layout has completed (but before render). 
        /// We make sure the current visible selection is centered onscreen.
        ///  
        ///  
        /// 
        private void OnZoomLayoutUpdated(object sender, EventArgs args) 
        {
            //Remove the event handler so we don't get called again.
            LayoutUpdated -= new EventHandler(OnZoomLayoutUpdated);
 
            ITextPointer selection = GetVisibleSelection();
 
            if (selection != null) 
            {
                //Now we make the selection visible. 
                MakeRectVisible(TextView.GetRectangleFromTextPosition(
                    selection), true /* alwaysCenter */);
            }
        } 

        ///  
        /// When the RowCache is changed for any reason (due to a new layout, or if pages change, etc...) 
        /// then we need to invalidate our Measure (so we can pick up any changes to visible pages)
        /// and invalidate our IDocumentScrollInfo parents so they know that something's changed. 
        /// 
        /// 
        /// 
        private void OnRowCacheChanged(object source, RowCacheChangedEventArgs args) 
        {
 
            //If: 
            //1) We have a saved pivot row from a previous RowCacheCompleted event,
            //and 
            //2) We've been told to do a "Page-Fit" operation (that is, a non-zoom viewing preference)
            //and
            //3) The pivot row is now "clean" (that is, we know the actual dimensions of all the pages
            //   on the row and we aren't just guessing) 
            //Then we can now officially calculate the scale needed in order to fit the given row in the manner
            //chosen. 
            if (_savedPivotRow != null && 
                RowIsClean(_savedPivotRow))
            { 
                if (_documentLayout.ViewMode != ViewMode.Zoom &&
                    _documentLayout.ViewMode != ViewMode.SetColumns
                    )
                { 
                    if (_savedPivotRow.FirstPage < _rowCache.RowCount)
                    { 
                        RowInfo newRow = _rowCache.GetRowForPageNumber(_savedPivotRow.FirstPage); 

                        //If the new row's dimensions differ, then we need to rescale, otherwise we do nothing. 
                        if (newRow.RowSize.Width != _savedPivotRow.RowSize.Width ||
                            newRow.RowSize.Height != _savedPivotRow.RowSize.Height)
                        {
                            //Rescale. 
                            ApplyViewParameters(newRow);
                        } 
 
                        //Null out the saved Pivot Row -- we've scaled this row properly now
                        //so we don't need to be concerned with it any longer. 
                        _savedPivotRow = null;
                    }
                }
                else 
                {
                    // The view is already correct; null out the saved Pivot Row. 
                    _savedPivotRow = null; 
                }
            } 

            //If we're viewing a document with varying page size, we've scrolled since the last layout change
            //and the Width of the document has increased
            //then this means that new, wider pages have just been scrolled into view. 
            //If we do nothing here, then the content prior to these new pages will appear to "jump"
            //to the right (because we center the pages within the width of the document.) 
            //This jump is jarring and not a good user experience. 
            //To prevent this, we adjust the HorizontalOffset such that the content that was previously visible
            //appears at the same position when it is rendered. 
            if (_pageCache.DynamicPageSizes &&
                _lastRowChangeVerticalOffset != VerticalOffset &&
                _lastRowChangeExtentWidth < ExtentWidth)
            { 
                if (_lastRowChangeExtentWidth != 0.0)
                { 
                    //Tweak the HorizontalOffset so that the content does not appear to move. 
                    SetHorizontalOffsetInternal(HorizontalOffset + (ExtentWidth - _lastRowChangeExtentWidth) / 2.0);
                } 

                _lastRowChangeExtentWidth = ExtentWidth;
            }
 
            _lastRowChangeVerticalOffset = VerticalOffset;
 
            //The row cache has been changed. 
            //If we're displaying rows that were affected,
            //we need to invalidate our measure so they'll be 
            //redrawn.
            for (int i = 0; i < args.Changes.Count; i++)
            {
                RowCacheChange change = args.Changes[i]; 

                if( RowCacheChangeIsVisible( change )) 
                { 
                    InvalidateMeasure();
                    InvalidateChildMeasure(); 
                }
            }

            InvalidateDocumentScrollInfo(); 
        }
 
        ///  
        /// When a new RowLayout has finished being computed we scale the layout such that it
        /// fits within our window. 
        /// 
        /// 
        /// 
        private void OnRowLayoutCompleted(object source, RowLayoutCompletedEventArgs args) 
        {
            if (args == null) 
            { 
                return;
            } 
            if (args.PivotRowIndex >= _rowCache.RowCount)
            {
                throw new ArgumentOutOfRangeException("args");
            } 

            //Get the pivot row 
            RowInfo pivotRow = _rowCache.GetRow(args.PivotRowIndex); 

            //If this row is not clean, and we're not applying a 
            //Zoom to the content then we need to rescale the layout when the
            //pages on this row are retrieved.
            if (!RowIsClean(pivotRow) && _documentLayout.ViewMode != ViewMode.Zoom)
            { 
                //Save off this row in case we need to rescale due to
                //dirty cache entries becoming clean (i.e. page sizes changing 
                //due to the cached size being an inaccurate guess.) 
                //OnRowCacheChanged will check this row to ensure that it gets scaled
                //properly when all the pages on the row become available. 
                _savedPivotRow = pivotRow;
            }
            else
            { 
                _savedPivotRow = null;
            } 
 
            //Now rescale.  We do this after checking the cleanliness of the row
            //so that _savedPivotRow is properly set before we apply our view parameters. 
            //Otherwise the code that relies on it in OnRowCacheChanged (which may be called
            //as a result of calling ApplyViewParameters) may use the wrong row.
            ApplyViewParameters(pivotRow);
 
            //Now that we've recalculated the row layout, it's time to make the previously-visible
            //content visible again. 
            //We do not do this the first time the content is assigned, for two reasons, 
            //(similar to the ones described in DocumentViewer.OnDocumentChanged()):
            //  1) If this is the first assignment, then we're already there by default. 
            //  2) The user may have specified vertical or horizontal offsets in markup or
            //     otherwise () and we need to honor
            //     those settings.
            if (!_firstRowLayout && !_pageJumpAfterLayout) 
            {
                MakePageVisible(pivotRow.FirstPage); 
            } 
            else if (_pageJumpAfterLayout)
            { 
                MakePageVisible(_pageJumpAfterLayoutPageNumber);
                _pageJumpAfterLayout = false;
            }
 
            _firstRowLayout = false;
 
            //If our view was of a "Fit" type, we need to ensure that the fit is 
            //correct -- if the status of Vertical/Horizontal Scrollbars has changed
            //as a result of our view selection then the Viewport size may have changed. 
            //If so, our current fit is probably wrong.  We'll attach a ScrollChanged handler
            //to our ScrollOwner and when the event is invoked (after layout, but before rendering)
            //we'll check.
            //This is "Step 2" of the two-pass layout necessary to do fit properly inside of a 
            //ScrollViewer.
            if (!_scrollChangedEventAttached && 
                ScrollOwner != null && 
                _documentLayout.ViewMode != ViewMode.Zoom &&
                _documentLayout.ViewMode != ViewMode.SetColumns) 
            {
                _scrollChangedEventAttached = true;
                ScrollOwner.ScrollChanged += new ScrollChangedEventHandler(OnScrollChanged);
            } 
        }
 
        #endregion Event Handlers 

        #endregion Private Methods 

        //------------------------------------------------------
        //
        //  Private Properties 
        //
        //----------------------------------------------------- 
        #region Private Properties 

        ///  
        /// Indicates that our Viewport is or is not exactly (0,0).
        /// 
        /// 
        private bool IsViewportNonzero 
        {
            get 
            { 
                return (ViewportWidth != 0.0 && ViewportHeight != 0.0);
            } 
        }

        /// 
        /// Provides access to DocumentViewer's TextEditor. 
        /// 
        private TextEditor TextEditor 
        { 
            get
            { 
                if (DocumentViewerOwner != null)
                {
                    return DocumentViewerOwner.TextEditor;
                } 
                else
                { 
                    return null; 
                }
            } 
        }

        /// 
        /// Represents the number of pixels to scroll by when using the 
        /// Mouse Wheel; based on System.Parameters.WheelScrollLines.
        ///  
        private double MouseWheelVerticalScrollAmount 
        {
            get 
            {
                //SystemParameters.WheelScrollLines indicates the number of lines to
                //scroll when the wheel is moved one "click," we multiply this by
                //our scroll amount to get the number of pixels to move. 
                return _verticalLineScrollAmount * SystemParameters.WheelScrollLines;
            } 
        } 

        ///  
        /// Represents the number of pixels to scroll by when using the
        /// Mouse Wheel; based on System.Parameters.WheelScrollLines.
        /// 
        private double MouseWheelHorizontalScrollAmount 
        {
            get 
            { 
                //SystemParameters.WheelScrollLines indicates the number of lines to
                //scroll when the wheel is moved one "click," we multiply this by 
                //our scroll amount to get the number of pixels to move.
                return _horizontalLineScrollAmount * SystemParameters.WheelScrollLines;
            }
        } 

        ///  
        /// Returns the minimum allowed scale based on the current view mode. 
        /// Thumbnails mode has a higher minimum than other views.
        ///  
        private double CurrentMinimumScale
        {
            get
            { 
                return _documentLayout.ViewMode == ViewMode.Thumbnails ?
                  DocumentViewerConstants.MinimumThumbnailsScale : 
                  DocumentViewerConstants.MinimumScale; 
            }
        } 

        #endregion Private Properties

        //------------------------------------------------------ 
        //
        //  Private Fields 
        // 
        //-----------------------------------------------------
 
        #region Private Fields

        // Our Caches
        private PageCache       _pageCache; 
        private RowCache        _rowCache;
 
        // Our collection of currently-displayed pages. 
        private ReadOnlyCollection _pageViews;
 
        // Data for Properties
        private bool            _canHorizontallyScroll;
        private bool            _canVerticallyScroll;
        private double          _verticalOffset; 
        private double          _horizontalOffset;
        private double          _viewportHeight; 
        private double          _viewportWidth; 
        private int             _firstVisibleRow;
        private int             _visibleRowCount; 
        private int             _firstVisiblePageNumber;
        private int             _lastVisiblePageNumber;
        private ScrollViewer    _scrollOwner;
        private DocumentViewer  _documentViewerOwner; 
        private bool            _showPageBorders = true;
        private bool            _lockViewModes; 
        private int             _maxPagesAcross = 1; 

        // The previous constraint passed to ArrangeCore. 
        private Size            _previousConstraint;

        // The viewing mode (columns, fit, etc...) last used to request a layout.
        private DocumentLayout  _documentLayout = 
            new DocumentLayout(1, ViewMode.SetColumns);
        private int             _documentLayoutsPending; 
 
        // The pivot rows last used to form the basis of a layout.
        private RowInfo         _savedPivotRow; 

        // The last ExtentWidth and VerticalOffsets encountered in
        // OnRowCacheChanged, used to determine whether to tweak the HorizontalOffset.
        private double          _lastRowChangeExtentWidth; 
        private double          _lastRowChangeVerticalOffset;
 
        // Editing 
        private ITextContainer  _textContainer;
 
        // RubberBand selector used for rubberband selection.
        private RubberbandSelector _rubberBandSelector;

        // Flags 
        private bool            _isLayoutRequested;  //Whether we have requested a layout from the RowCache.
        private bool            _pageJumpAfterLayout;   //Whether we need to bring a page into view after layout 
        private int             _pageJumpAfterLayoutPageNumber; //The page to jump to after layout 
        private bool            _firstRowLayout = true;
        private bool            _scrollChangedEventAttached; //Whether or not we've attached a ScrollChanged event to our ScrollViewer. 

        // We create a Border with a transparent background so that it can
        // participate in Hit-Testing (which allows click events like those
        // for our Context Menu to work).  This border is displayed behind 
        // the displayed pages so that "dead space" surrounding the pages can
        // be clicked on. 
        private Border          _documentGridBackground; 
        private const int       _backgroundVisualIndex = 0;
        private const int       _firstPageVisualIndex = 1; 

        //Constants for MeasureCore constraints
        //We use this size if we're placed inside a "Size-To-Parent" container like
        //ScrollViewer or StackPanel and are given Infinite constraints. 
        private readonly Size _defaultConstraint = new Size(250.0, 250.0);
 
        //Store all our visual children (pages) here 
        private VisualCollection _childrenCollection;
 
        //Information for MakeVisible operations involving pages that are not
        //yet visible.
        private int _makeVisiblePageNeeded = -1;
        private DispatcherOperation _makeVisibleDispatcher; 

        //DispatcherOperations used for executing time-consuming property changes in the background. 
        private DispatcherOperation _setScaleOperation; 

        //Delegate used for BringPageIntoView. 
        private delegate void BringPageIntoViewCallback(MakeVisibleData data, int pageNumber);

        /// 
        /// Represents a state in the Visual tree merging state machine 
        /// used in RecalcVisiblePages.
        ///  
        private enum VisualTreeModificationState 
        {
            ///  
            /// Inserting pages before existing
            /// 
            BeforeExisting = 0,
 
            /// 
            /// Scanning through existing pages 
            ///  
            DuringExisting,
 
            /// 
            /// Adding pages after existing
            /// 
            AfterExisting 
        }
 
        ///  
        /// Represents a layout mode specified by SetColumns,
        /// FitToPage, FitToWidth, etc... 
        /// 
        private enum ViewMode
        {
            ///  
            /// A request to lay out a specified number of columns was made.
            ///  
            SetColumns = 0, 

            ///  
            /// A request to make the specified number of columns visible was made.
            /// 
            FitColumns,
 
            /// 
            /// A request for a fit-to-page-width view was made. 
            ///  
            PageWidth,
 
            /// 
            /// A request for a fit-to-page-height view was made.
            /// 
            PageHeight, 

            ///  
            /// A request for a thumbnail view was made. 
            /// 
            Thumbnails, 

            /// 
            /// A request for a non "page-fit" view was made.
            ///  
            Zoom,
 
            ///  
            /// A request for the HorizontalOffset to be updated.
            ///  
            SetHorizontalOffset,

            /// 
            /// A request for the VerticalOffset to be updated. 
            /// 
            SetVerticalOffset 
        } 

        ///  
        /// Represents a particular document layout --
        /// includes the number of Columns to view and the
        /// mode to view them in.
        ///  
        private class DocumentLayout
        { 
            public DocumentLayout(int columns, ViewMode viewMode) 
                : this(columns, 0.0 /* default */, viewMode) { }
 
            public DocumentLayout(double offset, ViewMode viewMode)
                : this(1 /* default */, offset, viewMode) { }

            public DocumentLayout(int columns, double offset, ViewMode viewMode) 
            {
                _columns = columns; 
                _offset = offset; 
                _viewMode = viewMode;
            } 

            /// 
            /// The ViewMode to apply to the layout.
            ///  
            public ViewMode ViewMode
            { 
                set { _viewMode = value; } 
                get { return _viewMode; }
            } 

            /// 
            /// The number of columns for the layout.
            ///  
            public int Columns
            { 
                set { _columns = value; } 
                get { return _columns; }
            } 

            /// 
            /// The offset (horizontal of vertical) for the layout.
            ///  
            public double Offset
            { 
                // Set not currently used. 
                // set { _offset = value; }
                get { return _offset; } 
            }

            private ViewMode _viewMode;
            private int      _columns; 
            private double   _offset;
        } 
 
        /// 
        /// An MakeVisibleData object contains data and operation information 
        /// related to asynchronous MakeVisible operations.
        /// 
        private struct MakeVisibleData
        { 
            /// 
            /// Constructs a new MakeVisibleData object 
            ///  
            /// A visual to be made visible.
            /// A ContentPosition to be made visible. 
            /// Any bounding rect to be made visible.
            public MakeVisibleData(Visual visual, ContentPosition contentPosition, Rect rect)
            {
                _visual = visual; 
                _contentPosition = contentPosition;
                _rect = rect; 
            } 

            ///  
            /// The Visual to be made visible
            /// 
            public Visual Visual
            { 
                get { return _visual; }
            } 
 
            /// 
            /// The ContentPosition to be made Visible 
            /// 
            public ContentPosition ContentPosition
            {
                get { return _contentPosition; } 
            }
 
            ///  
            /// The bounding rectangle to be made visible
            ///  
            public Rect Rect
            {
                get { return _rect; }
            } 

            private Visual _visual; 
            private ContentPosition _contentPosition; 
            private Rect _rect;
 
        }

        //Constants for line scrolling amounts
        private const double _verticalLineScrollAmount = 16.0; 
        private const double _horizontalLineScrollAmount = 16.0;
 
        #endregion Private Fields 
    }
} 


// 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