In Wpf (4.0) my listbox (using a VirtualizingStackPanel) contains 500 items. Each item is of a custom Type
class Page : FrameworkElement
...
protected override void OnRender(DrawingContext dc)
{
// Drawing 1000 single characters to different positions
//(formattedText is a static member which is only instantiated once and contains the string "A" or "B"...)
for (int i = 0; i < 1000; i++)
dc.DrawText(formattedText, new Point(....))
// Drawing 1000 ellipses: very fast and low ram usage
for (int i = 0; i < 1000; i++)
dc.DrawEllipse(Brushes.Black, null, new Point(....),10,10)
}
Now when moving the scrollbar of the listbox back and forth so that every item’s visual is created at least once the ram usage goes up to 500 Mb after a while and then – after a while – goes back to ca 250 Mb but stays on this level. Memory leak ? I thought the advantage of a VirtualizingStackPanel is that visuals which are not needed/visible get disposed…
Anyway, this extreme ram usage only appears when drawing text using “DrawText”. Drawing other objects like “DrawEllipse” does not consume so much memory.
Is there a more efficient way to draw many text items than using Drawing.Context’s “DrawText” ?
Here is the complete sample (just create a new Wpf Application project and replace the window1 code): (I know there are FlowDocument and FixedDocument but they are no alternative)
Xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="900" Width="800">
<Grid Background="Black">
<ListBox Name="lb" ScrollViewer.CanContentScroll="True" Background="Black">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
</Window>
And the Window1.xaml.cs:
public partial class Window1 : Window
{
readonly ObservableCollection<FrameworkElement> collection = new ObservableCollection<FrameworkElement>();
public Window1()
{
InitializeComponent();
for (int i = 0; i < 500; i++)
{
collection.Add(new Page(){ Width = 500, Height = 800 });
}
lb.ItemsSource = collection;
}
}
public class Page : FrameworkElement
{
static FormattedText formattedText = new FormattedText("A", CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface(new FontFamily("Arial").ToString()),
12,Brushes.Black);
protected override void OnRender(DrawingContext dc)
{
dc.DrawRectangle(Brushes.White, null, new Rect(0, 0, Width, Height));
double yOff = 0;
for (int i = 0; i < 1000; i++) // draw 1000 "A"s
{
dc.DrawText(formattedText, new Point((i % 80) * 5, yOff ));
if (i % 80 == 0) yOff += 10;
}
}
}
While this isn’t entirely useful to you, my experience with VirtualizingStackPanel isn’t that it disposes of objects not in view, but that it allows objects not in view to be disposed to recover memory when the application needs more memory, which should result in your memory usage ballooning when there is memory available.
Is it possible that dc.DrawText is firing BuildGeometry() for each formattedText object, and that you can bring that outside the loop? I don’t know how much work BuildGeometry is, but it’s possible that the DrawingContext is only capable of accepting geometry, and the BuildGeometry call is being called unnecessarily 999 times in your sample. Have a look at:
http://msdn.microsoft.com/en-us/library/system.windows.media.formattedtext.aspx
to see whether there are any other optimizations you can make.
Can you output some memory profile data and some timing data within your loops to give a sense of whether it’s slowing down, or the memory is increasing in a non-linear fashion during the loop?