I have a control that uses a ParentObject as its DataContext. The ParentObject has a property called ChildObject that may change. When it does, ParentObject raises the PropertyChanged event.
The control has XAML to define a ListView for the items in the List property of the ChildObject. When the ChildObject property changes in the ParentObject, the entire view is recreated, meaning that new controls are instantiated.
The ListView is actually much more complicated than the example below, so recreating it is processor intensive and takes a long time.
What are my other options? Can I cache the entire ListView for each ChildObject? How would I go about doing that?
<ListView ItemsSource="{Binding ParentObject.ChildObject.List}">
<ListView.View>
<GridView>
<GridViewColumn Header="Error">
<GridViewColumn.CellTemplate>
<DataTemplate>
<local:ErrorControl DataContext="{Binding ErrorCollection}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The listview is creating new controls becuase of your datatemplate. With data templates, you have a one-to-one relationship between the instance of your data and the controls inside of the datatemplate. You have a few options:
1) Have you looked into virtualization? This will only render what is visible. If you have a high number of objects, you definitely want to consider this.
2) Have you thought about re-working your view-model to minimize the change in your collections? Instead of adding/removing objects anytime an error changes. Re-use the objects in the collection and only add/remove when your total count changes.
I've done both of these to slow changes to the view from the view-model. They both improved performance significantly, but I had hundreds of objects on the screen.
Related
I am currently building an UWP application which should control several jobs, where each job could be updated from a background thread. Currently, I am using an ObservableCollection in a static "Core-Class" in which all jobs are stored (see question 1). This list is bound using a property in the view model of my view:
public ObservableCollection<JobBase> Jobs
{
get
{
return Core.Jobs;
}
set
{
Core.Jobs = value;
}
}
The list view's ItemsSource is bound to this property in my view model. I am using a custom ListView.ItemTemplate which uses a DataTemplate linked to my Job-class to display the information for each job:
<ListView
x:Name="JobsListView"
Grid.Row="1"
ItemsSource="{x:Bind ViewModel.Jobs, Mode=OneWay}"
SelectionMode="Single"
IsItemClickEnabled="True"
SelectedItem="{x:Bind ViewModel.RunningJob, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="lib:JobBase">
<Grid Margin="0,12,0,12" Height="52">
[...]
<TextBlock FontSize="12" Text="{x:Bind Progress, Mode=OneWay}" Margin="4,0,0,0" />
[...]
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Some thoughts about this construct:
1) I think using an ObservableCollection in my static "background" Core-Class is not beautiful. I could change this to a normal List and do a cast to an ObservableCollection in the property of my list view. However, I will need some events when new jobs are added (or old ones deleted) in order to update my ObservableCollection which is bound the to the ListView. Is this the preferred implementation?
2) As soon as the progress of a certain job is updated (from within a background thread), I cannot show those updates in the list view. Even if I implement events for this, I have no clue how to update the binding of a certain item within my list view?
3) I could implement the INotifyPropertyChanged interface in my Job class, but I think this would not be a beautiful implementation either. In addition, I am raising exceptions as I cannot update my GUI from a background thread?
As you see, I am looking for a "professional" implementation on this issue. Therefore, I would prefer a solution which uses bindings and avoids a complete reloading of the whole job list if only a single item is updated.
I am really looking forward for your implementation tips :-)
You should not be replacing the instance of the object being bound to ItemsSource. Instead, you'll need to modify the contents of the list. You can replace the ItemsSource if you choose, but this can lead to serious heap fragmentation and performance problems.
To update the UI thread, use CoreDispatcher.RunAsync to get back on the UI thread from a thread that could potentially be in the background https://learn.microsoft.com/en-us/uwp/api/windows.ui.core.coredispatcher.runasync?view=winrt-18362
await _coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.Bindings.Update();
});
If you need to just update one item at a time, ObservableCollection could be ideal, especially when the List includes objects which implement INotifyPropertyChanged. If instead you prefer to update several objects at once you'd set the binding to OneTime then use Bindings.Update() to update all the bindings in the ListView at once. This documentation can help to provide more info about optimizing your ListView. https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/optimize-gridview-and-listview
ObservableCollection isn't necessary. There are other mechanisms of data binding if you prefer.
For more on data bindings, see https://learn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-in-depth
I have a BindingList<Pair<string,string>> (where Pair is just your bog standard generic to contain two related objects). I want to be able to bind this to a ListView such that the .First values is in column 1 and the .Second value is in column 2. Further to this, how can I make sure the list view constantly represents the contents of the list, such that if I change one of the strings the ListView automatically updates?
Here is my Pair<TI,TJ> class for reference:
public class Pair<TI, TJ>
{
public TI First;
public TJ Second;
public Pair(TI first, TJ second)
{
First = first;
Second = second;
}
}
Just to clarify, a BindingList is just the collection I'm currently trying to use, I can use any collection capable of supporting this functionality and holding Pair<string,string>.
I don't have the IDE at hand, but it depends mostly on if you use WinForms or XAML/WPF. Since it is probably the last (more modern), some tips:
Probably you need an ObservableCollection, if something changes in the collection the bindings will work.
For binding to your data, you need to bind them in WPF, and set a data context.
In general it looks like this:
<ListView ItemsSource="{Binding ListOfYourData}"
SelectedItem="{Binding Path=SelectedItem}"
...
<ListView.View>
<GridView>
<GridViewColumn Width="140" Header="First"
DisplayMemberBinding="{Binding First}"
<GridViewColumn Width="140" Header="Second"
DisplayMemberBinding="{Binding Second}"
There is two important interfaces for databinding with Winforms.
IBindingList - This one concern "Collection" and will update the control when a new element is added or when an element is removed. The BindingList already implement it so you don't have to take care about. You could test with a simple List if you want to test the behaviour without this interface.
INotifyPropertyChanged - This one concern "Object" and will update the control when an object have a change one of his properties (the value of the object has changed). Your class "Pair" should implement this interface. It is trivial. You juste have to add a Sub PropertyChanged() which raise a event and add a call of this Sub in the setter of your properties.
You could have a look at the MSDN if you want a sample.
https://msdn.microsoft.com/fr-fr/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx
Resume :
No IBindingList => Control doesn't update when you add or remove object in a collection.
No INotifyPropertyChanged => Control doesn't update when a property of an object have changed.
Edit - Of course the control have to support Binding which is not the case of the legacy ListView...
I have an ObservableCollection of Film called Films in my FilmListViewModel which I then display like this:
<ListView Grid.Row="0" ItemsSource="{Binding FilmListViewModel.Films }">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="Auto" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Genre" Width="Auto" DisplayMemberBinding="{Binding Genre}" />
</GridView>
</ListView.View>
</ListView>
Items is a property and its getter looks like this:
public ObservableCollection<Film> Films
{
get { return this._DBcontext.Set<Film>().Local; }
}
When I remove a film from the collection Items, the list of films in the window is automatically updated and shortened. However, I want to do other operations on the list of films: sorting, filtering and searching. I therefore added another property to the FilmListViewModel called OrderedFilms, which:
public ObservableCollection<Film> OrderedFilms
{
get
{
orderedFilms = this._DBcontext.Set<Film>().Local.OrderBy(film => film.Name);
return new ObservableCollection<Film> (orderedFilms);
}
}
In thew ListView I change ItemsSource to
ItemsSource="{Binding FilmListViewModel.OrderedFilms }"
However, if I then remove a film from the underlying table in the _DBContext the list of films in the window is not updated. I suspect the reason is that the getter of OrderedFilms actually constructs a new ObservableCollection and returns that.
How can I display the underlying table of Films in a modified fashion (filtered, sorted, etc.) and still have it updated in the window correctly when the table changes?
ObservableCollection<T> class is nothing more than an implementation of a list with change notifications (INotifyCollectionChanged and INotifyPropertyChanged). The important thing though is that it has a storage semantics, so even the constructors that accept IEnumerable<T> or List<T> simply create a copy (snapshot) from the passed collection.
So while your Films property is an accessor to the real observable storage, the observable collection returned by the OrderedFilms is a disconnected ordered/filtered snapshot of the storage.
What you really need is a class that provides a view (sorted, filtered) of the storage, keeping reference to it and reacting to the source change notifications. Luckily WPF provides such a class out of the box called CollectionView
Represents a view for grouping, sorting, filtering, and navigating a data collection.
You can use CollectionViewSource.GetDefaultView method to obtain a collection view implementation for your Films property and then filter, sort and bind to that view.
You can find more info about CollectionView and CollectionViewSource in the MSDN documentation as well as in these useful links How to Navigate, Group, Sort and Filter Data in WPF,
Working with CollectionView in WPF(Filter,Sort, Group, Navigate) etc.
I've been following this tutorial to try to work an understanding of XML, WPF, and C# (coming out from Fortran). I've gotten the functionality working (thanks to the tutorial), but I'm having troubles modifying it to work with WPF instead of WinForms.
Essentially, when I click the "Get RSS" button, the following is happening:
RssManager reader = new RSSManager();
ObservableCollection<Rss.Items> _list = new ObservableCollection<Rss.Items>();
reader.Url = txtFeed.Text;
reader.GetFeed();
_list = (ObservableCollection<Rss.Items>)reader.RssItems;
The listview just sits blank. It's code is like the following. Also, trying this with a listbox results in the name of the class being populated for each item in the list instead of my data:
<ListView ItemsSource="_rssItems">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Title}"/>
</GridView>
</ListView.View>
</ListView>
Would it be possible to do something like (again, forgive me for my ignorance, here):
_list.Add( new Rss.Items());
The list (_list) contains all of the information that I need, I just want to figure out how to properly bind it (or add it) to the ListView.
It looks like you are a bit lost.
Ultimately you want to bind your view(WPF form) to a View-Model and your View-Model to a model (the RSSManager).
For now lets bind the view directly to the model.
In your constructor you make a new instance of the model and you assign it to the data context.
This model is going to live as long as the form -
public MainWindow()
{
InitializeComponent();
_model = new RssManager();
DataContext = _model;
}
Then in your XAML you bind the item source to your collection property :
<ListView ItemsSource="{Binding Path=RssItems}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Title}"/>
</GridView>
</ListView.View>
</ListView>
Note that in "Path=RssItems" is relative to whatever you assigned to the DataContext.
Then in your refresh button logic you call:
_model.Url = txtFeed.Text;
_model.GetFeed();
What you ultimately want to do is put another layer in the middle. This is the view model. The View model is as you may have guessed a model of the view.
The view model's job is to collect information about the state of the view and to expose the data from the model that is to be presented by the view. It also can hold current ui state information - f.e. which row in the table is selected, since some command may act on that later. In essence it allows to abstract all the logic of the view from your code. Your commands operate on things like which row is selected - regardless of which type of control did the selection.
As Lee suggests in his comment, the binding for the ItemsSource property of the ListView does not appear to be correct. There are multiple ways to approach this, depending on how your project is designed/structured.
In order to DataBind, the ListView will need some kind of DataContext which (as the name implies) is kind of the Context for the Binding. If you are using MVVM, then most likely, the DataContext of your entire Window/Control would be a ViewModel. In that case, you bind to a property of the ViewModel like this:
<ListView ItemsSource="{Binding RssItems}">...</ListView>
This assumes you have a ViewModel with a public RssItems property (which is some kind of List/Enumerable) and the ViewModel is DataContext.
If you are not using MVVM, there are a lot of ways to assign the ItemsSource both with DataBinding and without. The easiest way I can suggest, if you're not fully comfortable with DataBinding, would be to manually assign the ItemsSource, like this:
Xaml:
<ListView x:Name="MyRssList">...</ListView>
Code Behind (somewhere after the UI has Loaded and after you've created/populated _list):
MyRssList.ItemsSource = _list;
This doesn't use DataBinding, but it will get the job done. If you want to start out with DataBinding, you could do the following:
XAML:
<ListView x:Name="MyRssList" ItemsSource="{Binding}>...</ListView>
Code Behind:
MyRssList.DataContext = _list;
This will assign the List as the DataContext of the ListView, then DataBind the ItemsSource property to the DataContext.
Overall, You're on the right track. I'd recommend some reading on DataBinding and MVVM. MVVM is a very good way to leverage the powerful DataBinding capabilities of WPF, and a strong understanding of DataBinding is extremely valuable in designing and building great WPF apps.
Hope this helps. Good luck!
xaml code
<ListView Name="lvw" VirtualizingStackPanel.IsVirtualizing="True" ItemsSource="{Binding Source={StaticResource MyList}}" >
<ListView.View>
<GridView AllowsColumnReorder="true" VirtualizingStackPanel.IsVirtualizing="True" >
<GridViewColumn x:Name="MiaCode" DisplayMemberBinding="{Binding Path=MIACODE}" Header="code" Width="80" />
<GridViewColumn x:Name="MiaName" DisplayMemberBinding="{Binding Path=MIANAME}" Header="name" Width="270"/>
</GridView>
</ListView.View>
</ListView>
//Binding Data count is over 10000
even though I set the property [VirtualizingStackPanel.IsVirtualizing="True"],
it takes too long to display data.
is there anything wrong in my code??
Is MyList a static data? you can use asynchronous binding by setting IsAsync property Binding.IsAsync Property or asynchronous data loading see ObjectDataProvider.IsAsynchronous Property,or develop some paging mechanism VirtualizingStackPanel.IsVirtualizing = "true" is not speeding up data loading time, it just doesn't create UI elements that are not visible Hope this helps
Confirming the correctness of arsenmkrt's reply.
A virtualized items source is generally pretty fast as long as it's not waiting for the underlying data to load.
If you get no joy, here are some more considerations:
Try pre-loading/caching your data if possible.
Double check that the property getters for MIACODE and MIANAME aren't performing CPU instensive work, and aren't creating any side effects.
Use Linq to page the in-bound data.
From experience, though, I can confirm that the GridView on a virtualized listview is amply capable of displaying thousands of items without severe performance panalties. Hope this helps you.
Oh... just spotted that you're binding to a static resource....
If you are creating an explicit CollectionViewSource resource for grouping and sorting your items, take care to note that the by-property sorting mechanism of the CollectionView is known to be slow.
If this scenario describes what you're doing, try using a customer sort filter with the collection view (which is considerably faster), or better yet retrieve your data pre-sorted the way it needs to be.
Broadly speaking, you can only rely on the based sorting of CollectionView up to a few thousand items before the sorting time shows it's poorer performance.