Проблемы С производительностью рендеринга ячеек WPF Datagrid

#c# #wpf #datagrid

Вопрос:

Я сталкиваюсь с проблемами, когда в моем приложении WPF есть сетка данных, которая показывает около 200 ячеек (10 столбцов, 20-40 строк в зависимости от размера окна). Обновление представления (например, с помощью фильтров) происходит медленно (200-400 мс), когда я ожидал бы, что оно будет значительно быстрее.

Виртуализация данных включена, и если я изменю размер окна так, чтобы были видны только 2-3 строки, фильтры мгновенно обновятся. Это наводит меня на мысль, что производительность виртуализации и сбора данных — не моя проблема.

Я запустил профилировщик, и похоже, что разрыв каждой ячейки происходит очень медленно. Даже удаление моих пользовательских шаблонов не снижает его до очень разумного уровня. Для визуализации каждой ячейки требуется 5-9 мс, поэтому она складывается.

Везде в приложении текстовые блоки отображаются примерно за 0,0015 мс или около того. но в сетке данных они составляют 0,15 мс каждый, плюс различные другие накладные расходы. Есть ли какие-либо дополнительные опции, которые я могу использовать для повышения производительности при рендеринге менее 60 мс?

введите описание изображения здесь

Мой объект datagrid:

 <DataGrid
    VirtualizingStackPanel.VirtualizationMode="Recycling"   
    VirtualizingStackPanel.IsVirtualizing="true"
    HeadersVisibility="Column" 
    BorderThickness="0 1 0 0" 
    IsReadOnly="True" 
    GridLinesVisibility="Horizontal" 
    HorizontalGridLinesBrush="#1e1e1e" 
    CanUserResizeColumns="False" 
    CanUserResizeRows="False"
    CanUserReorderColumns="False"
    EnableColumnVirtualization="True"
    EnableRowVirtualization="True"
    Margin="0 6 0 0" 
    Name="dgCombatLog"
    Foreground="#ffffff"
    Background="#252526"
    RowBackground="#252526"
    AlternatingRowBackground="#2a2a2a" 
    AlternationCount="2" 
    AutoGenerateColumns="False"
    MaxWidth="2560"
    MaxHeight="1600"
    DataGrid.RowHeight="24"
    ScrollViewer.HorizontalScrollBarVisibility="Disabled"
    ScrollViewer.VerticalScrollBarVisibility="Visible"
    RowHeaderWidth="0"
    Grid.Row="2">
    <DataGrid.Resources>
        <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#007acc"/>
        <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#007acc"/>
        <Style TargetType="{x:Type DataGridColumnHeader}">
            <Setter Property="Background"  Value="#000000" />
            <Setter Property="Padding"  Value="2 2 2 2" />
        </Style>
        <Style TargetType="{x:Type DataGridCell}">
            <Setter Property="Foreground" Value="#eeeeee"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="Padding" Value="2 0 2 0"/>
            <Setter Property="Height" Value="24"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type DataGridCell}">
                        <Border Height="{TemplateBinding Height}" Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" VerticalAlignment="Center">
                            <ContentPresenter VerticalAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding ActionName}" Value="AbilityActivate" />
                        <Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected}" Value="False" />
                    </MultiDataTrigger.Conditions>
                    <Setter Property="Background" Value="#002036" />
                </MultiDataTrigger>
                <DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Value="True">
                    <Setter Property="Background" Value="#1c97ea" />
                </DataTrigger>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Self}, Path=Foreground}" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </DataGrid.Resources>

    <DataGrid.Columns>
        <DataGridTextColumn Width="59" Header="@" Binding="{Binding RelativeTimestamp}" />
        <DataGridTextColumn Width="166" Header="Source" Binding="{Binding Source.Name}">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Source.IsFriendly}" Value="True">
                            <Setter Property="Background" Value="#006a03"/>
                            <Setter Property="Foreground" Value="#ffffff"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Source.IsEnemy}" Value="True">
                            <Setter Property="Background" Value="#581e26"/>
                            <Setter Property="Foreground" Value="#ffffff"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTemplateColumn Width="19" Header="A">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Image Width="14" Height="14" Source="{Binding ActionIcon}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTextColumn Width="166" Header="Target" Binding="{Binding Target.Name}">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Target.IsFriendly}" Value="True">
                            <Setter Property="Background" Value="#006a03"/>
                            <Setter Property="Foreground" Value="#ffffff"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Target.IsEnemy}" Value="True">
                            <Setter Property="Background" Value="#581e26"/>
                            <Setter Property="Foreground" Value="#ffffff"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTemplateColumn Width="*" Header="Ability" SortMemberPath="AbilityDisplay" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <StackPanel.Resources>
                            <Style TargetType="{x:Type Image}">
                                <Setter Property="Visibility" Value="Collapsed"/>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding Path=HasAbilityIcon}" Value="True">
                                        <Setter Property="Visibility" Value="Visible"/>
                                        <Setter Property="Margin" Value="0 0 5 0"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </StackPanel.Resources>

                        <Image Width="18" Height="18" Source="{Binding AbilityIcon}" />
                        <TextBlock Text="{Binding AbilityDisplay}" />
                    </StackPanel>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTextColumn Width="49" Header="Value" Binding="{Binding ActualValue, StringFormat={}{0:N0}}">
            <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Setter Property="HorizontalAlignment" Value="Right" />
                    <Setter Property="Foreground" Value="#daa574"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ActionName}" Value="Heal">
                            <Setter Property="Foreground" Value="#32cd32"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding ActionName}" Value="Damage">
                            <Setter Property="Foreground" Value="#db001e"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Type}" Value="Spend">
                            <Setter Property="Foreground" Value="#222222"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding HasNumericValues}" Value="False">
                            <Setter Property="Foreground" Value="Transparent"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Width="49" Header="Abs" Binding="{Binding AbsValue, StringFormat={}{0:N0}}">
            <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Setter Property="HorizontalAlignment" Value="Right" />
                    <Setter Property="Foreground" Value="#30cccd"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding AbsValue}" Value="0">
                            <Setter Property="Foreground" Value="Transparent"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Width="49" Header="Over" Binding="{Binding OverValue, StringFormat={}{0:N0}}">
            <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Setter Property="HorizontalAlignment" Value="Right" />
                    <Setter Property="Foreground" Value="Transparent"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ActionName}" Value="Heal">
                            <Setter Property="Foreground" Value="#8fbb8f"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Width="49" Header="Threat" Binding="{Binding Threat, StringFormat={}{0:N0}}">
            <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Setter Property="HorizontalAlignment" Value="Right" />
                    <Setter Property="Foreground" Value="#daa574"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Threat}" Value="0">
                            <Setter Property="Foreground" Value="Transparent"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Width="84" Header="Effects" />
    </DataGrid.Columns>
