简体   繁体   中英

WPF DataGrid Column Virtualization

I'm attempting to perform an horizontal virtualization on my DataGrid . My collection is of type :

   List<string[]>

which it's first dimension length is 64 and second is roughly 5000

I've been using Paul McClean's VirtualCollection to achieve vertical virtualization

My IItemsProvider encapsulates an Iterator which returns item's of string[] which represents a row in my table.

My ItemsProvider :

public class ArrayItemProvider<I,T> :IArrayItemProvider, IItemsProvider<T[]> where I : IList<T>
{      
    public int FetchCount()
    {
        return 64;
    }

    public IList<T[]> FetchRange(int startIndex, int count)
    {
        return _iterator.Skip(startIndex).Take(count).ToList();
    }
}  

The Iterator :

 public class ArrayItemIterator<I, T> : IArrayItemIterator<T> where I : IList<T>
 {             
    public IEnumerator<T[]> GetEnumerator()
    {
        for (int i = 0; i < _arrayItemLength; i++)
        {
            T[] arr = new T[_extent];

            for (int j = 0; j < _extent; j++)
            {
                arr[j] = _items[j][i];
            }

            yield return arr;
        }
    }

    public int Extent 
    {
        get { return _extent; }
    }

    public void UpdateExtent(int extent)
    {
        _extent = extent;
    }
}

}

To summarize the above i receive Items of string[] for a specific range through VirtualCollection .

What i am attempting now is to Virtualaize the Columns as well, My Columns are generated at run time by a given Extent , this is done in an attached property's callback , in a static class DataGridBuilderUtil

cs :

   for (int i = 0; i < _iterator.Extent; i++)
   {
        _dataGrid.Columns.Add(CreateColumn(i));
   }

   private static DataGridColumn CreateColumn(int i)
   {
       var column = new DataGridTextColumn();
       column.Header = "View - " + (i + 1);
       column.Binding = new Binding("[" + i + "]");
       return column;
   }

in DataGridBuilderUtil i also attach the DataGrid's ScrollViewer ScrollChanged event , When an Horizontal Extent is changed :

1) I add a new Column.

2) I update the Iterators Extent to accommodate another column .

3) I re-scroll to the same position Vertically , this makes my ItemsSource (VirtualCollection) which derives from IList to query it's index and request the current page again( with the help of my flag IsDefferedLoadPageRequired )

  private static void OnScrollChanged(object sender, ScrollChangedEventArgs e)
  {
      if(e.HorizontalChange > 0.0)
      {                
          // add column  (1)
         _dataGrid.Columns.Add(CreateColumn(_dataGrid.Columns.Count));              

          // Update the Extent  (2)
          _iterator.UpdateExtent(_dataGrid.Columns.Count);              

          // Makes the VirtualCollection request the current page again. (3)
          _collection.IsDefferedLoadPageRequired = true;                            
          _scrollViewer.ScrollToVerticalOffset(_scrollViewer.VerticalOffset);
       }
  }

So now inside the VirtualCollection

    public T this[int index]
    {
        get
        {
            ....
            int pageIndex = index / PageSize;
            RequestPage(pageIndex);                
            ....
        }             
     }

which quires the ItemsProvider :

    public IList<T[]> FetchRange(int startIndex, int count)
    {
        return _iterator.Skip(startIndex).Take(count).ToList();
    }

which quires the Iterator , Remember that our Extent was incremented to accommodate another column.

    public IEnumerator<T[]> GetEnumerator()
    {
        for (int i = 0; i < _arrayItemLength; i++)
        {
            T[] arr = new T[_extent];

            for (int j = 0; j < _extent; j++)
            {
                arr[j] = _items[j][i];
            }

            yield return arr;
        }
    }

So now i have a string[] item's which was incremented string[20] items are now string[21] My Horizontal Data Virtualization worked.

The Problem is that my cells which are bound in this fashion : (From CreateColumn method above)

 column.Binding = new Binding("[" + i + "]");

have a binding error , in each of the cells in a new column (The binding's in the original columns which are generated when loading the collection work fine :

      System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'String') from '' (type 'String[]').     BindingExpression:Path=[20]; DataItem='String[]' (HashCode=32127640); 
      target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')      ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: Specified argument was out of the range of  valid values. 
      Parameter name: index'

I think this has to do with the fact that when my column is created the array item for that row does not contain that index , alternatively i also tried creating the column after the array was updated.

that had the same result.

The big question here is , Why doesn't The Binding work , and how to refresh the binding ?

Additionally i set DataGrid.EnableColumnVirtualization = True to bring this all together (if the binding would of worked).

Edit :

Iv'e also attempted to create the column after the collection was updated :

     _collection.LoadCompletedEvent += OnLoadCompleted; // VirualCollection event after page is loaded. 

    private static void OnLoadCompleted(object sender, EventArgs e)
    {
        _dataGrid.Columns.Add(CreateColumn(_dataGrid.Columns.Count));
    }

    private static void OnScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if(e.HorizontalChange > 0.0)
        {                
            // Update the Extent
            _iterator.UpdateExtent(_dataGrid.Columns.Count+1);

            // Makes the VirtualCollection request the current page again.
            _collection.IsLoadPageRequired = true;                            
            _scrollViewer.ScrollToVerticalOffset(_scrollViewer.VerticalOffset);
        }
    }

OnLoadComplete is raised after the VirtualCollection ReLoads the current page.

It would be interesting to know if that error is always being thrown or only when you start scrolling. Then the binding might be asking for index that is not available yet since you havent realized it yet in your VirtualCollection.

Though to be honest I have a feeling you are using binding path syntax wrong with the indexers.

Take a look at those links:

http://msdn.microsoft.com/en-us/library/ms742451.aspx

http://msdn.microsoft.com/en-us/library/system.windows.data.binding.path.aspx

As example:

<Binding Path="[key]" .../>

key must be either the typed index to a dictionary or hash table, or the integer index of an array. Also, the value of the key must be a type that is directly bindable to the property where it is applied. For instance, a hash table that contains string keys and string values can be used this way to bind to Text for a TextBox.

Means if your indexer is of type integer you will need something like this in your XAML.

<Binding Path="[(sys:Int32)42,(sys:Int32)24]"... />

I am not sure why you creating your binding manually. You could have done that in xaml, coudnt you? :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM