Silverlight combobox performance issue - silverlight

I'm facing a performance issue with a crowded combobox (5000 items). Rendering of the drop down list is really slow (as if it was computing all items before showing any).
Do you have any trick to make this dropdown display lazy?
Xaml code:
<Grid x:Name="LayoutRoot">
<StackPanel Orientation="Horizontal" Width="200" Height="20">
<TextBlock>Test Combo </TextBlock>
<ComboBox x:Name="fooCombo" Margin="5,0,0,0"></ComboBox>
</StackPanel>
</Grid>
code behind:
public MainPage()
{
InitializeComponent();
List<string> li = new List<string>();
int Max = 5000;
for (int i = 0; i < Max; ++i)
li.Add("Item - " + i);
fooCombo.ItemsSource = li;
}
Well, there seems to be a bug in the Combobox UI virtualization, so an autocompletebox should be the way to go.

If you want an actual ComboBox (and not an AutoCompleteBox) that did this you could replace the ItemsTemplate with a VirtualizingStackPanel. In your example this would look like:
<ComboBox x:Name="fooCombo" Margin="5,0,0,0">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel></VirtualizingStackPanel>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
As a guide though, I'd probably review your usage scenario to see whether or not a ComboBox is the correct control for you - since 5000 items seems like a mighty lot for a drop down list.
By the way, the slowness is expected behavior in Silverlight and not a bug.

Use the AutoCompleteBox instead, adjust the number of characters that need to be entered before a drop down list will be filled to limit how many drop down items are needed at any one time.

Related

ItemsControl rendering slowly

I am using an ItemsControl for showing alist of items and itrs xaml is like
<ItemsControl ItemsSource="{Binding ShelfItemsCollection}" Name="shelfGridView" Margin="5" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Stackpanel>
<Image Width="150" Height="200" Stretch="Fill" Source="{Binding CoverImage}" ></Image>
+
some other infos
</Stackpanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
The problem i am facing is i had almost 100 items in list and i am doing some filtering operations on the list based on some properties and limit the results to a lesser no ( say 20 items at a time)for doing this filtering it took a lot of time to refresh and load the list view .
This is the code i used for filtering
ICollectionView dataView = CollectionViewSource.GetDefaultView(shelfGridView.ItemsSource);
dataView.Filter = CloudFilter;
dataView.Refresh();
private bool CloudFilter(object item)
{
MyObject lib = item as MyObject;
return lib.Property !=valuetofilter;
}
Is any way to improve the perfomance or any specific reason for slow rendering ?
ItemsControl doesn't support UI virtualization out of the box. Either use ListBox or make ItemsControl UI virtualized.
You can enable UI virtualization on ItemsControl by following some steps :
Set VirtualizingStackPanel.IsVirtualizing="True" on ItemsControl.
Set ItemsPanel to be VirtualizingStackPanel.
Override ControlTemplate of ItemsControl and wrap ItemsPresenter inside ScrollViewer.
Set ScrollViewer.CanContentScroll="True" on ItemsControl.
Details for the above proposal can be found here.
Moreover, you are setting ItemsSource directly to SourceCollection i.e. ShelfItemsCollection and later filtering it by getting defualtView created underneath for that collection. Binding directly with sourceCollection will force ItemsControl(non-Virtualized ofcourse) to generate 100 containers to host your underlying objects.
Instead you should create ICollectionView with filter predicate set on it and bind ItemsSource to that instance. May be you can also create CollectionViewSource and bind with it. If you bind with filtered instance, it will generate only 20 containers (non-virtualized ItemsControl).
Ofcourse, enabling UI virtualization on ItemsControl, will generate containers for only visible UI items on GUI.

How to build a grid of controls in XAML?