</DataGrid>
 

Комментарии:

1. Много триггеров. Вероятно, причина в этом. каждая ячейка постоянно контролируется.

2. @lidqy есть ли другой способ выполнить условные стили ячеек? Данные не являются динамическими. В коллекцию могут быть добавлены новые события, но существующие записи не изменяются

3. Не хотел беспокоиться, триггеры добавляют прослушиватели/обработчики событий и привязки, и у вас их довольно много для каждой ячейки. Если настройки стиля не изменятся после загрузки, вы также можете использовать селекторы стилей для ячеек (я бы подумал, что это немного быстрее). Кроме того, некоторые настройки (наведение курсора мыши/выбор) могут быть обработаны VisualStateManager, хотя я не знаю, повысит ли это производительность… Вы даже можете поместить кисти и другие элементы пользовательского интерфейса в виртуальную машину, хотя это плохой стиль. Но это, скорее всего, немного уменьшит задержку загрузки…

4. Может быть, вы уже пробовали это, но если вы прокомментируете как можно больше чисто эстетических триггеров, улучшит ли это ситуацию? Я согласен с @lidqy, что это много триггеров, но на современном оборудовании я бы не ожидал, что это будет проблемой.

5. Также вы пробовали работать без подключения отладчика? Я обнаружил, что это имеет огромное значение, особенно если у меня много ошибок привязки или что-то еще, действительно заполняющее консоль.