コード例 #1
0
        private VirtualPage CreateNewPage(int itemIndex)
        {
            Debug.Assert(!m_tableOfContent.ContainsPageForSourceIndex(itemIndex));

            int pageStartIndex = this.GetPageStartingIndexForItemIndex(itemIndex);

            int pageSize = m_pagingManager.PageSize;

            int expectedItemCount = Math.Min(pageSize, (m_virtualCount - pageStartIndex));

            expectedItemCount = Math.Max(0, expectedItemCount);

            VirtualPage page = VirtualPage.CreateEmptyPage(this, pageStartIndex, expectedItemCount);

            m_tableOfContent.AddPage(page);

            // If we have a pending commit page, this brandly new created page will get its query data queued when we are notified
            // of a commit completed and that we no longer have any pages awaiting commiting.
            if (!this.HasPagePendingCommit)
            {
                m_pagingManager.QueueQueryData(page);
            }

            Debug.WriteLineIf(VirtualPageManager.DebugDataVirtualization, "Creating VirtualItemPlaceHolder for page: " + page.ToString());
            return(page);
        }
コード例 #2
0
    protected internal override void OnCommitErrorChanged( VirtualPage page, AsyncCommitInfo commitInfo )
    {
      base.OnCommitErrorChanged( page, commitInfo );

      Debug.Assert( m_asyncCommitInfosInProgress.Contains( commitInfo ) );

      object error = commitInfo.Error;

      if( error == null )
      {
        Debug.Assert( ( m_asyncCommitInfosInError != null ) && ( m_asyncCommitInfosInError.Contains( commitInfo ) ) );

        m_asyncCommitInfosInError.Remove( commitInfo );

        if( m_asyncCommitInfosInError.Count == 0 )
          m_asyncCommitInfosInError = null;
      }
      else
      {
        if( m_asyncCommitInfosInError == null )
          m_asyncCommitInfosInError = new LinkedList<AsyncCommitInfo>();

        if( m_asyncCommitInfosInError.Contains( commitInfo ) )
          m_asyncCommitInfosInError.Remove( commitInfo );

        m_asyncCommitInfosInError.AddFirst( commitInfo );
      }

      this.UpdateConnectionState();
    }
コード例 #3
0
    protected internal override void OnQueryErrorChanged( VirtualPage page, AsyncQueryInfo queryInfo )
    {
      base.OnQueryErrorChanged( page, queryInfo );

      // It is possible that m_asyncQueryInfosInProgress does not contain the queryInfo when
      // the query was aborted but the user did not stop the query and later on set the queryInfo error
      // event if the queryInfo ShouldAbort is set to True.
      Debug.Assert( ( m_asyncQueryInfosInProgress.Contains( queryInfo ) ) || ( queryInfo.ShouldAbort ) );

      object error = queryInfo.Error;

      if( error == null )
      {
        // Even if the queryInfo's ShouldAbort property is set to True, clean-up the error.
        Debug.Assert( ( m_asyncQueryInfosInError != null ) && ( m_asyncQueryInfosInError.Contains( queryInfo ) ) );

        m_asyncQueryInfosInError.Remove( queryInfo );

        if( m_asyncQueryInfosInError.Count == 0 )
          m_asyncQueryInfosInError = null;
      }
      else if( !queryInfo.ShouldAbort )
      {
        // Only add errors if the queryInfo's ShouldAbort property is set to False.
        if( m_asyncQueryInfosInError == null )
          m_asyncQueryInfosInError = new LinkedList<AsyncQueryInfo>();

        if( m_asyncQueryInfosInError.Contains( queryInfo ) )
          m_asyncQueryInfosInError.Remove( queryInfo );

        m_asyncQueryInfosInError.AddFirst( queryInfo );
      }

      this.UpdateConnectionState();
    }
