I'm working on a WPF app, where I have a datagrid with custom expander. The issue I'm encountering is when the user click on the + button, it changes to - and the content shows up, but when I scroll down and then scroll up, the icon changed back to + but the content stays the same. I got to know that it's because of the datagrid's virtualization. I can add EnableRowVirtualization="False"
to the DataGrid property but it slows down the performance so much that the users would definitely notice it (as the list might contain thousands of rows). Is there a way to work around this?
Here is my code:
<UserControl
x:Class="ProjectName.View.HomePage.OutstandingPatientDataGrid">
<UserControl.Resources>
// Other styles
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRow}">
<Border>
<SelectiveScrollingGrid>
<SelectiveScrollingGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</SelectiveScrollingGrid.ColumnDefinitions>
<SelectiveScrollingGrid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</SelectiveScrollingGrid.RowDefinitions>
<DataGridCellsPresenter
Grid.Column="1"
ItemsPanel="{TemplateBinding ItemsPanel}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<DataGridDetailsPresenter
Grid.Row="1"
Grid.Column="1"
SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Visibility="{TemplateBinding DetailsVisibility}" />
<DataGridRowHeader
Grid.RowSpan="2"
SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Row}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
</SelectiveScrollingGrid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="EnableRowVirtualization" Value="True" />
<Setter Property="EnableColumnVirtualization" Value="True" />
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Standard" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
</Style>
<ControlTemplate x:Key="SimpleExpanderButtonTemp" TargetType="{x:Type ToggleButton}">
<Border
x:Name="ExpanderButtonBorder"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="2" Fill="Transparent" />
<Ellipse
Name="Circle"
Grid.Column="0"
Width="34"
Height="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stroke="Transparent" />
<Path
x:Name="Sign"
Grid.Column="0"
Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0,5 H 10 M 5,0 V 10 Z"
RenderTransformOrigin="0.5,0.5"
Stroke="#ADB7C9"
StrokeThickness="2">
<Path.RenderTransform>
<RotateTransform Angle="0" />
</Path.RenderTransform>
</Path>
<ContentPresenter
x:Name="HeaderContent"
Grid.Column="1"
Margin="4,0,0,0"
ContentSource="Content" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<!-- Change the sign to minus when toggled -->
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Sign" Property="Data" Value="M 0,5 H 10 Z" />
</Trigger>
<!-- MouseOver, Pressed behaviours -->
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Sign" Property="Stroke" Value="{StaticResource primary-clr}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!-- Simple Expander Template -->
<ControlTemplate x:Key="SimpleExpanderTemp" TargetType="{x:Type Expander}">
<DockPanel>
<ToggleButton
x:Name="ExpanderButton"
Padding="1.5,0"
Content="{TemplateBinding Header}"
DockPanel.Dock="Top"
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
OverridesDefaultStyle="True"
Template="{StaticResource SimpleExpanderButtonTemp}" />
<ContentPresenter
x:Name="ExpanderContent"
DockPanel.Dock="Bottom"
Visibility="Collapsed" />
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="ExpanderContent" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid>
<DataGrid x:Name="outstandingPatientDataGrid" ItemsSource="{Binding}" >
<DataGrid.Columns>
<DataGridTemplateColumn Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Expander
Collapsed="Expander_Collapsed"
Expanded="Expander_Expanded"
Template="{StaticResource SimpleExpanderTemp}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="410" CanUserResize="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding Total, StringFormat={}{0:C}}" />
</Grid>
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
// ROW STYLES
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</UserControl>
This is how it looks like: Collapsed:
Expanded:
Code Behind:
using ProjectName.Classes;
using System.Windows;
using System.Windows.Controls;
namespace ProjectName.View.HomePage
{
public partial class OutstandingPatientDataGrid : UserControl
{
public OutstandingPatientDataGrid()
{
InitializeComponent();
}
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
DataGridRow row = TreeHelpers.FindParent<DataGridRow>(sender as Expander);
row.DetailsVisibility = Visibility.Visible;
}
private void Expander_Collapsed(object sender, RoutedEventArgs e)
{
DataGridRow row = TreeHelpers.FindParent<DataGridRow>(sender as Expander);
row.DetailsVisibility = Visibility.Collapsed;
}
}
}
So regarding the solution:
You have two event handlers that set the DetailsVisibility which covers the usecase that the expander togglebutton is toggled by the user. But the other way around is not present: when the DetailsVisibility is set (by the virtualizing system), the togglebutton.IsChecked is not set.
So instead of using eventhandlers, i would directly bind the IsExpanded to the DetailsVisibility of the row:
<Expander
IsExpanded="{Binding DetailsVisibility, RelativeSource={RelativeSource
AncestorType=DataGridRow}, Converter={StaticResource
VisibilityToBooleanConverter}, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Template="{StaticResource SimpleExpanderTemp}" />
The converter should be added to the resources of the UserControl:
<YourNamespace:VisibilityToBooleanConverter x:Key="VisibilityToBooleanConverter"/>
And the code of the converter would be:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace YourNamespace
{
public class VisibilityToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Visibility visibility = (Visibility)value;
return visibility == Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool booleanValue = (bool)value;
return booleanValue ? Visibility.Visible : Visibility.Collapsed;
}
}
}
But apart from that, it is strange that you use an Expander since you don't use the Header and Content of it. All you need is a ToggleButton, so i would replace the Expander by a simple ToggleButton and bind its IsChecked property like the way above.