I am trying to build a UI in WPF to a specification. The UI is for editing a collection of items. Each item has an editable string property, and also a variable number of read-only strings which the UI needs to display. It might look something like this:
or, depending on data, might have a different number of text label columns:
The number of text columns is completely variable and can vary from one to "lots". The specification calls for the columns to be sized to fit the longest entry (they are invariably very short), and the whole thing should look like a grid. This grid will be contained in a window, stretching the text box horizontally to fit the window.
Importantly, the text boxes can contain multi-line text and will grow automatically to fit the text. The rows below need to be pushed out of the way if that happens.
Question: what would be a good way of doing this in WPF?
Coming from a WinForms background, I am thinking of a TableLayoutPanel, which gets populated directly by code I write. However, I need to do this in WPF. While I could still just get myself a Grid and populate it in code, I would really rather prefer a way that's more in line with how things are done in WPF: namely, define a ViewModel, populate it, and then describe the View entirely in XAML. However, I can't think of a way of describing such a view in XAML.
The closest I can get to this using MVVM and XAML is to use an ItemsControl with one item per row, and use a data template which, in turn, uses another ItemsControl (stacked horizontally this time) for the variable number of labels, followed by the text box. Unfortunately, this can't be made to align vertically in a grid pattern like the spec requires.
This does not map all too well, you could probably use a DataGrid and retemplate it to look like this. In other approaches you may need to imperatively add columns or the like to get the layout done right.
(You can hook into AutoGeneratingColumn to set the width of that one writeable column to *)
You can create your own Panel and then decide on how you want the layout logic to work for the children that are put inside it.
Look at this for inspiration:
http://www.nbdtech.com/Blog/archive/2010/07/27/easy-form-layout-in-wpf-part-1-ndash-introducing-formpanel.aspx
You could have a "ColumnCount" property, and then use that within the MeassureOverride and ArrangeOverride to decide when to wrap a child.
Or you could modify this bit of code (I know it's Silverlight code, but it should be close to the same in WPF).
http://blogs.planetsoftware.com.au/paul/archive/2010/04/30/autogrid-ndash-part-1.aspx
Instead of having the same width for all columns (the default is 1-star "*"), you could add a List/Collection property that records the different column widths sized you want, then in the AutoGrid_LayoutUpdated use those widths to make the ColumnDefinition values.
You've asked for quite a bit, the following code shows how to build a grid with the controls you want that sizes as needed, along with setting up the bindings:
public void BuildListTemplate(IEnumerable<Class1> myData, int numLabelCols)
{
var myGrid = new Grid();
for (int i = 0; i < myData.Count(); i++)
{
myGrid.RowDefinitions.Add(new RowDefinition() { Height= new GridLength(0, GridUnitType.Auto)});
}
for (int i = 0; i < numLabelCols; i++)
{
myGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Auto) });
}
myGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
for (int i = 0; i < myData.Count(); i++)
{
for (int j = 0; j < numLabelCols; j++)
{
var tb = new TextBlock();
tb.SetBinding(TextBlock.TextProperty, new Binding("[" + i + "].labels[" + j + "]"));
tb.SetValue(Grid.RowProperty, i);
tb.SetValue(Grid.ColumnProperty, j);
tb.Margin = new Thickness(0, 0, 20, 0);
myGrid.Children.Add(tb);
}
var edit = new TextBox();
edit.SetBinding(TextBox.TextProperty, new Binding("[" + i + "].MyEditString"));
edit.SetValue(Grid.RowProperty, i);
edit.SetValue(Grid.ColumnProperty, numLabelCols);
edit.AcceptsReturn = true;
edit.TextWrapping = TextWrapping.Wrap;
edit.Margin = new Thickness(0, 0, 20, 6);
myGrid.Children.Add(edit);
}
contentPresenter1.Content = myGrid;
}
A Quick Explanation of the above All it is doing is creating the grid, defines rows for the grid; and a series of columns for the grid that auto size for the content.
Then it simply generates controls for each data point, sets the binding path, and assigns various other display attributes along with setting the correct row/column for the control.
Finally it puts the grid in a contentPresenter that has been defined in the window xaml in order to show it.
Now all you need do is create a class with the following properties and set the data context of the contentPresenter1 to a list of that object:
public class Class1
{
public string[] labels { get; set; }
public string MyEditString { get; set; }
}
just for completeness here is the window xaml and constructor to show hooking it all up:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ContentPresenter Name="contentPresenter1"></ContentPresenter>
</Window>
public MainWindow()
{
InitializeComponent();
var data = new List<Class1>();
data.Add(new Class1() { labels = new string[] {"the first", "the second", "the third"}, MyEditString = "starting text"});
data.Add(new Class1() { labels = new string[] { "col a", "col b" }, MyEditString = "<Nothing>" });
BuildListTemplate(data, 3);
DataContext = data;
}
You can of course use other methods such as a listview and build a gridview for it (I'd do this if you have large numbers of rows), or some other such control, but given your specific layout requirements probably you are going to want this method with a grid.
EDIT: Just spotted that you're looking for a way of doing in xaml - tbh all I can say is that I don't think that with the features you're wanting that it is too viable. If you didn't need to keep things aligned to dynamically sized content on seperate rows it would be more viable... But I will also say, don't fear code behind, it has it's place when creating the ui.
Doing it in the code-behind is really not a WPFish(wpf way).
Here I offer you my solution, which looks nice imo.
0) Before starting, you need GridHelpers. Those make sure you can have dynamically changing rows/columns. You can find it with a little bit of google:
How can I dynamically add a RowDefinition to a Grid in an ItemsPanelTemplate?
Before actually implementing something, you need to restructure your program a little. You need new structure "CustomCollection", which will have:
RowCount - how many rows are there(implement using INotifyPropertyChanged)
ColumnCount - how many columns are there(implement using INotifyPropertyChanged)
ActualItems - Your own collection of "rows/items"(ObservableCollection)
1) Start by creating an ItemsControl that holds Grid. Make sure Grid RowDefinitions/ColumnDefinitions are dynamic. Apply ItemContainerStyle.
<ItemsControl
ItemsSource="{Binding Collection.ActualItems,
Converter={StaticResource presentationConverter}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
local:GridHelpers.RowCount="{Binding Collection.RowCount}"
local:GridHelpers.StarColumns="{Binding Collection.ColumnCount,
Converter={StaticResource subtractOneConverter}"
local:GridHelpers.ColumnCount="{Binding Collection.ColumnCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Grid.Row" Value="{Binding RowIndex}"/>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
The only thing left to do: implement presentationConverter which converts your Viewmodel presentation to View presentation. (Read: http://wpftutorial.net/ValueConverters.html)
The converter should give back a collection of items where each "label" or "textbox" is a seperate entity. Each entity should have RowIndex and ColumnIndex.
Here is entity class:
public class SingleEntity
{
..RowIndex property..
..ColumnIndex property..
..ContentProperty.. <-- This will either hold label string or TextBox binded property.
..ContentType..
}
Note that ContentType is an enum which you will bind against in ItemsTemplate to decide if you should create TextBox or Label.
This might seem like a quite lengthy solution, but it actually is nice for few reasons:
The ViewModel does not have any idea what is going on. This is purely View problem.
Everything is dynamic. As soon you add/or remove something in ViewModel(assuming everything is properly implemented), your ItemsControl will retrigger the Converter and bind again. If this is not the case, you can set ActualItems=null and then back.
If you have any questions, let me know.
Well, the simple yet not not very advanced way would be to fill the UI dynamically in the code-behind. This seems to be the easiest solution, and it more or less matches your winforms experience.
If you want to do it in a MVVM way, you should perhaps use ItemsControl, set the collection of items as its ItemsSource, and define a DataTemplate for your collection item type.
I would have the DataTemplate with something like that:
<Window x:Class="SharedSG.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:SharedSG"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type app:LabelVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="G1"/>
<ColumnDefinition SharedSizeGroup="G2"/>
<ColumnDefinition MinWidth="40" Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding L1}" Grid.Column="0"/>
<Label Content="{Binding L2}" Grid.Column="1"/>
<TextBox Grid.Column="2"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Grid.IsSharedSizeScope="True">
<ItemsControl ItemsSource="{Binding}"/>
</Grid>
</Window>
You're probably way past this issue, but I had a similar issue recently and I got it to work surprisingly well in xaml, so I thought I'd share my solution.
The major downside is that you have to be willing to put an upper-bound on what "lots" of labels means. If lots can mean 100s, this won't work. If lots will definitely be less than the number of times you're willing to type Ctrl+V, you might be able to get this to work. You also have to be willing to put all the labels into a single ObservableCollection property in your view model. It sounded to me in your question that you already tried that out anyway though.
I takes advantage of AlternationIndex to get the index of the label and assign it to a column. Think I learned that from here. If an item has < x labels the extra columns won't get in the way. If an item has > x labels, the labels will start stacking on top of each other.
<!-- Increase AlternationCount and RowDefinitions if this template breaks -->
<ItemsControl ItemsSource="{Binding Labels}" IsTabStop="False" AlternationCount="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Grid.Column"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(ItemsControl.AlternationIndex)}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A"/>
<ColumnDefinition SharedSizeGroup="B"/>
<ColumnDefinition SharedSizeGroup="C"/>
<ColumnDefinition SharedSizeGroup="D"/>
<ColumnDefinition SharedSizeGroup="E"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