コード例 #4
0
        protected internal override void OnQueryItems(VirtualPage page, AsyncQueryInfo queryInfo)
        {
            base.OnQueryItems(page, queryInfo);

            DataGridVirtualizingQueryableCollectionViewGroup collectionViewGroup =
                this.GetLinkedCollectionViewGroup(page.ParentVirtualList) as DataGridVirtualizingQueryableCollectionViewGroup;

            IQueryable queryableToUse;

            int virtualItemCount = collectionViewGroup.VirtualItemCount;

            bool queryableIsReversed;

            if ((!m_supportsPrimaryKeyOptimizations) || (queryInfo.StartIndex < (virtualItemCount / 2)))
            {
                queryableIsReversed = false;
                queryableToUse      = collectionViewGroup.Queryable.Slice(queryInfo.StartIndex, queryInfo.RequestedItemCount);
            }
            else
            {
                queryableIsReversed = true;

                int reversedStartIndex = virtualItemCount - (queryInfo.StartIndex + queryInfo.RequestedItemCount);

                queryableToUse = collectionViewGroup.ReversedQueryable.Slice(reversedStartIndex, queryInfo.RequestedItemCount);
            }

            System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(this.AsyncGatherItems), new object[] { queryInfo, queryableToUse, queryableIsReversed });
        }
コード例 #5
0
    protected internal override void OnQueryItems( VirtualPage page, AsyncQueryInfo queryInfo )
    {
      // The VirtualPageManager is not connected to the CollectionView anymore,
      // do NOT query items since it will be done by the new VirtualPageManager
      // assigned to the same CollectionView
      if( !this.IsConnected )
        return;

      Debug.Assert( !m_asyncQueryInfosInProgress.Contains( queryInfo ) );
      m_asyncQueryInfosInProgress.Add( queryInfo );

      if( m_asyncQueryInfosInError != null )
      {
        LinkedListNode<AsyncQueryInfo> queryInfoInErrorNode = m_asyncQueryInfosInError.First;

        while( queryInfoInErrorNode != null )
        {
          if( DataGridPageManagerBase.QueryInfoWeakComparer.Equals( queryInfo, queryInfoInErrorNode.Value ) )
          {
            m_asyncQueryInfosInError.Remove( queryInfoInErrorNode );
            break;
          }

          queryInfoInErrorNode = queryInfoInErrorNode.Next;
        }

        if( m_asyncQueryInfosInError.Count == 0 )
          m_asyncQueryInfosInError = null;
      }

      this.UpdateConnectionState();
    }
コード例 #6
0
        internal void RemovePage(VirtualPage page)
        {
            if (page.IsDisposed)
            {
                return;
            }

            Debug.Assert(page != null);
            //Debug.Assert( !page.IsRestarting );

            Debug.Assert(!page.IsDirty);

            // A filled page is being removed.  Change the version.
            this.IncrementVersion();

            // Update the table of content of the page's ParentVirtualList
            page.ParentVirtualList.TableOfContent.RemovePage(page);

            m_pageNodes.Remove(page);

            Debug.WriteLineIf(VirtualPageManager.DebugDataVirtualization, "Removed Page: " + page.ToString());

            // Dispose the page since it will never be reused
            page.Dispose();
        }
コード例 #7
0
        internal static VirtualPage CreateEmptyPage(VirtualList parentVirtualList, int startSourceIndex, int entryCount)
        {
            if (parentVirtualList == null)
            {
                throw new ArgumentNullException("parentVirtualList");
            }

            if (startSourceIndex < 0)
            {
                throw new ArgumentOutOfRangeException("startSourceIndex", startSourceIndex, "startSourceIndex must be greater than or equal to zero.");
            }

            if (entryCount < 0)
            {
                throw new ArgumentOutOfRangeException("entryCount", entryCount, "entryCount must be greater than or equal to zero.");
            }

            EmptyDataItem[] emptyDataItems = new EmptyDataItem[entryCount];
            for (int i = 0; i < entryCount; i++)
            {
                emptyDataItems[i] = new EmptyDataItem(startSourceIndex + i, parentVirtualList);
            }

            VirtualPage emptyDataItemPage = new VirtualPage(parentVirtualList, startSourceIndex, emptyDataItems);

            emptyDataItemPage.IsFilled = false;

            return(emptyDataItemPage);
        }
