2

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.

eran otzap
  • 12,293
  • 20
  • 84
  • 139

1 Answers1

0

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? :)

dev hedgehog
  • 8,698
  • 3
  • 28
  • 55
  • only when i start scrolling – eran otzap Oct 28 '13 at 11:41
  • and the key is an index in an array , each row's DataContext is an array of strings – eran otzap Oct 28 '13 at 11:42
  • Then its likely the first case and you havent fetched data yet though at the time cell binding is being updated. Are you using Recycling Mode of Virtualization? – dev hedgehog Oct 28 '13 at 11:46
  • i actually didn't put any mode , some guessing it's standard Recycling Mode , FYI , Iv'e also tried creating the columns before updating the collection. – eran otzap Oct 28 '13 at 11:48
  • First you fetch new data and then you should create binding. When binding is being created it tries to set value. When you create binding before you fetch new data, you will get an error. Logically, isnt it? :) :) – dev hedgehog Oct 28 '13 at 11:52
  • So you are sure that count in VirtualCollection is greater than 20? Are you using simple list or ObservableCollection as VirtualCollection? If you using simple list, you will fetch new data and the count will be greater but the control (DataGrid) dont get notified about that. In such case the DataGrid holds a duplicate of your old list without newly fetched data. ObservableCollection on other side keeps ItemsSource and your collection sync. – dev hedgehog Oct 28 '13 at 11:56
  • each item in VirtualCollection is a string[] and yes at the point of OnLoadCompleted all the string[] are 21 in length. – eran otzap Oct 28 '13 at 11:57
  • Try out ObservableCollections. – dev hedgehog Oct 28 '13 at 12:00
  • i thought about that i'll try to see how i can fit this in there, but since i'm not adding new items i don't think this is the case , i am dynamically enlarging existing items which are arrays. – eran otzap Oct 28 '13 at 12:03
  • you are not aware of adding items but you are adding items by fetching them :) :) – dev hedgehog Oct 28 '13 at 12:05
  • but if i fetch a static length for the array's it works fine , i can re fetch the items over and over and everything works fine . the only difference is that now i fetch items which are larger in length , and i add a new column to accommodate them , it's manly the same 64 items that i have on the get go.. – eran otzap Oct 28 '13 at 12:08
  • hmmm... I dont understand a word. What works? You said before scrolling is not working but displaying the visible data does work. Now you are telling me that all works when static size is being fetched. – dev hedgehog Oct 28 '13 at 15:51
  • you didn't understand , scrolling works i just want to fetch the values for each record not all at once if i have 5000 cells for each record i don't want to fetch all 5000 for each record. i want to fetch like 20-30 at a time . not the record it self . – eran otzap Oct 29 '13 at 07:44
  • 5000 cells horizontally? really? can you upload your project or send it to me over mail. i think you have already my mail addy. :) – dev hedgehog Oct 29 '13 at 08:32
  • sure , i'll do it later , i'll be glad to get your input – eran otzap Oct 29 '13 at 10:05
  • any resolution to this? I too experience wrong content being put in a my DataGridViewColumn cell via a DataTemplate. if I disable the RowVirtualization then it is fine, always correct content. But performance blows. – T McKeown May 02 '22 at 19:33