WPF ComboBox performance problems by binding a large collections

I'm trying to bind a large collection to a ComboBox and I faced performance problems when opening ComboBox's popup. I searched internet and found that using VirtualizingStackPanel as a items panel template might help, but it helped only partially. If I bind a large collection to a ComboBox, I could open popup very quickly, that's ok, but if after that I bind another collection to a ComboBox and try to open popup again, it becomes very slow. Same is happening if you open popup for an empty ComboBox, then bind large collection and try to open popup again - it takes some seconds before popup opens.
Here is the XAML:
<ComboBox Name="cbBlah">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
and the sample code for binding to reproduce the problem:
var list = new List<string>();
for (var i = 0; i < new Random().Next(9000, 10000); i++)
list.Add(i.ToString());
cbBlah.ItemsSource = list;
I tried to make virtualizing stack panel to look like this:
<VirtualizingStackPanel VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" />
but it doesn't help, seems VirtualizationMode is ignored so popup opens very fast only first time and then, each time after binding changes, it's very slow.
UPDATE: I thought about not binding new collection every time, but bind an ObservableCollection once and then just changing its content. Same thing, as soon as content of collection changes, opening a popup still takes several seconds :(
According to this blog: http://vbcity.com/blogs/xtab/archive/2009/12/15/wpf-using-a-virtualizingstackpanel-to-improve-combobox-performance.aspx
I've tested it with this code:
<ComboBox Name="cbBlah" ItemsSource="{Binding}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
It works fine for first time and next times. It's not necessary to code these lines:
<VirtualizingStackPanel VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" />
I had the issue with slow performance as well. But I had created a class that inherited form Combobox, therefor I would like to do this programmatically. So here is that solution for other googlers out there.
ItemsPanel = new ItemsPanelTemplate();
var stackPanelTemplate = new FrameworkElementFactory(typeof (VirtualizingStackPanel));
ItemsPanel.VisualTree = stackPanelTemplate;
I just ran into this issue as well. I'm using this code in a custom combo box with a style template. When I ran my code in VS debugging mode the virtualization did not work properly. Once I ran it outside of debugging I can switch the content of the ObservableCollection without locking the UI up. It also might help if you set a max height and max width.
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling"/>
<Popup>
<Border/>
<ScrollViewer>
<VirtualizingStackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained"/>
</ScrollViewer>
</Grid>
</Popup>
From a usability perspective, using a standard combobox with more items than will fit on the screen is always cumbersome. It requires at least a textbox filter. In many cases the options can be prefiltered (e.g. by department, by first letter or range) that creates less objects and usualy is more user friendly.

Changing opacity in a long listbox make last elements disapear

I created a listbox with an Opacity set to a value less than 1, and when I bind a long list, the last elements disappear.
I created a little sample to reproduce the problem
In the XAML, a listbox :
<ListBox x:Name="mainList" ItemsSource="{Binding}" Opacity="0.5"></ListBox>
and it is bound to a long list :
public MainPage()
{
InitializeComponent();
List<int> l = new List<int>();
for (int i = 0; i < 100; i++)
{
l.Add(i);
}
this.DataContext = l;
}
When I execute it, the last element I see is the "87", there is the place for the others elements in the bottom, but it is completely black.
What is the problem exactly ?
edit: a colleague told me it is probably a problem with virtualization, as the problem happens at the 87, and we have 29 items visible on the screen (the listbox virtualizes 3 times the number of items shown, 3*29 = 87).
I made the same test with an ItemsControl (without virtualization), and the problem is the same.
*Edit - I was able to reproduce your issue - one thing that does not produce exactly the same UI, but works and looks similar, is setting the ListBox opacity to 1, and then set the UI element in the DataTemplate to opacity 0.5.
Code:
<ListBox x:Name="mainList" ItemsSource="{Binding}" Opacity="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Opacity="0.5" Text="{Binding}">
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

VirtualizingStackPanel Is not working

I have the following ListBox:
<ScrollViewer>
<!--Spec Definitions-->
<ListBox DataContext="{Binding SpecPackageSpecGroupListViewModel}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.IsDeferredScrollingEnabled="True"
ItemContainerStyle="{StaticResource SpecPackageSpecGroupListBoxStyle}"
ItemsSource="{Binding SortedChildren}"
Background="Transparent"
BorderThickness="0" SelectionMode="Extended"
Margin="5,5,5,5">
<ListBox.ItemTemplate>
<DataTemplate>
<Controls:SpecPackageSpecGroupControl/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
This list box is supposed to host ~1000 items, but complex ones.
I want it to work with the VirtualizingStackPanel, so I have set the visualizing XAML configuration to:
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
My problem is that I think it doesn't work - first - it takes a very long time to load ~700 items, and secondly, when I hit a breakpoint on my control constructor - I can see it is called 700 times:
public static int Counter = 0;
public SpecPackageSpecGroupControl()
{
InitializeComponent();
Counter++;
if (Counter%100 == 0)
Console.WriteLine("Hi");
}
I break point on the Console.WriteLine("Hi") and I can see that the static counter reached 700.
So basically the UIElements are being created although this is a virtual mode.
Am I misunderstanding the virtualization mode, or is there something I'm doing wrong?
Don't put it in a ScrollViewer. The XAML as you pasted indeed bypasses virtualization but for a different reason: the ListBox extends fully (without scrolling) because the scrollViewer allows it to. Because it is fully 'extended', the ListBox doesn't use virtualization. It will use its built-in scroll viewer if you place it in a regular container - Border, Grid etc.

Resources