private PageIndexes CalculateVisibleIndexesForDesiredLastVisibleIndex( int desiredLastVisibleIndex )
    {
      PageIndexes layoutedIndexes = new PageIndexes();

      int stickyFooterCountForLastVisibleIndex = this.GetStickyFooterCountForIndex( desiredLastVisibleIndex );
      layoutedIndexes.EndIndex = desiredLastVisibleIndex + stickyFooterCountForLastVisibleIndex;

      int minVisibleIndex = layoutedIndexes.EndIndex
        - this.CalculateFlooredPageItemCount( this.AnimatedScrollInfo.ViewportHeight );

      layoutedIndexes.StartIndex = minVisibleIndex;

      // Process indexes from bottom to top up to an index for which
      // the number of required sticky headers will force a visible index
      // larger than the maximal acceptable last visible index
      for( int i = desiredLastVisibleIndex - 1; i >= minVisibleIndex; i-- )
      {
        int stickyHeaderCountForIndex = this.GetStickyHeaderCountForIndex( i );

        // If the index of the container - its sticky header count
        // is less than the minimal acceptable visible index, we ensure
        // the next container index as the minimal one to set as current
        if( minVisibleIndex > ( i - stickyHeaderCountForIndex ) )
        {
          layoutedIndexes.StartIndex = i + 1;
          break;
        }
      }

      return layoutedIndexes;
    }
    private void CalculateLayoutedIndexes( double verticalOffset, double viewportHeight, out PageIndexes layoutedIndexes )
    {
      layoutedIndexes = new PageIndexes();

      int itemCount = this.CustomItemContainerGenerator.ItemCount;
      if( itemCount > 0 )
      {
        layoutedIndexes.StartIndex = Math.Min(
          this.GetContainerIndexFromOffset( verticalOffset, false ),
          itemCount - 1 );

        layoutedIndexes.EndIndex = Math.Min(
          this.GetContainerIndexFromOffset( verticalOffset + viewportHeight - m_containerHeight, true ),
          itemCount - 1 );

        layoutedIndexes.EndIndex = Math.Max(
          layoutedIndexes.StartIndex,
          layoutedIndexes.EndIndex );
      }
    }
    private PageIndexes CalculateVisibleIndexesForDesiredFirstVisibleIndex( int desiredFirstVisibleIndex )
    {
      PageIndexes layoutedIndexes = new PageIndexes();

      // Get the sticky header count for the desiredFirstVisibleIndex
      // and set the StartIndex
      int stickyHeaderCountForFirstVisibleIndex = this.GetStickyHeaderCountForIndex( desiredFirstVisibleIndex );
      layoutedIndexes.StartIndex = desiredFirstVisibleIndex - stickyHeaderCountForFirstVisibleIndex;

      // Get the index of the maximal visible index according
      // to the number of container the viewport can display
      // and the number of sticky header the desiredFirstVisibleIndex
      // must display
      int maxLastVisibleIndex = layoutedIndexes.StartIndex
        + this.CalculateFlooredPageItemCount( this.AnimatedScrollInfo.ViewportHeight );

      layoutedIndexes.EndIndex = maxLastVisibleIndex;

      // Process indexes from top to bottom up to an index for which
      // the number of required sticky footers will force a visible index
      // larger than the maximal acceptable last visible index
      for( int i = desiredFirstVisibleIndex + 1; i <= maxLastVisibleIndex; i++ )
      {
        int stickyFooterCountForIndex = this.GetStickyFooterCountForIndex( i );

        // The index of the container + its sticky footer count
        // is greater than the last acceptable visible index, we ensure
        // the previous container as the maximal one to set as current
        if( maxLastVisibleIndex < ( i + stickyFooterCountForIndex ) )
        {
          layoutedIndexes.EndIndex = i - 1;
          break;
        }
      }

      return layoutedIndexes;
    }
    private void GeneratePageAndUpdateIScrollInfoValues(
      Size availableSize,
      bool measureInvalidated,
      ref double viewportHeight )
    {
      PageIndexes generatedPage;

      // We must ensure the VerticalOffset is valid according
      // to the actual viewport height in case the VerticalOffset
      // is greater than the new viewportHeight.
      IScrollInfo scrollInfo = this as IScrollInfo;

      double maxOffset = Math.Max( 0d, scrollInfo.ExtentHeight - 1 );
      double offset = Math.Max( Math.Min( m_verticalOffset, maxOffset ), 0d );

      if( offset != m_verticalOffset )
        this.SetVerticalOffsetCore( offset );

      int verticalOffset = ( int )m_verticalOffset;

      if( m_indexToBringIntoView != -1 )
      {
        int intOffset = Math.Min( m_indexToBringIntoView, ( int )maxOffset );

        if( intOffset < m_lastLayoutedPage.StartIndex )
        {
          generatedPage = new PageIndexes( intOffset, -1 );
        }
        else
        {
          generatedPage = new PageIndexes( -1, intOffset );
        }
      }
      else
      {
        generatedPage = new PageIndexes( verticalOffset, -1 );
      }

      // CALCULATE THE VIEWPORT HEIGHT AND GENERATE CONTAINERS
      this.GeneratePage( availableSize.Height, measureInvalidated, ref generatedPage, out viewportHeight );

      // CALCULATE THE EXTENT WIDTH
      m_extentWidth = Math.Max( this.GetMaxDesiredWidth(), this.GetSynchronizedExtentWidth() );

      // CALCULATE THE VIEWPORT WIDTH
      m_viewportWidth = Double.IsInfinity( availableSize.Width )
        ? m_extentWidth : Math.Min( m_extentWidth, availableSize.Width );

      this.SetVerticalOffsetCore( generatedPage.StartIndex );
      this.InvalidateScrollInfo();
    }
    private void GenerateContainers(
      ICustomItemContainerGenerator generator,
      double pageHeight,
      HashSet<UIElement> layoutedContainersToRecycle,
      bool measureInvalidated,
      ref PageIndexes pageIndexes,
      out double containersHeight )
    {
      int currentIndex = pageIndexes.StartIndex;
      GeneratorDirection direction;

      if( currentIndex == -1 )
      {
        currentIndex = pageIndexes.EndIndex;
        Debug.Assert( currentIndex != -1 );
        direction = GeneratorDirection.Backward;
      }
      else
      {
        direction = GeneratorDirection.Forward;
      }

      int startIndex = currentIndex;
      int endIndex = currentIndex;
      containersHeight = 0d;
      GeneratorPosition position;

      position = generator.GeneratorPositionFromIndex( currentIndex );
      int itemCount = generator.ItemCount;

      using( generator.StartAt( position, direction, true ) )
      {
        while( ( ( direction == GeneratorDirection.Forward )
          ? ( currentIndex < itemCount ) : ( currentIndex >= 0 ) ) && ( pageHeight > 0 ) )
        {
          UIElement container = this.GenerateContainer( generator, currentIndex, measureInvalidated );

          if( container == null )
            break;

          double containerHeight = container.DesiredSize.Height;

          m_layoutedContainers.Add( new LayoutedContainerInfo( currentIndex, container ) );
          layoutedContainersToRecycle.Remove( container );
          m_layoutedContainersToRecycle.Remove( container );

          if( ( direction == GeneratorDirection.Backward ) && ( ( pageHeight - containerHeight ) < 0 ) )
          {
            // We do not want to recycle the container since it will cause a re-invalidation of the measure and 
            // may cause an infinit loop.  This case has been observed with a MaxHeight set on the DataGridControl.
            break;
          }

          pageHeight -= containerHeight;
          containersHeight += containerHeight;
          endIndex = currentIndex;
          currentIndex += ( direction == GeneratorDirection.Forward ) ? 1 : -1;
        }
      }

      if( pageHeight > 0 )
      {
        if( direction == GeneratorDirection.Forward )
        {
          direction = GeneratorDirection.Backward;
        }
        else
        {
          direction = GeneratorDirection.Forward;
        }

        DataGridContext dataGridContext = DataGridControl.GetDataGridContext( this );

        if( ( direction == GeneratorDirection.Forward ) || ( ( dataGridContext == null ) || ( TableView.GetAutoFillLastPage( dataGridContext ) ) ) )
        {
          currentIndex = ( direction == GeneratorDirection.Forward ) ? startIndex + 1 : startIndex - 1;

          if( ( direction == GeneratorDirection.Forward ) ? ( currentIndex < itemCount ) : ( currentIndex >= 0 ) )
          {
            position = generator.GeneratorPositionFromIndex( currentIndex );

            using( generator.StartAt( position, direction, true ) )
            {
              // If we still have more space, try to get more container to fill up the page.
              while( ( ( direction == GeneratorDirection.Forward ) ?
                ( currentIndex < itemCount ) : ( currentIndex >= 0 ) ) && ( pageHeight > 0 ) )
              {
                UIElement container = this.GenerateContainer( generator, currentIndex, measureInvalidated );

                if( container == null )
                  break;

                double containerHeight = container.DesiredSize.Height;
                pageHeight -= containerHeight;

                m_layoutedContainers.Add( new LayoutedContainerInfo( currentIndex, container ) );
                layoutedContainersToRecycle.Remove( container );
                m_layoutedContainersToRecycle.Remove( container );

                if( ( direction == GeneratorDirection.Backward ) && ( pageHeight < 0 ) )
                {
                  // We do not want to recycle the container since it will cause a re-invalidation of the measure and 
                  // may cause an infinit loop.  This case has been observed with a MaxHeight set on the DataGridControl.
                  break;
                }

                containersHeight += containerHeight;
                startIndex = currentIndex;
                currentIndex += ( direction == GeneratorDirection.Forward ) ? 1 : -1;
              }
            }
          }
        }
      }

      m_layoutedContainers.Sort();

      if( startIndex > endIndex )
      {
        pageIndexes = new PageIndexes( endIndex, startIndex );
      }
      else
      {
        pageIndexes = new PageIndexes( startIndex, endIndex );
      }
    }
    private void GeneratePage(
      double availableHeight,
      bool measureInvalidated,
      ref PageIndexes generatedPage,
      out double containersHeight )
    {
      containersHeight = 0d;
      ICustomItemContainerGenerator generator = this.CustomItemContainerGenerator;

      // The generator can be null if we're in design mode.
      if( generator == null )
        return;

      // Make sure that container recycling is currently enabled on the generator.
      generator.IsRecyclingEnabled = true;

      bool pageChanged =
        ( generatedPage.StartIndex != m_lastGeneratedPage.StartIndex )
        || ( m_lastGeneratedPageViewPortHeight != availableHeight );

      if( ( pageChanged ) || ( measureInvalidated ) )
      {
        UIElement focusedContainer = DataGridItemsHost.GetItemsHostContainerFromElement( this, Keyboard.FocusedElement as DependencyObject );
        HashSet<UIElement> layoutedContainersToRecycle = new HashSet<UIElement>();
        int newPageLengthApproximation = Math.Max( 1, m_lastGeneratedPage.Length );

        foreach( LayoutedContainerInfo containerInfo in m_layoutedContainers )
        {
          int realizedIndex = containerInfo.RealizedIndex;
          UIElement container = containerInfo.Container;
          bool waitForRecycle = ( ( generatedPage.StartIndex != -1 ) && ( realizedIndex >= generatedPage.StartIndex ) && ( realizedIndex <= generatedPage.StartIndex + newPageLengthApproximation ) )
                             || ( ( generatedPage.EndIndex != -1 ) && ( realizedIndex >= generatedPage.EndIndex - newPageLengthApproximation ) && ( realizedIndex <= generatedPage.EndIndex ) );

          // Mark the container has a candidate for recycling.
          if( ( waitForRecycle ) || ( container == focusedContainer ) )
          {
            layoutedContainersToRecycle.Add( container );
          }
          // The element will probably not be on the generated page.  Recycle its container immediatly
          // to minimize the number of new containers created.
          else
          {
            this.TrySafeRecycleContainer( generator, realizedIndex, container );

            m_layoutedContainersToRecycle.Add( container );
          }
        }

        m_layoutedContainers.Clear();

        this.GenerateContainers( generator, availableHeight, layoutedContainersToRecycle, measureInvalidated, ref generatedPage, out containersHeight );

        // We do not recycle the focused element!
        if( layoutedContainersToRecycle.Contains( focusedContainer ) )
        {
          layoutedContainersToRecycle.Remove( focusedContainer );
          m_layoutedContainersToRecycle.Remove( focusedContainer );
          m_layoutedContainers.Add( new LayoutedContainerInfo( generator.GetRealizedIndexForContainer( focusedContainer ), focusedContainer ) );
        }

        // Recycle the containers for the current page.
        this.RecycleContainers( layoutedContainersToRecycle, generator );

        foreach( UIElement container in layoutedContainersToRecycle )
        {
          m_layoutedContainersToRecycle.Add( container );
        }

        m_lastGeneratedPage = generatedPage;
        m_lastGeneratedPageViewPortHeight = availableHeight;
        m_lastGeneratedPageContainersHeight = containersHeight;
      }
      else
      {
        generatedPage = m_lastGeneratedPage;
        containersHeight = m_lastGeneratedPageContainersHeight;
      }
    }
    protected override void HandlePageDownKey( KeyEventArgs e )
    {
      if( e.Handled )
        return;

      e.Handled = true;
      DataGridControl dataGridControl = this.ParentDataGridControl;
      NavigationBehavior navigationBehavior = dataGridControl.NavigationBehavior;
      bool changeCurrentColumn = ( navigationBehavior == NavigationBehavior.CellOnly );

      // CTRL + PageDown
      if( ( e.KeyboardDevice.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control )
      {
        // Simply scroll to bottom and set last index as current
        this.ScrollInfo.ScrollOwner.ScrollToBottom();
        this.SetCurrent( this.CustomItemContainerGenerator.ItemCount - 1, changeCurrentColumn );
        return;
      }

      UIElement focusedContainer = DataGridItemsHost.GetItemsHostContainerFromElement( this, Keyboard.FocusedElement as DependencyObject );

      // No focused container or no navigation allowed
      if( ( focusedContainer == null ) || ( navigationBehavior == NavigationBehavior.None ) )
      {
        // We just need to scroll one page down.
        this.ScrollInfo.ScrollOwner.PageDown();
        return;
      }

      int generatorItemCount = this.CustomItemContainerGenerator.ItemCount;
      int focusedContainerRealizedIndex = this.CustomItemContainerGenerator.GetRealizedIndexForContainer( focusedContainer );
      int maxIndex = generatorItemCount - 1;

      if( focusedContainerRealizedIndex == maxIndex )
      {
        this.MoveFocus( new TraversalRequest( FocusNavigationDirection.Down ) );
      }
      else
      {
        this.InvalidateMeasure();
        double containersHeight;
        PageIndexes generatedPage = new PageIndexes( focusedContainerRealizedIndex, -1 );
        this.GeneratePage( this.RenderSize.Height, false, ref generatedPage, out containersHeight );
        this.SetVerticalOffsetCore( generatedPage.StartIndex );
        int initialDesiredIndex;

        // Last row not totally visible, take the one before the last
        if( ( containersHeight > this.RenderSize.Height ) && ( generatedPage.Length > 1 ) )
        {
          initialDesiredIndex = generatedPage.EndIndex - 1;
        }
        else
        {
          initialDesiredIndex = generatedPage.EndIndex;
        }

        if( focusedContainerRealizedIndex != initialDesiredIndex )
        {
          int desiredPageDownIndex = initialDesiredIndex;

          bool isDataRow = false;

          // SetCurrent on the index or up to focusedContainerRealizedIndex to a focusable index 

          while( ( !dataGridControl.HasValidationError )
              && ( !isDataRow )
              && ( desiredPageDownIndex > focusedContainerRealizedIndex ) )
          {
            if( this.SetCurrent( desiredPageDownIndex, changeCurrentColumn, out isDataRow ) )
              return;

            desiredPageDownIndex--;
          }

          if( ( dataGridControl.HasValidationError ) || ( isDataRow ) )
            return;

          //Debug.Assert( false, "When this will occur???" );
          desiredPageDownIndex = initialDesiredIndex + 1;
          isDataRow = false;

          // No container were focused while processing indexes from focused to 
          // initialDesiredIndex, try SetCurrent on indexes higher than
          // the initial down to maxIndex
          while( ( !dataGridControl.HasValidationError )
              && ( !isDataRow )
              && ( desiredPageDownIndex <= maxIndex ) )
          {
            if( this.SetCurrent( desiredPageDownIndex, changeCurrentColumn, out isDataRow ) )
              return;

            desiredPageDownIndex++;
          }
        }
      }
    }
    protected override void HandlePageUpKey( KeyEventArgs e )
    {
      if( e.Handled )
        return;

      e.Handled = true;

      DataGridControl dataGridControl = this.ParentDataGridControl;
      NavigationBehavior navigationBehavior = dataGridControl.NavigationBehavior;
      bool changeCurrentColumn = ( navigationBehavior == NavigationBehavior.CellOnly );

      if( ( e.KeyboardDevice.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control )
      {
        // Simply scroll to top and set first index as current
        this.ScrollInfo.ScrollOwner.ScrollToTop();
        this.SetCurrent( 0, changeCurrentColumn );
        return;
      }

      UIElement focusedContainer = DataGridItemsHost.GetItemsHostContainerFromElement( this, Keyboard.FocusedElement as DependencyObject );

      if( ( focusedContainer == null ) || ( navigationBehavior == NavigationBehavior.None ) )
      {
        // We just need to scroll one page up.
        this.ScrollInfo.ScrollOwner.PageUp();
        return;
      }

      int focusedContainerRealizedIndex = this.CustomItemContainerGenerator.GetRealizedIndexForContainer( focusedContainer );

      if( focusedContainerRealizedIndex == 0 )
      {
        this.MoveFocus( new TraversalRequest( FocusNavigationDirection.Up ) );
      }
      else
      {
        this.InvalidateMeasure();
        double containersHeight;
        PageIndexes generatedPage = new PageIndexes( -1, focusedContainerRealizedIndex );
        this.GeneratePage( this.RenderSize.Height, false, ref generatedPage, out containersHeight );
        this.SetVerticalOffsetCore( generatedPage.StartIndex );
        int initialDesiredIndex = generatedPage.StartIndex;

        if( focusedContainerRealizedIndex != initialDesiredIndex )
        {
          int desiredPageUpIndex = initialDesiredIndex;

          bool isDataRow = false;

          // SetCurrent on the index or down to a focusable index
          while( ( !dataGridControl.HasValidationError )
              && ( !isDataRow )
              && ( desiredPageUpIndex < focusedContainerRealizedIndex ) )
          {
            if( this.SetCurrent( desiredPageUpIndex, changeCurrentColumn, out isDataRow ) )
              return;

            desiredPageUpIndex++;
          }

          if( ( dataGridControl.HasValidationError ) || ( isDataRow ) )
            return;

          // No container were focused while processing indexes from focused to 
          // initialDesiredIndex, try SetCurrent on indexes lower than
          // the initial up to 0
          desiredPageUpIndex = initialDesiredIndex - 1;
          isDataRow = false;

          while( ( !dataGridControl.HasValidationError )
              && ( !isDataRow )
              && ( desiredPageUpIndex > 0 ) )
          {
            if( this.SetCurrent( desiredPageUpIndex, changeCurrentColumn, out isDataRow ) )
              return;

            desiredPageUpIndex--;
          }
        }
      }
    }