I'm currently trying to create an ItemsControl (ListView) to scroll a list of strings (as a demo for a more complicated scenario). In doing so, I've noticed that when initially scrolling the list the memory usage of my application increases significantly.
Here is the code I am using for the XAML and the code-behind:
<ListView VirtualizingPanel.CacheLength="1" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" x:Name="rowsList" ScrollViewer.CanContentScroll="True" VirtualizingPanel.ScrollUnit="Item" VirtualizingPanel.CacheLengthUnit="Item" VirtualizingPanel.IsContainerVirtualizable="True">
<ListView.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Template>
<ControlTemplate>
<Border>
<ScrollViewer>
<ItemsPresenter VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.IsContainerVirtualizable="True"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</ListView.Template>
</ListView>
Code-behind:
public TestControl()
{
InitializeComponent();
List<string> list = new List<string>();
for (int i = 0; i < 10000; i++)
{
list.Add("testStringtestStringtestStringtestStringtestStringtestStringtestString");
}
rowsList.ItemsSource = list;
}
This is the memory profile from VS diagnostics tools:
Memory profile whilst scrolling
The memory usage is stable but when I start scrolling the list the memory use spikes up significantly. When scrolling back up the list, however, the memory usage doesn't seem to increase. I've tried a lot of ways to get round this but can't seem to rectify this simple issue.
Can anybody shed some light on this for me?
Thanks.
Related
I am using a StackPanel to layout several controls vertically (ie, Title, sub titles, listbox, separator, listbox, etc).
The StackPanel is a child of a ScrollViewer to ensure its content is always scrollable.
One of the controls in the StackPanel is a ListBox.
Its ItemsSource is data bound to a huge collection, and a complex DataTemplate is used to realise each item.
Unfortunately, I'm getting really poor performance (high cpu/memory) with it.
I tried
setting the ListBox's ItemsPanel to a VirtualizingStackPanel, and
overriding its ControlTemplate to only an ItemsPresenter (remove the ListBox's ScrollViewer).
But there were no difference in performances. I'm guessing the StackPanel gives its internal children infinite height during measure?
When I replaced the ScrollViewer and StackPanel with other panels/layouts (e.g, Grid, DockPanel) and the performance improves significantly, which leads me to believe the bottleneck, as well as solution, is in virtualization.
Is there any way for me to improve the cpu/memory performance of this view?
[Update 1]
Original Sample project: http://s000.tinyupload.com/index.php?file_id=29810707815310047536
[Update 2]
I tried restyling/templating TreeView/TreeViewItems to come up with the following example. It still takes a long time to start/same,high memory usage. But once loaded, scrolling feels a lot more responsive than the original sample.
Wonder if there's any other way to further improve the start up time/memory usage?
Restyled TreeView project: http://s000.tinyupload.com/index.php?file_id=00117351345725628185
[Update 2]
pushpraj's solution works like a charm
Original:
Startup: 35s,
Memory: 393MB
Scrolling: Slow
TreeView:
Startup: 18s,
Memory 377MB,
Scrolling: Fast
pushpraj's solution:
Startup: <1s,
Memory: 20MB,
Scrolling: Fast
you may perhaps limit the maximum size of the huge list box and enable Virtualization
eg
<ListBox MaxHeight="500"
VirtualizingPanel.IsVirtualizing="true"
VirtualizingPanel.VirtualizationMode="Recycling" />
this will enable the ListBox to load a few items only and will enable a scrollbar on listbox to scroll to rest of the items if needed.
at the same time setting VirtualizationMode to Recycling will help you to reuse the complex data templates thus eliminating the need of re creating them again for every item.
EDIT
here is a solution based on your sample, I have used CompositeCollection with Virtualization to achieve the desired.
xaml
<Grid xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:l="clr-namespace:PerfTest">
<Grid.Resources>
<DataTemplate DataType="{x:Type l:Permission}">
<StackPanel Orientation="Horizontal">
<CheckBox />
<TextBlock Text="{Binding Name}" />
<Button Content="+" />
<Button Content="-" />
<Button Content="..." />
</StackPanel>
</DataTemplate>
<CompositeCollection x:Key="data">
<!-- Content 1 -->
<TextBlock Text="Title"
FontSize="24"
FontWeight="Thin" />
<!-- Content 2 -->
<TextBlock Text="Subtitle"
FontSize="16"
FontWeight="Thin" />
<!-- Content 3 -->
<CollectionContainer Collection="{Binding DataContext, Source={x:Reference listbox}}" />
<!-- Content 4 -->
<TextBlock Text="User must scroll past the entire list box before seeing this"
FontSize="16"
FontWeight="Thin"
Padding="5"
TextWrapping="Wrap"
Background="#99000000"
Foreground="White" />
</CompositeCollection>
</Grid.Resources>
<ListBox x:Name="listbox"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{StaticResource data}" />
</Grid>
code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var items = new ObservableCollection<Permission>();
foreach (var i in Enumerable.Range(0, 10000).Select(i => new Permission() { Name = "Permission " + i }))
{ items.Add(i); }
DataContext = items;
}
}
public class Permission
{
public string Name { get; set; }
}
since we can not create data template for string so I changed the string collection to Permission collection. I hope in your real project it would be something similar.
give this a try and see if this is close to what you need.
note: you may safely ignore if there is any designer warning on Collection="{Binding DataContext, Source={x:Reference listbox}}"
Is there some kind of "best practices" manual for creating proper GUI for kiosk touch screens? These applications need to have consistent look and feel across different screen resolutions and more importantly screen ratios (since everything is rendered as vectors so screen resolution and DPI shouldn't be an issue with WPF).
Take for example this screenshot where I tried to create simple keyboard for touch screens. I've used UniformGrid so that each button gets cell of equal size:
Here is the code for this:
<TabItem Header="Test 1">
<ItemsControl ItemsSource="{Binding KeyboardItems}" SnapsToDevicePixels="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Margin="5">
<Button Content="{Binding}"></Button>
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</TabItem>
Notice that all buttons are sized to content which makes them non-stretchable so each button has its own size... This is how Viewbox scales its content and of course this kind of GUI is out of question. This is not the keyboard I want to use on some kiosk application, so the next better version is following:
<TabItem Header="Test 2">
<ItemsControl ItemsSource="{Binding KeyboardItems}" SnapsToDevicePixels="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="5">
<Viewbox>
<TextBlock Text="{Binding}" />
</Viewbox>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</TabItem>
Now this is a bit better as Viewbox is now wrapping the content rather than the whole button. Notice however that because we are now wrapping the button's content rather the whole button the button's border is now not scaled. I want this to be scaled too, not just the content. In the first example we had this but the overall look of the GUI was horrible.
Also notice that in this version I've set Margin to the Button and in the first version on the Viewbox. This means that in the first version margin will scale too (I want that!) while in the second version it will be constant for any screen size. So for really big screens the white space between buttons will become relatively smaller though they are absolutely of constant size (not what I want!).
Here is the code for generating keyboard buttons:
public partial class MainWindow : Window
{
public List<string> KeyboardItems { get; set; }
public MainWindow()
{
KeyboardItems = new List<string>();
for (char c = 'A'; c <= 'Z'; c++)
{
KeyboardItems.Add(c.ToString());
}
KeyboardItems.Add("Space");
DataContext = this;
InitializeComponent();
}
}
Problems like this are all around development of WPF touch screen kiosks so I'd like to hear some ideas and solutions you came about while dealing with scaling issues.
You didn't show us Test 3, which I thought might be this:
<TabItem Header="Test 3">
<Viewbox>
<ItemsControl ItemsSource="{Binding KeyboardItems}" SnapsToDevicePixels="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="5">
<TextBlock Text="{Binding}" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
</TabItem>
Does that have the desired effect?
I have a user control with the following XAML:
<ScrollViewer>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<RichTextBox>
<Paragraph>
<Run Text="{Binding}"/>
</Paragraph>
</RichTextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
And code behind:
public partial class MainPage {
public MainPage() {
InitializeComponent();
Items = new ObservableCollection<string>(Enumerable.Range(0, 100).Select(x => "some text"));
DataContext = this;
}
public ObservableCollection<string> Items { get; set; }
}
When this code runs, the vertical scroll bar for the ScrollViewer goes down to the bottom. However, if I remove the binding in the Run in the RichTextBox and hard-code the text:
<Run Text="some text"/>
Now the scroll bar stays at the top (as I would expect).
Is this a bug? If not, what is going on? How can I fix this (note: this is simplified XAML, I need the ScrollViewer because the ListBox is actually in a grid)?
I canĀ“t tell you why the ScrollViewer behaves like that, but i would change the XAML to the following. Then is the scroller at the top, if you use binding or not in the DataTemplate.
XAML:
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<RichTextBox>
<Paragraph>
<Run Text="{Binding}"/>
</Paragraph>
</RichTextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Make it Fixed width and Height of scrollviewer depends upon the grid row and column size. it help to fixed size in run time. like that
<ScrollViewer VerticalScrollBarVisibility="Auto" VerticalAlignment="Top" HorizontalScrollBarVisibility="Auto" Width="135" Height="463">
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left" Orientation="Vertical">
<RichTextBox>
<Paragraph>
<Run Text="{Binding}"/>
</Paragraph>
</RichTextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I hope its helpful
try setting the listbox's height to auto and make the scrollviewer height fixed. that way, the scrollbars only show when the listbox's height is greater than the scrollviewer's height.
But, looking at the way the objects are defined. You are gonna have one big problem in the future. That is, in SL4, Listboxes take up height and don't give it back. So if you have something that expands inside the listbox (i.e. Accordion items) or allow deleting inside the listbox, the listbox would expand to show all it's items. But once an item is deleted, it will never give back the height. The result would be your scrollbar always shows even when you have nothing more to show at the bottom.
That is completely out of topic but I felt that I should let you know.
I hope I helped, if not now, then for the future.
I finally came up with a solution to this problem. I removed the ScrollViewer from the RichTextBox template.
Set the MaxHeight on the Listbox, this will allow the scrollviewer to only show up when the screen dimensions are too small.
Thank you so much! You just saved me days of pain and suffering... :)
For those (like me) who wonder how to remove the scrollviewer from the rtb template :
Extract the template with blend.
Find the scrollviewer element and replace it with a stackpanel (keep the x:name attribute).
I'm trying to create a scrolling list of fairly large textblocks. I want there to be a vertical scrollbar to show them all, and if they overflow a certain size I want them to display an ellipsis. I actually have all this working pretty good.
I have the following Silverlight XAML:
<Grid x:Name="LayoutRoot" MaxWidth="500" MinWidth="100"
MaxHeight="500" MinHeight="100">
<Grid.DataContext>
<app:MainPageViewModel/>
</Grid.DataContext>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding TextItems}" Margin="0,20,0,20">
<ItemsControl.ItemTemplate><DataTemplate>
<Border MaxHeight="175" Margin="0,0,0,18" CornerRadius="5">
<TextBlock Margin="2" TextTrimming="WordEllipsis"
TextWrapping="Wrap" Text="{Binding}"/>
</Border>
</DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
My problem is that this layout does not use UI virtualization, such as with a VirtualizingStackPanel. So it is pretty slow. What is the best way to get UI virtualization into this layout? I've tried about a half dozen different ways and nothing has worked out all that well.
I managed to get this working in a ListBox because it seems to support virtualization out of the box. However, I'd prefer to use ItemsControl as I don't want these things to be selectable, and I don't want the styling that comes along with a ListBox.
This in Silverlight 4.
There are a few things you need to do to make this work.
Set the ItemsPanelTemplate for
your ItemsControl to a
VirtualizingStackPanel.
Incorporate the ScrollViewer inside
a ControlTemplate for your
ItemsControl instead of just
wrapping it around the outside.
Make sure the ItemsControl has a fixed height so the layout system can work out how many items it needs to fill the viewport. (It looks like you are already doing this by putting the ItemsControl in a Grid - that will allow the layout system to determine the alloted height for the control)
Here's the simplest example I could come up with of this working:
<ItemsControl ItemsSource="{Binding TextItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Border>
<ScrollViewer>
<ItemsPresenter/>
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
I have a ListBox in a grid that gets databound when the page loads... pretty straightforward. The problem I'm having is that, after the box is databound, I can scroll... but not all the way to the bottom of the list. It stops an item or two short and won't let me scroll anymore. Here's the listbox XAML:
<Grid x:Name="ContentGrid" Grid.Row="2">
<ListBox x:Name="lbFeed" ItemsSource="{Binding Items}" SelectionChanged="lbFeed_SelectionChanged" VerticalAlignment="Top" Width="480">
<ListBox.ItemTemplate>
<DataTemplate x:Key="MyDataTemplate">
<StackPanel Orientation="Vertical" Width="430">
<TextBlock Text="Some text" TextAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I can post more code, but I'm still getting my feet wet in XAML and I'm not sure what might be causing this. If you need more context, let me know and I'll add it here.
This is a known issue at this stage of ctp release if you happen to have rows that are not fixed height. If this is the case you will likely notice your scrolling is a bit jittery too. Fix the height of your content for now if this is the case for your app and all is resolved.