コード例 #8
0
        private void QueueCommitDataOrAbortIfRequired(LinkedListNode <VirtualPage> pageNode, bool removeAfterOperation)
        {
            VirtualPage page = pageNode.Value;

            // Update the flag in case this page must be removed after an abort or commit operation
            page.RemoveAfterOperation = removeAfterOperation;

            // The only circumstance when we should remove a page which is not removable is if we are restarting.
            Debug.Assert((page != null) && (!page.IsDisposed) && ((page.IsRemovable) || (page.ParentVirtualList.IsRestarting)));

            if (page.IsDirty)
            {
                // Don't remove pages which contains modifications.  We'll remove them from the book when they are committed, if they aren't locked.
                this.QueueCommitData(page);
            }
            else if (!page.IsFilled)
            {
                // The page is not filled, we must send abort the QueryData for this page in case it was sent
                page.AbortQueryDataOperation();
            }

            // The page must be removed after operation and it has nothing to commit and is not  currently aborting an operation. It is safe to remove it
            if (removeAfterOperation && !page.IsCommitPending && !page.IsAborting)
            {
                this.RemovePage(page);
            }
        }
コード例 #9
0
        internal virtual void OnVirtualListPageRestarted(VirtualList virtualList, VirtualPage page)
        {
            Debug.Assert(m_managedLists.Contains(virtualList));
            Debug.Assert(m_pageNodes.Contains(page));

            this.RemovePage(page);
        }
コード例 #10
0
 internal void OnVirtualPageRestarting(VirtualPage page)
 {
     // Notify the VirtualPageManager that this page is restarted
     // to ensure it commits its data or aborts the QueryItems
     // if already invoked
     Debug.Assert(m_restartingPages.Contains(page));
     m_pagingManager.OnVirtualListPageRestarting(this, page);
 }
コード例 #11
0
        protected internal override void OnQueryItemsCompleted(VirtualPage page, AsyncQueryInfo queryInfo, object[] fetchedItems)
        {
            base.OnQueryItemsCompleted(page, queryInfo, fetchedItems);

            Debug.Assert(m_asyncQueryInfosInProgress.Contains(queryInfo));
            m_asyncQueryInfosInProgress.Remove(queryInfo);

            this.UpdateConnectionState();
        }
コード例 #12
0
        private void RaiseCollectionViewOnCommitItems(VirtualPage dispatchedPage, AsyncCommitInfo dispatchedCommitInfo)
        {
            DataGridVirtualizingCollectionViewBase collectionView = this.CollectionView as DataGridVirtualizingCollectionViewBase;

            DataGridVirtualizingCollectionViewGroupBase collectionViewGroup =
                this.GetLinkedCollectionViewGroup(dispatchedPage.ParentVirtualList) as DataGridVirtualizingCollectionViewGroupBase;

            Debug.Assert((collectionViewGroup != null) && (collectionView != null));

            collectionView.OnCommitItems(dispatchedCommitInfo);
        }
コード例 #13
0
        internal virtual void OnVirtualListPageRestarting(VirtualList virtualList, VirtualPage page)
        {
            Debug.Assert(m_managedLists.Contains(virtualList));

            LinkedListNode <VirtualPage> pageNode = m_pageNodes.Find(page);

            Debug.Assert(pageNode != null);

            // RemovePageNode takes care of either raising the AbortQueryData event or aborting the QueryData Dispatcher Operation altogether.
            // It also takes care of raising the CommitVirtualData event for loaded pages which contains modified data.
            this.QueueCommitDataOrAbortIfRequired(pageNode, false);
        }
