Hi! My name is Marc and I work in the technical support department here at Xceed. My main focus is to help those seeking help and give developers something exciting to read about. Since data virtualization is plenty to get excited about, let’s start there.
We all try to understand exactly what is going on in the “Data Virtualization” world and try to grasp its intentions; however, we sometimes need to sit back and take a look at what our possibilities are. What if we can go beyond that and try to cheat a little (as long as no one gets hurt)? Being from technical support and noticing that the same questions are often asked. How can something that seems so trivial be so complicated? Usually getting selected items is easy to achieve by simply accessing a property of a control. If you have tried to access the SelectedItems property from the DataGridControl when using data virtualization, you now know that an exception will be thrown stating that the operation is not supported. This happens when the number of SelectedItems exceeds the PageSize because these records aren’t loaded into memory yet, and the ones that were, well, they’re gone anyways since I scrolled down.
But not to worry, there are workarounds! This actually became a common question, especially when you want to allow deletion of multiple rows. So today I thought I would create this post in hopes of answering some of your questions.
Like I mentionned before, I will go ahead and explain how to delete multiple rows, although this workaround can apply to various other scenarios.
So to make things a little clearer, we will begin by creating a class called VirtualizationHelper. And from here on out, we will use this class to process any lookups that need to be done.
public class VirtualizationHelper<T>
where T : class
{
private static List<int>
OrderSelectionRangesIndexes( IList<SelectionRange>
selectedItemRanges )
{
List<int>
indexes = new List<int>();
foreach( var selectionRange in selectedItemRanges )
{
for( int i = selectionRange.StartIndex; i <=
selectionRange.EndIndex; i++ )
{
indexes.Add( i );
}
}
return ( from i in indexes orderby i select i ).ToList<int>();
}
public VirtualizationHelper( DataGridControl
dataGridControl )
{
if( typeof( T ) == typeof( object ) )
throw new InvalidOperationException();
m_dataGridControl
= dataGridControl;
}
public void ProcessSelectionLookUp()
{
if(
m_orderedSelectionIndexes != null )
throw new InvalidOperationException();
var indexes
= VirtualizationHelper<T>.OrderSelectionRangesIndexes(
m_dataGridControl.SelectedItemRanges );
if( indexes.Count() == 0 )
return;
m_orderedSelectionIndexes
= indexes;
INotifyCollectionChanged notifyCollectionChanged
= m_dataGridControl.Items as INotifyCollectionChanged;
notifyCollectionChanged.CollectionChanged
+= ( sender, args ) =>
{
if( args.Action
== NotifyCollectionChangedAction.Replace )
{
if( ( m_selectedIndexWaitingForRealizedItem != -1 ) &&
( m_selectedIndexWaitingForRealizedItem == args.OldStartingIndex ) )
{
m_selectedIndexWaitingForRealizedItem
= -1;
m_dataGridControl.Dispatcher.BeginInvoke( new Action( () => this.ProcessSelectionLookUpCore() ) );
}
}
};
this.ProcessSelectionLookUpCore();
}
public event EventHandler<LookUpEventArgs<T>>
SelectedItemLookUp;
public event EventHandler SelectedItemLookUpCompleted;
private void ProcessSelectionLookUpCore()
{
if(
m_orderedSelectionIndexes == null )
throw new InvalidOperationException();
while( m_orderedSelectionIndexes.Count > 0 )
{
int globalIndex
= m_orderedSelectionIndexes.Last();
T item =
m_dataGridControl.Items[ globalIndex ] as T;
if( item == null )
{
// Item not yet realized.
m_selectedIndexWaitingForRealizedItem
= globalIndex;
return;
}
// Item is realized
this.OnSelectedItemLookUp(
item, globalIndex );
m_orderedSelectionIndexes.Remove(
globalIndex );
}
this.OnSelectedItemLookUpCompleted();
return;
}
private void OnSelectedItemLookUp(
T item, int globalIndex )
{
if( this.SelectedItemLookUp != null )
this.SelectedItemLookUp( this, new LookUpEventArgs<T>(
item, globalIndex ) );
}
private void OnSelectedItemLookUpCompleted()
{
if( this.SelectedItemLookUpCompleted != null )
this.SelectedItemLookUpCompleted( this, EventArgs.Empty );
// The whole lookup of all the selected items is completed.
m_orderedSelectionIndexes
= null;
m_selectedIndexWaitingForRealizedItem
= -1;
var collectionView =
m_dataGridControl.ItemsSource as ICollectionView;
collectionView.Refresh();
m_dataGridControl
= null;
}
private List<int>
m_orderedSelectionIndexes = null;
private int m_selectedIndexWaitingForRealizedItem
= -1;
private DataGridControl m_dataGridControl;
}
public class LookUpEventArgs<T>
: EventArgs
where T : class
{
public LookUpEventArgs(
T item, int globalIndex )
{
this.Item =
item;
this.GlobalIndex
= globalIndex;
}
public T Item { get; private set; }
public int GlobalIndex
{ get; private set; }
}
Now we can send in our DataGridControl and get the SelectedItems we need. Even though this does defeat the whole purpose of data virtualization, there are those who may want to do something with those records that are not in memory yet. Now back to business… So we have this class, but how do we use it? The next part is pretty simple since the helper class does all the work for you, but you still have to call the appropriate methods.
In your code behind, handle the DeletingSelectedItems event and use the helper class from there.
void dataGridControl1_DeletingSelectedItems( object sender, CancelRoutedEventArgs e )
{
e.Cancel
= true;
this.DeleteSelectedItems();
var collectionView =
dataGridControl1.ItemsSource as ICollectionView;
collectionView.Refresh();
dataGridControl1.IsEnabled
= true;
}
private void DeleteSelectedItems()
{
dataGridControl1.IsEnabled = false;
var helper = new VirtualizationHelper<Record>(
dataGridControl1 );
helper.SelectedItemLookUp
+= ( sender, args ) =>
{
myBusinessObjectCollection.Remove(
args.Item.Index );
};
helper.SelectedItemLookUpCompleted
+= ( sender, args ) =>
{
dataGridControl1.SelectedItemRanges.Clear();
dataGridControl1.IsEnabled
= true;
};
helper.ProcessSelectionLookUp();
}
So once we have deleted whatever we had to, we need to refresh the CollectionView so that the DataGridControl can reflect the changes that have been made.
One issue to keep in mind is that the above solution deletes when a user selects DataRows from top to bottom. If you are looking to implement a delete capability, please remember that you need to handle selection as well as selecting DataRows from bottom to top (user clicks on the 100th record and holds the Shift key and clicks on the 25th record). If it is not handled, the DataGrid won’t delete those items. It is important to think of everything that the user can do.
I will conclude this post by saying "thank you!" to all the readers and for your support! Marc - out.