like mentioned in the title I get a horrible performance if I use ListView
s with nested objects.
My scenario is:
Each row of a ListView
presents an object of the class Transaction
with following attributes:
private int mTransactionID;
private IBTTransactionSender mSender;
private IBTTransactionReceiver mReceiver;
private BTSubstrate mSubstrate;
private double mAmount;
private string mDeliveryNote;
private string mNote;
private DateTime mTransactionDate;
private DateTime mCreationTimestamp;
private BTEmployee mEmployee;
private bool mImported;
private bool mDescendedFromRecurringTransaction;
Each attribute can be accessed by its corresponding property. An ObservableCollection<Transaction>
is bound to the ItemsSource
of a ListView
. The ListView
itself looks like the following:
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.ToSave" Width="80">
<GridViewColumnHeader Name="GVCHLoadedToSave" Style="{StaticResource ListViewHeaderStyle}">Speichern</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<CheckBox Name="CBListViewItem" IsChecked="{Binding Path=Transaction.ToSave, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.TransactionDate" Width="80">
<GridViewColumnHeader Name="GVCHLoadedDate" Style="{StaticResource ListViewHeaderStyle}">Datum</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding ElementName=DPDate, Path=Text}" Style="{StaticResource GridBlockStyle}"/>
<toolkit:DatePicker Name="DPDate"
Width="{Binding ElementName=GVCHDate, Path=ActualWidth}"
SelectedDateFormat="Short"
Style="{StaticResource GridEditStyle}"
SelectedDate="{Binding Path=Transaction.TransactionDate, Mode=TwoWay}"
SelectedDateChanged="DPDate_SelectedDateChanged"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.Sender.Description" Width="120">
<GridViewColumnHeader Name="GVCHLoadedSender" Style="{StaticResource ListViewHeaderStyle}">Von</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Transaction.Sender.Description}" Style="{StaticResource GridBlockStyle}"/>
<ComboBox Name="CBSender"
Width="{Binding ElementName=GVCHSender, Path=ActualWidth}"
SelectedItem="{Binding Path=Transaction.Sender}"
DisplayMemberPath="Description"
Text="{Binding Path=Sender.Description, Mode=OneWay}"
ItemsSource="{Binding ElementName=Transaction, Path=SenderList}"
Style="{StaticResource GridEditStyle}">
</ComboBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.Receiver.Description" Width="120">
<GridViewColumnHeader Name="GVCHLoadedReceiver" Style="{StaticResource ListViewHeaderStyle}">Nach</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Transaction.Receiver.Description}" Style="{StaticResource GridBlockStyle}"/>
<ComboBox Name="CBReceiver"
Width="{Binding ElementName=GVCHReceiver, Path=ActualWidth}"
SelectedItem="{Binding Path=Transaction.Receiver}"
DisplayMemberPath="Description"
Text="{Binding Path=Receiver.Description, Mode=OneWay}"
ItemsSource="{Binding ElementName=Transaction, Path=ReceiverList}"
Style="{StaticResource GridEditStyle}">
</ComboBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.Substrate.Description" Width="140">
<GridViewColumnHeader Name="GVCHLoadedSubstrate" Style="{StaticResource ListViewHeaderStyle}">Substrat</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Transaction.Substrate.Description}" Style="{StaticResource GridBlockStyle}"/>
<ComboBox Name="CBSubstrate"
Width="{Binding ElementName=GVCHSubstrate, Path=ActualWidth}"
SelectedItem="{Binding Path=Transaction.Substrate}"
DisplayMemberPath="Description"
Text="{Binding Path=Substrate.Description, Mode=OneWay}"
ItemsSource="{Binding ElementName=Transaction, Path=SubstrateList}"
Style="{StaticResource GridEditStyle}">
</ComboBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.Amount" Width="80">
<GridViewColumnHeader Name="GVCHLoadedAmount" Style="{StaticResource ListViewHeaderStyle}">Menge [kg]</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Transaction.Amount}" Style="{StaticResource GridBlockStyle}"/>
<TextBox Name="TBAmount" Text="{Binding Path=Transaction.Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="{Binding ElementName=GVCHAmount, Path=ActualWidth}" Style="{StaticResource GridTextBoxStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.DeliveryNote" Width="100">
<GridViewColumnHeader Name="GVCHLoadedDeliveryNote" Style="{StaticResource ListViewHeaderStyle}">Lieferschein Nr.</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Transaction.DeliveryNote}" Style="{StaticResource Gri开发者_高级运维dBlockStyle}"/>
<TextBox Name="TBDeliveryNote" Text="{Binding Path=Transaction.DeliveryNote, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="{Binding ElementName=GVCHDeliveryNote, Path=ActualWidth}" Style="{StaticResource GridEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.Note" Width="190">
<GridViewColumnHeader Name="GVCHLoadedNote" Style="{StaticResource ListViewHeaderStyle}">Bemerkung</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Transaction.Note}" Style="{StaticResource GridBlockStyle}"/>
<TextBox Name="TBNote" Text="{Binding Path=Transaction.Note, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="{Binding ElementName=GVCHNote, Path=ActualWidth}" Style="{StaticResource GridEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn core:SortableListView.SortPropertyName="Transaction.Employee.LastName" Width="100">
<GridViewColumnHeader Name="GVCHLoadedEmployee" Style="{StaticResource ListViewHeaderStyle}">Mitarbeiter</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Transaction.Employee.LastName}" Style="{StaticResource GridBlockStyle}"/>
<ComboBox Name="CBEmployee"
Width="{Binding ElementName=GVCHEmployee, Path=ActualWidth}"
SelectedItem="{Binding Path=Transaction.Employee}"
DisplayMemberPath="LastName"
Text="{Binding Path=Employee.LastName, Mode=OneWay}"
ItemsSource="{Binding ElementName=Transaction, Path=EmployeeList}"
Style="{StaticResource GridEditStyle}">
</ComboBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
As you can see in the screenshot the user got the possibility to change the values of the transaction attributes with comboboxes.
Ok now to my problem. If I click on the "Laden" button the application will load about 150 entries in the ObservableCollection<Transaction>
. Before I fill the collection I set the ItemsSource
of the ListView
to null
and after filling I bind the collection to the ItemsSource
once again. The loading itself takes a few milliseconds, but the rendering of the filled collection takes a long time (150 entries = about 20 sec). I tested to delete all Comboboxes
out of the xaml and i got a better performance, because I don't have to fill the ComboBoxes
for each row. But I need to have these comboboxes for modifing the attributes of the Transaction
.
Does anybody know how to improve the performance?
THX
All the answers up to now seem to focus on making the UI perform faster, while it seems to me the datacollection that is to blame. I made a similar application and had no performance issues. Do the properties you bind to represent fields or queries?
150 entries doesn't sound that many to me and 20 seconds does sound like a long time. I can't see anything untoward in what you have posted but it doesn't mean to say that there isn't an issue there.
First thing to look at would be to see if UI Virtualization is enabled.
Potentailly try to see if the DataGrid, instead of the ListView, improves performance (bundled with wpf4 or can be downloaded as part of the WPFToolKit)?
One more thing to improve overall performance (but probably not fix the initial problem if your UI is already using UI Virtualization) would be to implement Data Virtualization. It looks tricky but the article is pretty good at walking you through it and it's the optimal solution to improve performance of rendering large lists of data.
As a last ditched work around you could consider some sort of paging mechanism so you only show a smaller of number of items at any one time. You could look into a classic paging solution with page numbers, forward, back buttons.
Can we get a look at a larger picture of your data structure? My concern is where some of the comboboxes are getting are getting their data. For instance,
<ComboBox Name="CBSender"
Width="{Binding ElementName=GVCHSender, Path=ActualWidth}"
SelectedItem="{Binding Path=Transaction.Sender}"
DisplayMemberPath="Description"
Text="{Binding Path=Sender.Description, Mode=OneWay}"
ItemsSource="{Binding ElementName=Transaction, Path=SenderList}"
Style="{StaticResource GridEditStyle}">
This one depends on whether Sender list is different for each object in your transaction collection. If not, I would suggest loading sender list/collection into it's own resource in xaml, and just pulling the values into the combo box from there. As it is now, each combobox has to query it's respective object get a list, then pre-render that list. 4 combo boxes times 150 objects is 600 lists of data to get and pre-render separately from each other.
If you pulled those lists into a XAML resource you would only be storing 4 lists.
Edit:
Also just out of curiosity, Is it a design requirement (e.g. the customer wants it to look this way) to have the Display controls and edit controls visible at the same time? You could instead use one template to display and another to edit so that when a record is not selected, all of the cells are TextBlocks, when the record is selected, cells are edit controls, ComboBox, TextBox, DatePicker etc.
I had the same issue and finally figured out that it was due to something unrelated with ListView or ComboBox.
It happens that the ListView was nested inside an Infragistic TabControl and each time something was bound inside the ListView (ie: ComboBoxes), the "SelectionChange" of the TabControl was firing, causing the delay...
I've also tested with a native Microsft TabControl and I got fairly the same behavior, but somewhat a bit more performant.
I solved the issue by validating the SelectionChangedEventArgs... making sure the e.AddedItems contains only "TabItem" (and not ComboBoxes) before processing.
Hope it helps,
精彩评论