コード例 #14
0
        internal void FillEmptyPage(AsyncQueryInfo asyncQueryInfo, object[] fetchedItems)
        {
            // The VirtualList is disposed or part of a PagingManager that will be disposed (only disconnected when dispose is required)
            if (!this.PagingManager.IsConnected)
            {
                return;
            }

            Debug.Assert(!this.IsDisposed);
            Debug.Assert(!asyncQueryInfo.IsDisposed);

            // We do not want to move the page we are about to fill to the front since it does not count as a legitimate user-acess.  It will get moved to the front when one of its item is accessed.
            VirtualPage page = null;

            page = this.GetPageOrDefaultForItemIndex(asyncQueryInfo.StartIndex, true);

            // Although extremely rare, this situation could occur if we are calling RemovePageNode and the QueryData Dispatcher Operation
            // which has been asyncronously invoked in CreateNewPage is raising the QueryData event at the exact moment when we
            // try to abort the dispatcher operation.  This means that the customer will have queued an async request for data
            // for a page we no longer care about, and have already removed from the Table of Content and our LinkedList.
            // This should NOT occur if the user did not abort the request and called the AsyncQueryInfo EndQuery method since AsyncQueryInfo should
            // not have invoked the EndQueryAction if its ShouldAbort property was set to true.
            if (page == null)
            {
                return;
            }

            Debug.Assert(!page.IsFilled);
            Debug.Assert(this.GetPageStartingIndexForItemIndex(asyncQueryInfo.StartIndex) == asyncQueryInfo.StartIndex);

            Debug.Assert(fetchedItems.Length <= page.Count);

            if (fetchedItems.Length == page.Count)
            {
                object[] oldItems = page.ToItemArray();

                m_tableOfContent.RemovePage(page);

                page.EndQueryItems(asyncQueryInfo, fetchedItems);

                m_tableOfContent.AddPage(page);

                Debug.WriteLineIf(VirtualPageManager.DebugDataVirtualization, "Replaced TOC items/index for page: " + page.ToString());

                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, fetchedItems, oldItems, asyncQueryInfo.StartIndex));
            }
            else
            {
                // The expected count was not met.  Maybe the user told us the source was bigger than it really is, or maybe there were delete operations made on the source since the last restart.
                // Let's refresh the CollectionView. This will restart the VirtualItemBook and raise the CollectionView's OnCollectionChanged Reset notification.
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }
コード例 #15
0
        public bool TryGetPageForItem(object item, out VirtualPage page)
        {
            page = null;

            int index;

            if (m_objectVersusIndexDictionary.TryGetValue(item, out index))
            {
                return(m_indexVersusPageDictionary.TryGetValue(index, out page));
            }

            return(false);
        }
コード例 #16
0
    protected internal override void OnAbortQueryItems( VirtualPage page, AsyncQueryInfo queryInfo )
    {
      base.OnAbortQueryItems( page, queryInfo );

      // It is possible that the queryInfo was removed previously
      m_asyncQueryInfosInProgress.Remove( queryInfo );

      // In case the page query was aborted when
      // VirtualPageManager.CleanUpUnused is called
      if( page.RemoveAfterOperation )
        this.RemovePage( page );

      this.UpdateConnectionState();
    }
コード例 #17
0
    protected internal override void OnBuiltInAbort( VirtualPage page, AsyncQueryInfo queryInfo )
    {
      // When a built-in abort occurs, we ensure to remove
      // any AsyncQueryInfo from references since the ConnectionState
      // use this array to update its actual state
      m_asyncQueryInfosInProgress.Remove( queryInfo );

      // In case the page query was aborted when
      // VirtualPageManager.CleanUpUnused is called
      if( page.RemoveAfterOperation )
        this.RemovePage( page );

      this.UpdateConnectionState();
    }
コード例 #18
0
        private VirtualPage GetPageOrDefaultForItemIndex(int index, bool preventMoveToFront)
        {
            VirtualPage page = null;

            if (m_tableOfContent.TryGetPageForSourceIndex(index, out page))
            {
                if (!preventMoveToFront)
                {
                    m_pagingManager.MovePageToFront(page);
                }
            }

            return(page);
        }
コード例 #19
0
        internal void QueueCommitData(VirtualPage page)
        {
            Debug.WriteLineIf(VirtualPageManager.DebugDataVirtualization, "QueueCommitData for page " + page.ToString());
            Debug.Assert(m_managedLists.Contains(page.ParentVirtualList));
            Debug.Assert(page.IsFilled);
            Debug.Assert(page.IsDirty);

            if (this.RestartingManager)
            {
                this.ShouldRefreshAfterRestart = true;
            }

            page.QueueCommitData(this.Dispatcher);
        }
コード例 #20
0
    protected internal override void OnCommitItemsCompleted( VirtualPage page, AsyncCommitInfo commitInfo )
    {
      Debug.Assert( m_asyncCommitInfosInProgress.Contains( commitInfo ) );
      m_asyncCommitInfosInProgress.Remove( commitInfo );
      this.UpdateConnectionState();

      base.OnCommitItemsCompleted( page, commitInfo );

      // In case the page query was aborted when
      // VirtualPageManager.CleanUpUnused is called
      if( page.RemoveAfterOperation )
        this.RemovePage( page );

      Debug.WriteLineIf( VirtualPageManager.DebugDataVirtualization, "OnCommitItemsCompleted for page " + page.ToString() );
    }
