开发者

Horrorble performance using ListViews with nested objects in WPF

like mentioned in the title I get a horrible performance if I use ListViews 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,

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