Hiding and Expanding Row Details in WPF

by MikeHogg 18. November 2010 19:03

We had a normal datagrid, with lots of data.  Each of the rows was a parent to some list of other objects, and the users wanted to be able to expand a row to see the children detail rows. Nested Datagrids, a popular or usual feature request. The Toolkit DataGrid has a RowDetails object, but it is visible all the time.  Here’s how I set it up to have only one exclusive RowDetails visible at a time in WPF:

ViewModel has a property called IsSelected, and in addition to calling OnPropertyChanged event for itself, it calls OnPropertyChanged for another property, DetailsVisibility.  The Model Binder will do a Get on DetailsVisibility, and so we just need to put the logic there…

    public class MatchGroupVM : ViewModelBase
    { 
        Models.MatchGroup _matchgroup;
        bool _etiselected;
        public decimal Match_Group_Id { get { return _matchgroup.Match_Group_Id; } }
        public string Product { get { return _matchgroup.Product; } }
        public DateTime Contract_Month { get { return _matchgroup.Contract_Month; } }
        public decimal? Lot_Size { get{ return _matchgroup.Lot_Size; } }
        <snip>
        public decimal? Price_Diff { get { return _matchgroup.Price_Diff; } }
        public decimal? Float_Diff { get { return _matchgroup.Float_Diff; } }
        public List<Models.MatchDetail> Details { get{ return _matchgroup.Details; } }
        public string Single_ETI { get{ return _matchgroup.Single_ETI; } }
        public string Single_Goldman_Id { get{ return _matchgroup.Single_Goldman_Id; } }
        public bool ETISelected
        {
            get
            {
                return _etiselected;
            }
            set
            {
                if (_etiselected != value)
                {
                    _etiselected = value;
                    OnPropertyChanged("ETISelected");
                    OnPropertyChanged("DetailsVisibility");
                }
            }
        }
        
        
        public System.Windows.Visibility DetailsVisibility
        {
            get
            {
                return Details.Count > 1 == true && ETISelected == true ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
            }
            set { var bindingdummysetter = value; }
        } 
        public MatchGroupVM(Models.MatchGroup mg){
            _matchgroup = mg;
        }
    }

 

Note in this case we are not writing our own Getters and Setters in our VM, but simply exposing the composition model.  That is because this VM is a readonly report.  Similarly, the Model Binder doesn’t ever need to Set DetailsVisibility, but by calling OnPropertyChanged the Getter is called, so this one line handles both Showing a Details grid and Hiding a Details Grid.  A parent VM handles the populating of this VM and its Details.

The View will have the nested grid in the RowDetailsTemplate, just like any other, but it binds its DetailsVisibility to the Model.  Note that the nested DetailsVisibility needs to be set to Collapsed to fix the cascading style of WPF.  And we add some eye candy to a row’s colors when it becomes selected. 