コード例 #21
0
        protected internal override void OnQueryItemsCompleted(VirtualPage page, AsyncQueryInfo queryInfo, object[] fetchedItems)
        {
            DataGridVirtualizingQueryableCollectionView collectionView = this.CollectionView as DataGridVirtualizingQueryableCollectionView;

            // The VirtualPageManager was Disposed
            if (collectionView == null)
            {
                return;
            }

            using (collectionView.DeferRefresh())
            {
                base.OnQueryItemsCompleted(page, queryInfo, fetchedItems);
            }
        }
コード例 #22
0
        private void PreEmptiveLoadPages(int sourceIndex, VirtualPage page)
        {
            // The VirtualList is disposed or part of a PagingManager
            // that will be disposed (only disconnected when dispose is required)
            if (!this.PagingManager.IsConnected)
            {
                return;
            }

            Debug.Assert(!this.IsDisposed);

            double preemptivePageQueryRatio = m_pagingManager.PreemptivePageQueryRatio;
            int    pageSize = m_pagingManager.PageSize;

            double pageRatio = (preemptivePageQueryRatio > 0.5) ? 0.5 :
                               (preemptivePageQueryRatio < 0.0) ? 0 : preemptivePageQueryRatio;

            double boundariesItemCount = (pageRatio * pageSize);

            int preEmptivePageStartIndex = -1;

            if ((page.StartDataIndex > 0) && (sourceIndex < (page.StartDataIndex + boundariesItemCount)))
            {
                // Pre emptively load the previous page.
                preEmptivePageStartIndex = page.StartDataIndex - pageSize;
            }
            else if ((page.EndDataIndex < (m_virtualCount - 1)) && (sourceIndex > (page.EndDataIndex - boundariesItemCount)))
            {
                // Pre emptively load the next page.
                preEmptivePageStartIndex = page.EndDataIndex + 1;
            }

            if (preEmptivePageStartIndex != -1)
            {
                VirtualPage preEmptivePage = null;

                // We do not want to move the pre-emptive page to the front if it is already created since it does not count as a
                // legitimate user-acess.
                preEmptivePage = this.GetPageOrDefaultForItemIndex(preEmptivePageStartIndex, true);

                if (preEmptivePage == null)
                {
                    // The pre-emptive page is not yet created. Let's do it and add it to the back since it is not really accessed at the moment.
                    preEmptivePage = this.CreateNewPage(preEmptivePageStartIndex);
                    m_pagingManager.AddPage(preEmptivePage, VirtualPageManager.PageInsertPosition.Back);
                }
            }
        }
コード例 #23
0
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();

            ReadOnlyCollection <VirtualPage> virtualPages = m_tableOfContent.VirtualPages;
            int pageCount = virtualPages.Count;

            for (int i = 0; i < pageCount; i++)
            {
                VirtualPage page = virtualPages[i];

                builder.Append(i.ToString() + ": Page " + page.ToString() + Environment.NewLine);
            }

            return(builder.ToString());
        }
コード例 #24
0
        internal void MovePageToFront(VirtualPage page)
        {
            // The further from the front a page is, the longer it has been since it was requested.
            Debug.Assert(page != null);

            LinkedListNode <VirtualPage> firstNode = m_pageNodes.First;

            if (firstNode.Value != page)
            {
                LinkedListNode <VirtualPage> node = m_pageNodes.Find(page);
                m_pageNodes.Remove(node);
                m_pageNodes.AddFirst(node);

                Debug.WriteLineIf(VirtualPageManager.DebugDataVirtualization, "Moved To Front: Page " + page.ToString());
            }
        }
コード例 #25
0
        protected internal override void OnAbortQueryItems(VirtualPage page, AsyncQueryInfo queryInfo)
        {
            DataGridVirtualizingCollectionView collectionView = this.CollectionView as DataGridVirtualizingCollectionView;

            // The VirtualPageManager was Disposed
            if (collectionView == null)
            {
                return;
            }

            DataGridVirtualizingCollectionViewGroup collectionViewGroup = this.GetLinkedCollectionViewGroup(page.ParentVirtualList) as DataGridVirtualizingCollectionViewGroup;

            collectionView.OnAbortQueryItems(queryInfo, collectionViewGroup);

            base.OnAbortQueryItems(page, queryInfo);
        }
