Search code examples
c#wpfwpfdatagridtabcontrolrowdetails

Setting DataGridRow RowDetails visibility after toggling between TabItems


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?


Solution

  • 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.