I am working on an application that gets data from the Steam API. I am taking a huge performance hit to my ListView of games when applying a style to the ScrollViewer and ScrollBars.
When not using a style, it typically takes around 1.5 minutes to fully load all 86,000 games from the steam store. The memory usage stay right around 200mb.
Total Time: 1 minute and 23 second
Diagnostic Image without using a style
This is 1 minute and 40 seconds into importing while using a style. As you can see I only got 10,000 objects into the ListView while using the styling. If i am not using the styling this would by done by this time. Instead I am getting some kind of memory leak, at 50+ minutes in I was around 60,000 objects and around 1,800MB of Memory.
Total Time: 50+ minutes
Diagnostic Image while using a style
I am doing the work on a background thread and calling back to the UI thread to update the ObservableCollection.
Here is my background worker.
public void Import_Steam_Games_Worker_StartWork()
{
steamAppWorker.WorkerSupportsCancellation = false;
steamAppWorker.DoWork += Import_Steam_Games_Worker_DoWork;
steamAppWorker.RunWorkerCompleted += Import_Steam_Games_Worker_RunWorkerCompleted;
steamAppWorker.RunWorkerAsync();
Console.WriteLine("STARTUP", "Start import of all of steams games");
TextBoxSearchSteamGames.IsEnabled = false;
ButtonSearchSteamGames.IsEnabled = false;
}
private void Import_Steam_Games_Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("Finished importing all steam games from API");
TextBoxSearchSteamGames.IsEnabled = true;
ButtonSearchSteamGames.IsEnabled = true;
}
private void Import_Steam_Games_Worker_DoWork(object sender, DoWorkEventArgs e)
{
SteamApiClient client = new SteamApiClient();
SteamAppChunkResponse appChunk = new SteamAppChunkResponse();
//continue pulling chunks until more results = false;
bool moreResults = true;
int lastAppId = 0;
int count = 0;
while (moreResults)
{
//Pulls 10,000 results from the API
appChunk = client.GetSteamAppChunk(lastAppId);
//For each game it finds, add it to the steamGames Observable Collection
foreach (var app in appChunk.apps)
{
//counter to see how many games have been imported
count++;
//Dispatcher to call back to the UI thread
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate
{
steamGames.Add(app);
TextBlockSteamImportGameCount.Text = "Games Imported: " + count;
}));
}
//If response indicated there is no more results, break out of the while loop.
if (!appChunk.have_more_results)
{
moreResults = false;
}
//If the response indicates there is more results, then set the lastAppId to the last app returned from the previous call.
else
{
lastAppId = appChunk.last_appid;
}
}
}
Here I am binding the the name prop of the SteamAppChunk class to the ListViews Text.
<ListView x:Name="ListViewSteamGames" Foreground="White" FontSize="12" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Grid.RowSpan="9" Margin="10,0,10,10" Background="#131821" SelectionChanged="ListViewSteamGames_SelectionChanged" BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="#FFA2BADE" FontSize="15" Text="{Binding name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Objects derived from this class are added to the ObservableCollection();
public class SteamAppChunk
{
public int appid { get; set; }
public string name { get; set; }
public int last_modified { get; set; }
public int price_change_number { get; set; }
}
The only difference between the 2 images above is that While using styling this code applies, While not using styling I comment this block of code out. This resides in my App.xaml
<!--All the following is required to Re-Style the ScrollViewer, see
http://msdn2.microsoft.com/en-us/library/aa970847(VS.85).aspx
http://msdn2.microsoft.com/en-us/library/ms742173(VS.85).aspx
for the default Styles that the ScrollViewer has out of the box-->
<!--Brushes used in Styling of ScrollViewer-->
<SolidColorBrush x:Key="StandardBorderBrush" Color="#191d25" />
<SolidColorBrush x:Key="StandardBackgroundBrush" Color="#191d25" />
<SolidColorBrush x:Key="HoverBorderBrush" Color="#191d25" />
<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#191d25" />
<SolidColorBrush x:Key="SelectedForegroundBrush" Color="blue" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="blue" />
<SolidColorBrush x:Key="NormalBrush" Color="#191d25"/>
<SolidColorBrush x:Key="NormalBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="HorizontalNormalBrush" Color="#191d25" />
<SolidColorBrush x:Key="HorizontalNormalBorderBrush" Color="#191d25" />
<LinearGradientBrush x:Key="ListBoxBackgroundBrush"
StartPoint="0,0" EndPoint="1,0.001">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#191d25" Offset="0.0" />
<GradientStop Color="#191d25" Offset="0.6" />
<GradientStop Color="#191d25" Offset="1.2"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="StandardBrush"
StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#131821" Offset="0.0"/>
<GradientStop Color="#131821" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<SolidColorBrush x:Key="GlyphBrush" Color="#569dfb" />
<LinearGradientBrush x:Key="PressedBrush"
StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#131821" Offset="0.0"/>
<GradientStop Color="#131821" Offset="0.1"/>
<GradientStop Color="#131821" Offset="0.9"/>
<GradientStop Color="#131821" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<!--SrollViewer ScrollBar Repeat Buttons (at each end)-->
<Style x:Key="ScrollBarLineButton" TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border
Name="Border"
Margin="1"
CornerRadius="2"
Background="{StaticResource NormalBrush}"
BorderBrush="{StaticResource NormalBorderBrush}"
BorderThickness="1">
<Path
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{StaticResource GlyphBrush}"
Data="{Binding Path=Content,
RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource PressedBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--SrollViewer ScrollBar Repeat Buttons (The part in the middle,
not the thumb the long area between the buttons )-->
<Style x:Key="ScrollBarPageButton" TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="#191d25" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--ScrollViewer ScrollBar Thumb, that part that can be dragged
up/down or left/right Buttons-->
<Style x:Key="ScrollBarThumb" TargetType="{x:Type Thumb}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border
CornerRadius="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="VerticalScrollBar"
TargetType="{x:Type ScrollBar}">
<Grid >
<Grid.RowDefinitions>
<RowDefinition MaxHeight="18"/>
<RowDefinition Height="0.00001*"/>
<RowDefinition MaxHeight="18"/>
</Grid.RowDefinitions>
<Border
Grid.RowSpan="3"
CornerRadius="2"
Background="#191d25" />
<RepeatButton
Grid.Row="0"
Style="{StaticResource ScrollBarLineButton}"
Height="18"
Command="ScrollBar.LineUpCommand"
Content="M 0 4 L 8 4 L 4 0 Z" />
<Track
Name="PART_Track"
Grid.Row="1"
IsDirectionReversed="true">
<Track.DecreaseRepeatButton>
<RepeatButton
Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageUpCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb
Style="{StaticResource ScrollBarThumb}"
Margin="1,0,1,0"
Background="#569dfb"
BorderBrush="Transparent" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton
Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageDownCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton
Grid.Row="3"
Style="{StaticResource ScrollBarLineButton}"
Height="18"
Command="ScrollBar.LineDownCommand"
Content="M 0 0 L 4 4 L 8 0 Z"/>
</Grid>
</ControlTemplate>
<!--HorizontalScrollBar Template using the previously created Templates-->
<ControlTemplate x:Key="HorizontalScrollBar"
TargetType="{x:Type ScrollBar}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="18"/>
<ColumnDefinition Width="0.00001*"/>
<ColumnDefinition MaxWidth="18"/>
</Grid.ColumnDefinitions>
<Border
Grid.ColumnSpan="3"
CornerRadius="2"
Background="#191d25" />
<RepeatButton
Grid.Column="0"
Style="{StaticResource ScrollBarLineButton}"
Width="18"
Command="ScrollBar.LineLeftCommand"
Content="M 4 0 L 4 8 L 0 4 Z" />
<Track
Name="PART_Track"
Grid.Column="1"
IsDirectionReversed="False">
<Track.DecreaseRepeatButton>
<RepeatButton
Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageLeftCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb
Style="{StaticResource ScrollBarThumb}"
Margin="0,1,0,1"
Background="#569dfb"
BorderBrush="Transparent" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton
Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageRightCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton
Grid.Column="3"
Style="{StaticResource ScrollBarLineButton}"
Width="18"
Command="ScrollBar.LineRightCommand"
Content="M 0 0 L 4 4 L 0 8 Z"/>
</Grid>
</ControlTemplate>
<!--Style for overall ScrollBar-->
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Style.Triggers>
<Trigger Property="Orientation" Value="Horizontal">
<Setter Property="Width" Value="Auto"/>
<Setter Property="Height" Value="18" />
<Setter Property="Template"
Value="{StaticResource HorizontalScrollBar}" />
</Trigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="Width" Value="18"/>
<Setter Property="Height" Value="Auto" />
<Setter Property="Template"
Value="{StaticResource VerticalScrollBar}" />
</Trigger>
</Style.Triggers>
</Style>
<!--Style for ScrollViewer-->
<Style TargetType="{x:Type ScrollViewer}">
<Setter Property="OverridesDefaultStyle" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid Background="#33000000">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.Column="0"/>
<ScrollBar Name="PART_VerticalScrollBar"
Grid.Column="1"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
<ScrollBar Name="PART_HorizontalScrollBar"
Orientation="Horizontal"
Grid.Row="1"
Grid.Column="0"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
For some reason I am getting a huge performance hit when I enable this style for ScrollViewer and the ScrollBars.
What is the best way to go about pulling 80,000 entries and adding them to a list asynchronously?
I really do not want to get rid of the styling on the ScrollViewer/ScrollBars as the default scroll bars look hideous with my Skin.
Any help is appreciated, if you need more clarification on the problem please ask.
Editing the ControlTemplate can lead to virtualization being disabled. Setting those two Properties on the ScrollContentPresenter might fix your problem:
ScrollContentPresenter: CanContentScroll="True" VirtualizingPanel.IsVirtualizing="True"