コード例 #26
0
        protected internal override void OnCommitItems(VirtualPage page, AsyncCommitInfo commitInfo)
        {
            base.OnCommitItems(page, commitInfo);

            Debug.Assert(!m_asyncCommitInfosInProgress.Contains(commitInfo));
            m_asyncCommitInfosInProgress.Add(commitInfo);

            this.UpdateConnectionState();

            Debug.WriteLineIf(VirtualPageManager.DebugDataVirtualization, "OnCommitItems for page " + page.ToString());

            m_collectionView.Dispatcher.BeginInvoke(
                new Action <VirtualPage, AsyncCommitInfo>(this.RaiseCollectionViewOnCommitItems),
                DispatcherPriority.Background,
                page,
                commitInfo);
        }
コード例 #27
0
        public void AddPage(VirtualPage page)
        {
            Debug.Assert(!m_virtualPages.Contains(page));

            m_virtualPages.Add(page);

            int itemCount = page.Count;

            for (int i = 0; i < itemCount; i++)
            {
                VirtualizedItemInfo virtualizedItemInfo = page[i];

                Debug.Assert(!m_objectVersusIndexDictionary.ContainsKey(virtualizedItemInfo.DataItem));
                Debug.Assert(!m_indexVersusPageDictionary.ContainsKey(virtualizedItemInfo.Index));

                m_objectVersusIndexDictionary.Add(virtualizedItemInfo.DataItem, virtualizedItemInfo.Index);
                m_indexVersusPageDictionary.Add(virtualizedItemInfo.Index, page);
            }
        }
コード例 #28
0
        internal void OnVirtualPageRestarted(VirtualPage page)
        {
            Debug.Assert(m_restartingPages.Contains(page));

            // The page is restarted, remove it from the restarting pages
            m_restartingPages.Remove(page);

            // Notify the manager that this page is restarted in order to let it remove it from its m_pageNodes and also from this VirtualList TableOfContent.
            // NOTE: We do not remove it from the TableOfContent immediately to avoid have to insert a condition in  VirtualPageManager.RemovePage since this method
            // used widely to ensure a page is removed from the TableOfContent and from the m_pageNodes list.
            m_pagingManager.OnVirtualListPageRestarted(this, page);

            // Ensure all restarted pages completed their commit or abort operation before notifying that this list is restarted
            if (m_restartingPages.Count == 0)
            {
                Debug.WriteLineIf(VirtualPageManager.DebugDataVirtualization, "Cleared VirtualList");
                this.EndRestart();
            }
        }
コード例 #29
0
        public void Dispose()
        {
            while (m_virtualPages.Count > 0)
            {
                // Remove the page from every Dictionaries and
                // also from m_virtualPages
                VirtualPage page = m_virtualPages[0];
                this.RemovePage(page);
                page.Dispose();
            }

            Debug.Assert(m_objectVersusIndexDictionary.Count == 0);
            Debug.Assert(m_indexVersusPageDictionary.Count == 0);
            Debug.Assert(m_virtualPages.Count == 0);

            m_objectVersusIndexDictionary.Clear();
            m_indexVersusPageDictionary.Clear();
            m_virtualPages.Clear();
        }
コード例 #30
0
        public void RemovePage(VirtualPage page)
        {
            Debug.Assert(m_virtualPages.Contains(page));

            int itemCount = page.Count;

            for (int i = 0; i < itemCount; i++)
            {
                VirtualizedItemInfo virtualizedItemInfo = page[i];

                Debug.Assert(m_objectVersusIndexDictionary.ContainsKey(virtualizedItemInfo.DataItem));
                Debug.Assert(m_indexVersusPageDictionary.ContainsKey(virtualizedItemInfo.Index));

                Debug.Assert(m_indexVersusPageDictionary[virtualizedItemInfo.Index] == page);

                m_objectVersusIndexDictionary.Remove(virtualizedItemInfo.DataItem);
                m_indexVersusPageDictionary.Remove(virtualizedItemInfo.Index);
            }

            m_virtualPages.Remove(page);
        }