I'm in a bit of a bind here (no pun intended); I have a large collection of view models (500+) which are displayed using an ItemsControl with a WrapPanel as the ItemsPanelTemplate. Each of these view models exposes a Boolean? whose value is bound to the IsChecked property of a CheckBox on the user interface.
The problem is this... whenever I attempt to update all the checkboxes at once it is horrendously slow. Almost 10 seconds to update a list of 500 items. If I run the updating code in a seperate thread I can almost watch the checkboxes be updated one by one.
Can anyone enlighten me as to why this process is so slow and how I could improve it?
I have considered that perhaps the non-virtualizing nature of the WrapPanel could be the guilty party. However, when I bind to the IsEnabled property instead of IsChecked I see an interesting result; namely that changing the value of IsEnabled to true is slow as expected, but changing to false happens instantaneously. This makes me suspicious that the checkbox animation is at fault because as far as I can tell visually there is no animation when disabling a checkbox, but there is when enabling. Profiling has revealed that the vast majority of time is spent in the PropertyChangedEventManager.OnPropertyChanged() method.
Example code below, I'm unfortunately forced to use .NET 3.5:
XAML:
<ItemsControl ItemsSource="{Binding ChildItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type SampleViewModel}">
<CheckBox IsThreeState="True" IsChecked="{Binding Path=IncludeInPrinting, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
ViewModel:
public class SampleViewModel : INotifyPropertyChanged
{
private Boolean? _includeInPrinting;
public Boolean? IncludeInPrinting
{
get
{
return _includeInPrinting;
}
set
{
if (_includeInPrinting != value)
{
_includeInPrinting = value;
RaisePropertyChanged(() => IncludeInPrinting);
}
}
}
}
Slow Code:
foreach (SampleViewModel model in ChildItems)
{
model.IncludeInPrinting = false;
}
EDIT: For what it's worth I'm also seeing a spike in memory usage whenever I check all or uncheck all the checkboxes. ~10MB
EDIT: The performance analysis below seems to confirm that animation is definitely the issue.
I would look at the following control which is open source on CodePlex..
http://virtualwrappanel.codeplex.com/ (Note: I have no affilication with Virtualizing Wrap Panel)
Due to the large number of view models you are working with this would drastically improve performance for you.
Related
I am working on a WPF Application where i have a combobox with ItemsSource binded to a property of 5000 records coming from database. The problem is that when i click the dropdown arrow of combobox the UI not responding or combobox is taking too much time to respond. I searched it but nothing worked for me.
here is the code:
<ComboBox IsEditable="True" ItemsSource="{Binding List,Mode=OneWay}" DisplayMemberPath="name" SelectedValue="{Binding SelectedItem,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
and the property
private ObservableCollection<Object> _List = new ObservableCollection<Object>();
public ObservableCollection<Object> List
{
get { return _List ; }
set { _List = value; OnPropertyChanged("List"); }
}
Edit:
here is the code that loads data inside the constructor
public FormVM()
{
List = new ObservableCollection<Object>(db.cat.ToList());
}
You have to enable UI virtualization.
Currently UI virtualization is disabled for your ComboBox!
Controls like ListBox or ListView have this feature enabled by default.
Other controls that extend ItemsControl like ComboBox have to enable it explicitly.
To enable UI virtualization
The ItemsPresenter (or any Panel with Panel.IsItemsHost set to True) of the ItemsControl must be the child of a ScrollViewer.
This is already the case for the ComboBox.
The ScrollViewer must be configured to scroll by items (logical units) instead of pixels (physical units) by setting the attached ScrollViewer.CanContentScroll property to True.
ItemsControl must have its ItemsPanel set to a VirtualizingStackPanel.
The virtualization mode of the VirtualizingPanel must be enabled by setting the attached property VirtualizingPanel.IsVirtualizing to True.
Example
<ComboBox VirtualizingPanel.IsVirtualizing="True"
ScrollViewer.CanContentScroll="True">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
Further improvements can be achieved by enabling deferred scrolling:
<ComboBox ScrollViewer.IsDeferredScrollingEnabled="True" />
Satisfying one of the following conditions will make UI virtualization impossible:
Item containers are added directly to the ItemsControl. For example,
if an application explicitly adds ListBoxItem objects to a ListBox,
the ListBox does not virtualize the ListBoxItem objects.
Item containers in the ItemsControl are of different types. For
example, a Menu that uses Separator objects cannot implement item
recycling because the Menu contains objects of type Separator and
MenuItem.
Setting CanContentScroll to false.
Setting IsVirtualizing to false.
If you have followed every constraint then UI virtualization does work. You then have a problem which is not related to UI virtualization. If yoou would set up a new empty project with just a ComboBox you should encounter no issues.
I'm working on my first Universal App (I'm beginner with it).
In app I have few places, where I want to select ListViewItem, and remove/edit (http://i.imgur.com/ZRkIQHm.png) it by some button. I'm not a designer, so I want to choose some simple, good-looking solution how to display these buttons.
I was inspired by Windows 10, there are few places, where you click on ListViewItem, and this selected item shows some buttons (Printer setting, WiFi connection, Bluetooth connection) - like on this image - http://i.imgur.com/WjerA5F.png.
I've spend lot of time with googling, trying, and now I have no idea how to do it. I was closest probably with using VisualStateManager in ListViewItemContainer.
Here is code example (in c# is filled ItemsSource by some ObservableCollection<>):
<ListView Name="dbList" Grid.Column="0" SelectionChanged="dbList_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="XXX">
<TextBlock Text="{Binding}"/>
<Button Name="removeDatabaseButton" Click="removeDatabaseButton_Click" Content="Remove" Tag="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Thanks!
PS: Sorry for my english :)
The answer is quite complicated:
You need to modify your list Type Class which should implement the INotifyPropertyChanged interface which has the property Visible.
private Visibility _visible;
public Visibility Visible //Binding to this in Listbox - Button Visibility
{
get { return _visible; }
set { _visible = value; } //Call OnPropertyChanged method
}
Now create a listview_itemclick() method. Call the listview.selectedindex and set the visibility of the listitem property "Visible" to Visible - the event PropertyChanged will fire and the ui gets notified that the visibility of this specific button should change.
Helping links:
INotifyPropertyChangedInterface
Playlist to MVVM tutorials - containing INotifyPropertyChanged
I need to display a large amount of data (thousands of items) in a ListBox however it takes some times and the UI is not responsive until the whole items are displayed in the ListBox.
The ItemsSource of the ListBox is bound the an CollectionView.
I know the benefit of using the VirtualizingStackPanel but I insist to use a WrapPanel.
I've searched over the internet and found some VirtualizedWrapPanels but they have the same issue which is they don't allow the VirtualizingWrapPanel to grow to whatever size it likes and I have to set both Width and Height for them.
Now I need to know what other options are out there for me to do the job? What can I do so that the ListBox loads and displays this large amount quickly.
Please let me know if I haven't explained m
y issue clearly.
Any help is appreciated. Thanks in advance.
Edit
This is the relevant code
public ObservableCollection<T> Items
{
get { return _items; }
set
{
if (_items == value)
return;
_items = value;
OnPropertyChanged(() => Items);
}
}
I instantiate the ObservableColelction in the LoadData method of the ViewModel
_items = _service.Select();
and the CollectionView is instantiated in this way
ICollectionView cv = new CollectionViewSource() { Source = Items }.View;
and the xaml code of the ListBox
<ListBox
x:Name="Items"
ItemsSource="{Binding CollectionView}"
Padding="10,10,10,10"
SelectionMode="Single"
VerticalAlignment="Top"
IsSynchronizedWithCurrentItem="True">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" Height="480" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I am not sure if this is applicable or you are looking for this, however virtualizing the data is one more solution you can look into. there are some very good posts describing this. these 2 blog/post seems to be very good. Please have a look Here[http://www.codeproject.com/Articles/34405/WPF-Data-Virtualization] and here[http://www.zagstudio.com/blog/498#.U7a6OvldV1F].
Hi this should be faily simple, however I don't know what I am doing wrong. I've been looking all over the internet seeing people make this work, even followed the tutorial on MSDN still nothing has worked for me.
I want to Iterate over a ListBox, and get the ListBoxItems so I can find the DataTemplate that I have added to it.
This is my code behind.
private void SetListBoxDataTemplate(ListBox MyListBox)
{
try
{
foreach (CustomDataTemplateObject dataobject in MyListBox.Items)
{
ListBoxItem lbi = (ListBoxItem)(MyListBox.ItemContainerGenerator.ContainerFromItem(dataobject));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(lbi);
DataTemplate dt = myContentPresenter.ContentTemplate;
TextBlock tb = (TextBlock)dt.FindName("ListBoxItemTextBlock1", myContentPresenter);
ComboBox cb = (ComboBox)dt.FindName("ListBoxItemComboBox1", myContentPresenter);
tb.Text = dataobject.Text;
cb.ItemsSource = dataobject.ListColors;
}
}
catch (Exception ex)
{
MessageBox.Show(""+ex);
}
}
XAML looks like this:
<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Name="ListBoxItemTextBlock1" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox Name="ListBoxItemComboBox1" />
</StackPanel>
</DataTemplate>*
<StackPanel>
<ListBox Name="ListBoxTest1" ItemTemplate="{DynamicResource ListBoxItemDataTemplate1}" />
</StackPanel>
I have tried with setting my itemtemplate to static to see if it works, and the method i'm calling from code behind, is called after I have populated my ListBoxs
My dataobject is NOT null, however when i call the line in my code behind, my lbi, ends up being null.
Any suggestions? thanks in advance!
FIRST UPDATE
This problem only occurs if i call the method in my constructor, so perhaps it's because it hasn't initialized the full group element section yet. However I want to do this as soon as possible. Am I perhaps forced to do it in a WindowLoaded event?
SECOND UPDATE
Code updated, Rachel's answer worked for iterating over my ListBoxItems, however the Listbox Has not fully rendered since i'm unable to reach the Datatemplate at this time. So MyListBox_GeneratorStatusChanged is not working for this problem, but it does get the ListBoxItems.
WPF's main thread runs items at different priority levels. Code that runs in the Constructor all gets run at Normal priority, while things like rendering the ListBox and it's items run at the Render priority level, which occurs after all Normal priority operations have finished.
This means that your entire Constructor gets run (including SetListBoxDataTemplate()) before your ListBox is even rendered and the items get generated.
If you want to run some code after the items are generated, use the ItemsContainerGenerator.StatusChanged event
// Constructor
MyListBox.ItemContainerGenerator.StatusChanged += MyListBox_GeneratorStatusChanged;
...
void MyListBox_GeneratorStatusChanged(object sender, EventArgs e)
{
// return if containers have not been generated yet
if (MyListBox.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;
// remove event
MyListBox.ItemContainerGenerator.StatusChanged -= MyListBox_GeneratorStatusChanged;
// your items are now generated
SetListBoxDataTemplate(MyListBox);
}
What are you trying to accomplish with this method anyways? It is a bit unusual for WPF, and there may be a much better WPF way of accomplishing your task.
Updated based on new code added to Question
A much better method of setting your Text and ItemsSource properties is to make use of WPF's data bindings.
Your DataTemplate should look like this:
<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Text="{Binding Text}" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox ItemsSource="{Binding ListColors}" />
</StackPanel>
</DataTemplate>*
A DataTemplate is like a cookie cutter. It's used to make the UI objects, but is not part of the UI object itself. All it does is tell WPF that "When you go to render this object, render it using this XAML". So the way your XAML gets rendered is
<ListBoxItem>
<StackPanel>
<Border>
<TextBlock Text="{Binding Text}" />
</Border>
<ComboBox ItemsSource="{Binding ListColors}">
</StackPanel>
</ListBoxItem>
In addition, the DataContext behind your ListBoxItem is the item from the collection bound to ListBox.ItemsSource, which based on your code should be CustomDataTemplateObject. That allows the bindings from the DataTemplate to work
If you're new to WPF and struggling to understand how exact the DataContext works, I'd recommend reading this article of mine: What is this "DataContext" you speak of?.
To summarize, WPF has two layers to an application: the UI layer and the Data Layer (DataContext). When you perform a basic binding like above, you are pulling data from the data layer into the UI layer.
So your ListBoxItem has a data layer of CustomDataTemplateObject, and the TextBlock.Text and ComboBox.ItemsSource bindings are pulling data from the data layer for use in the UI layer.
I'd also highly recommend using a utility like Snoop which lets you view the entire Visual Tree of a running WPF application to see how items get rendered. Its very useful for debugging or learning more about how WPF works.
You're confusing two jobs and mixing them into one. First, get access to the ListBoxItem:
private void SetListBoxDataTemplate(ListBox MyListBox)
{
foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
}
}
Now you can get the DataTemplate from the ListBoxItem:
foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
ContentPresenter presenter = FindVisualChild<ContentPresenter>(listBoxItem);
DataTemplate dataTemplate = presenter.ContentTemplate;
if (dataTemplate != null)
{
// Do something with dataTemplate here
}
}
The FindVisualChild method can be found in the How to: Find DataTemplate-Generated Elements page on MSDN.
UPDATE >>>
To answer your edit, yes, the constructor will be too early to try to access these DataTemplates because the Framework won't have applied them to all of the objects by then. It is best to use the FrameworkElement.Loaded Event to do these kinds of things, as that is the first event that can be called after the controls have all been initialised.
I was migrating my app from WP7 to WP8, and a funny thing is happening. I've a databound pivot who works perfectly in WP7. But, in WP8, the exact same code, doesn't load the first PivotItem. I've tried all the solutions in the question for WP7, none works (I want a solution, not an ugly workaround). I'm setting the DataContext in the constructor, the collection is OK, and everything should work. It only loads pivots when I scroll in the app. Anyone has any solution?
I can't repro any databinding issues with Pivot on WP8. There is a known issue with Panorama Databinding on WP8, but not Pivot. What exactly doesn't work for you?
Here's a basic WP8 Pivot Databinding code that works just fine for me.
C# code setting a DataContext to an observable collection of cows:
this.DataContext = new ObservableCollection<Cow>()
{
new Cow("Foo"),
new Cow("Bar"),
new Cow("Baz")
};
public class Cow
{
public Cow(string name)
{
Name = name;
}
public string Name { get; set; }
}
XAML code using that DataContext as the ItemSource and Binding PivotItem.Header and PivotItem.Content to the cow name.
<phone:Pivot ItemsSource="{Binding}">
<phone:Pivot.HeaderTemplate>
<DataTemplate>
<ContentControl Content="{Binding Name}" />
</DataTemplate>
</phone:Pivot.HeaderTemplate>
<phone:Pivot.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding Name}" />
</DataTemplate>
</phone:Pivot.ItemTemplate>
</phone:Pivot>
Works just fine...
If it helps i had the same problem, did an ugly fix but it worked..
pivotTest.SelectedIndex = 1;
pivotTest.SelectedIndex = 0;
I created a repro of this bug here: https://github.com/michaellperry/PivotIsBroken
It appears that the bug occurs because the content animation is not triggered. The selected index is not actually changing.
The ugly workaround that I employed is similar to DavidN's recommendation, but I had to insert a dummy page. Setting SelectedIndex to 1 with only one page throws an exception.