I have a TabControl
, in each TabItem
there is a DataGrid
. Each DataGridRow
has a button on the DataGridRowHeader
, which expands and collapses the RowDetails
.
As I use the row details expansion functionality, so have set the VirtualizingPanel ScrollUnit to Pixel, so scrolling is a bit more natural.
The program captures which rows have been expanded in an ObservableCollection<int>
which holds the row index to expand.
When I toggle to a new TabItem
and back to the original TabItem
, it loses which rows have been expanded.
Upon the DataContext
being changed, I'd like to use the ObservableCollection
to reset the expanded rows. I have tried this in the DataGrid
DataContextChanged
event.
public class DataGridBehaviors : Behavior<DataGrid>{
protected override void OnAttached(){
base.OnAttached();
this.AssociatedObject.DataContextChanged += DataGrid_DataContextChanged;
}
protected override void OnDetaching(){
this.AssociatedObject.DataContextChanged -= DataGrid_DataContextChanged;
base.OnDetaching();
}
private void DataGrid_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e){
ModuleGeometry oldModuleGeometry = (ModuleGeometry)e.OldValue;
ModuleGeometry newModuleGeometry = (ModuleGeometry)e.NewValue;
Task.Factory.StartNew(() => {
PerformRowExpansions(newModuleGeometry);
});
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(this.AssociatedObject);
if (scrollViewer != null){
/*
* do some stuff
*/
}
}
private void PerformRowExpansions(ModuleGeometry newModuleGeometry){
ObservableCollection<int> tempExpandedIndexes = new ObservableCollection<int>(newModuleGeometry.ExpandedIndexes);
newModuleGeometry.ExpandedIndexes = new ObservableCollection<int>();
if (this.Dispatcher.CheckAccess()){
foreach (int index in tempExpandedIndexes)
{
DataGridRow row = this.AssociatedObject.ItemContainerGenerator.ContainerFromIndex(index) as DataGridRow;
row.DetailsVisibility = Visibility.Visible;
}
}
else{
this.Dispatcher.Invoke(new Action(() =>{
foreach (int index in tempExpandedIndexes){
DataGridRow row = this.AssociatedObject.ItemContainerGenerator.ContainerFromIndex(index) as DataGridRow;
row.DetailsVisibility = Visibility.Visible;
}
}));
}
}
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++){
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
child = GetVisualChild<T>(v);
if (child != null)
break;
}
return child;
}
}
However, it doesn't like doing this on rows that are not currently in the VirtualizingPanel
.
How can I accomplish this?
Should I just do the expand the currently virtualized rows?
Should the DataGrid
ScrollViewer
be manipulated programmatically as well? How can I account for the rows not visible in the ScrollViewer
?
Is there another object, other the DataGridRow
that I should be setting the RowDetails
Visibility
to?
There are no DataGridRow
containers for the rows that are not currently in the VirtualizingPanel
. That's how the virtualization works.
If you want to persist some information between tab switches, you should store the data to be persisted in your view models. You could for example bind the Button
in the DataGridRowHeader
to some command that adds and removes indexes from a collection in the underlying data class, i.e. the DataContext
of the parent DataGridRow
, TabItem
or TabControl
.
Trying to iterate through the visual DataGridRow
containers won't work unless you disable the UI virtualization. And this may of course lead to performance issues.