<tk:DataGrid Name="GridFloatPrices" ItemsSource="{Binding MatchGroups}"  Grid.Row="1"
                         RowHeaderWidth="1" IsReadOnly="True" AutoGenerateColumns="False"
                         AlternatingRowBackground="Honeydew" CanUserAddRows="False"   
                         SelectionUnit="Cell" ScrollViewer.CanContentScroll="False"
                          >
                
                <tk:DataGrid.Resources>
                    <Style  TargetType="{x:Type tk:DataGridCell}" >
                        <Style.Triggers>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Background" Value="LightSkyBlue"/>
                                <Setter Property="Foreground" Value="Black"/>
                                <Setter Property="BorderBrush" Value="LightSkyBlue"/> 
                            </Trigger>
                        </Style.Triggers>
                    </Style> 
                    <Style TargetType="tk:DataGridRow"> 
                        <Setter Property="DetailsVisibility" Value="{Binding DetailsVisibility}" />                          
                    </Style>
                    <Style TargetType="{x:Type tkp:DataGridColumnHeader}">
                        <Setter Property="ContentTemplate">
                            <Setter.Value>
                                <DataTemplate>
                                    <TextBlock TextWrapping="Wrap" Text="{Binding}"></TextBlock>
                                </DataTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </tk:DataGrid.Resources>    
                 
                <tk:DataGrid.RowDetailsTemplate>
                    <DataTemplate>
                        <Grid Background="LightGray" HorizontalAlignment="Stretch" ScrollViewer.CanContentScroll="False"> 
                        <tk:DataGrid ItemsSource="{Binding Details}" RowBackground="LightYellow"
                                     Margin="749,0,0,0" IsReadOnly="True" CanUserAddRows="False"
                                     HeadersVisibility="None" AutoGenerateColumns="False" ScrollViewer.ScrollChanged="OnMouseWheel"
                                     Width="1450" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
                                     HorizontalAlignment="Left">
                                <tk:DataGrid.Resources>
                                    <Style TargetType="tk:DataGridRow">
                                        <Setter Property="DetailsVisibility" Value="Collapsed"/><!-- to turn off cascading style from master grid -->
                                    </Style> 
                                </tk:DataGrid.Resources>
                                
                                <tk:DataGrid.Columns>
                               <snip>
                                        <tk:DataGridTextColumn Binding="{Binding CER_Check}" Width="100"></tk:DataGridTextColumn>
                                        <tk:DataGridTextColumn Binding="{Binding CER_Diff}" Width="100"></tk:DataGridTextColumn>
                                        
                                        <tk:DataGridTextColumn Binding="{Binding GS_Settled_Value}" Width="100"></tk:DataGridTextColumn>
                                        <tk:DataGridTextColumn Binding="{Binding MV_Diff_Value}" Width="100"></tk:DataGridTextColumn>
                                        <tk:DataGridTextColumn Binding="{Binding GS_Settled_Total}" Width="100"></tk:DataGridTextColumn>
                                        <tk:DataGridTextColumn Binding="{Binding GS_Check}" Width="100"></tk:DataGridTextColumn>
                                        <tk:DataGridTextColumn Binding="{Binding GS_Diff}" Width="100"></tk:DataGridTextColumn>
                                        
                                        <tk:DataGridTextColumn Binding="{Binding Settled_Diff}" Width="100"></tk:DataGridTextColumn>
                                </tk:DataGrid.Columns>
                            </tk:DataGrid>  
                            </Grid>
                    </DataTemplate>
                </tk:DataGrid.RowDetailsTemplate>
 
                <tk:DataGrid.Columns>
                    <tk:DataGridTextColumn Binding="{Binding Product}" Header="Product" Width="100"></tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding Contract_Month, StringFormat=MMM-yyyy}" Header="Contract Month" Width="100"></tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding Match_Group_Id}" Header="Match Group Id" Width="100"></tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding CER_Price}" Header="CER Price" Width="75"></tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding Lot_Size}" Header="Lot Size" Width="75"></tk:DataGridTextColumn>
                    
                    <tk:DataGridTextColumn Binding="{Binding Single_ETI}" Header="ETI" Width="100">
                        <tk:DataGridTextColumn.CellStyle>
                            <Style TargetType="tk:DataGridCell">
                                <Setter Property="IsSelected" Value="{Binding ETISelected, Mode=OneWayToSource}"/>
                                <Style.Triggers>
                                    <Trigger Property="IsSelected" Value="True">
                                        <Setter Property="Background" Value="LightSkyBlue"/>
                                        <Setter Property="Foreground" Value="Black"/>
                                        <Setter Property="BorderBrush" Value="LightSkyBlue"/>
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </tk:DataGridTextColumn.CellStyle>
                    </tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding Single_Goldman_Id}" Header="Goldman Id" Width="100"></tk:DataGridTextColumn>                   
                    <tk:DataGridTextColumn Binding="{Binding Total_CER_Lots}" Header="Tot CER Lots" Width="75"></tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding Total_GS_Lots}" Header="Tot GS Lots" Width="75"></tk:DataGridTextColumn>
                <snip>               
                    <tk:DataGridTextColumn Binding="{Binding Settled_Diff}" Header="Settled Diff" Width="100"></tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding Qty_Diff}" Header="Qty Diff" Width="100"></tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding Price_Diff}" Header="Price Diff" Width="100"></tk:DataGridTextColumn>
                    <tk:DataGridTextColumn Binding="{Binding Float_Diff}" Header="Float Diff" Width="100"></tk:DataGridTextColumn>
                </tk:DataGrid.Columns>
            </tk:DataGrid> 
and that’s it.

Tags:

WPF

About Mike Hogg

Mike Hogg is a c# developer in Brooklyn.

More Here

Favorite Books

This book had the most influence on my coding style. It drastically changed the way I write code and turned me on to test driven development even if I don't always use it. It made me write clearer, functional-style code using more principles such as DRY, encapsulation, single responsibility, and more. amazon.com

This book opened my eyes to a methodical and systematic approach to upgrading legacy codebases step by step. Incrementally transforming code blocks into testable code before making improvements. amazon.com

More Here