none
CollectionViewSource ... langsam und merkwürdiges Verhalten RRS feed

  • Allgemeine Diskussion

  • Hallo, ich verwende für die Darstellung in einem ListView (GridView) eine CollectionViewSource. Der ListView arbeitet mit Virtualizing. Bei einer Collection mit gerade rund 400 Einträgen benötigt eine Änderung der Sortierung bereits rund 2 Sekunden. Interessanterweise ist die Funktion, die die SortDescription ändert, aber bereits nach ca. 70 ms fertig. Angezeigt werden von der Liste nur rund 25 Zeilen (mit 11 Spalten). Wo werden die restlichen fast zwei Sekunden verbraucht ? Kann man das (signifikant) beschleunigen ? Viele Grüße Christoph
    • Typ geändert cpehonk Samstag, 15. Oktober 2011 18:30 Neue Erkenntnisse
    • Typ geändert cpehonk Freitag, 21. Oktober 2011 04:20 Im englischen Forum neu gestellt
    Mittwoch, 12. Oktober 2011 03:57

Alle Antworten

  • Ich habe Dein Szenario mal nachgestellt und kann Dein Problem nicht reproduzieren. Ich vermute, dass es in deinem Code noch andere Probleme gibt, z.B. falsche Bindungen. Hier meine Demo:
    <Window x:Class="Window32"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window32" Height="300" Width="600"
            xmlns:local="clr-namespace:WpfApplication1" >
      <Window.Resources>
        <local:Window32VM x:Key="vm"/>
      </Window.Resources>
      <Grid DataContext="{Binding Source={StaticResource vm}}">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Content="Sort" Command="{Binding cmdSort}" Name="Button1" />
        <ListView Grid.Row="1" ItemsSource="{Binding View}">
          <ListView.View>
            <GridView >
              <GridViewColumn Width="Auto" Header="Datum" DisplayMemberBinding="{Binding Datum}"/>
              <GridViewColumn Width="Auto" Header="Wert 1" DisplayMemberBinding="{Binding Wert1}"/>
              <GridViewColumn Width="Auto" Header="Wert 2" DisplayMemberBinding="{Binding Wert2}"/>
              <GridViewColumn Width="Auto" Header="Wert 3" DisplayMemberBinding="{Binding Wert3}"/>
              <GridViewColumn Width="Auto" Header="Wert 4" DisplayMemberBinding="{Binding Wert4}"/>
              <GridViewColumn Width="Auto" Header="Wert 5" DisplayMemberBinding="{Binding Wert5}"/>
              <GridViewColumn Width="Auto" Header="Wert 6" DisplayMemberBinding="{Binding Wert6}"/>
              <GridViewColumn Width="Auto" Header="Wert 7" DisplayMemberBinding="{Binding Wert7}"/>
              <GridViewColumn Width="Auto" Header="Wert 8" DisplayMemberBinding="{Binding Wert8}"/>
              <GridViewColumn Width="Auto" Header="Wert 9" DisplayMemberBinding="{Binding Wert9}"/>
              <GridViewColumn Width="Auto" Header="Wert 10" DisplayMemberBinding="{Binding Wert10}"/>
            </GridView>
          </ListView.View>
        </ListView>
      </Grid>
    </Window>
    

    Dazu der ViewModel:

    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class Window32VM
      Implements INotifyPropertyChanged
    
      Dim rnd As New Random
    
      Public Sub New()
        For i = 1 To 4000
          liste.Add(New Window32Item With {.Datum = Now.AddDays(i * 10), _
                                           .Wert1 = rnd.NextDouble, _
                                           .Wert2 = rnd.NextDouble, _
                                           .Wert3 = rnd.NextDouble, _
                                           .Wert4 = rnd.NextDouble, _
                                           .Wert5 = rnd.NextDouble, _
                                           .Wert6 = rnd.NextDouble, _
                                           .Wert7 = rnd.NextDouble, _
                                           .Wert8 = rnd.NextDouble, _
                                           .Wert9 = rnd.NextDouble, _
                                           .Wert10 = rnd.NextDouble})
        Next
      End Sub
    
      Private liste As New ObservableCollection(Of Window32Item)
      Private cvs As New CollectionViewSource With {.Source = liste}
    
      Public ReadOnly Property View As ICollectionView
        Get
          Return cvs.View
        End Get
      End Property
    
      Public ReadOnly Property cmdSort As ICommand
        Get
          Return New RelayCommand(New Action(Of Object)(AddressOf cmdExec))
        End Get
      End Property
    
      Private Sub cmdExec(ByVal state As Object)
        If cvs.View.SortDescriptions.Count = 0 OrElse cvs.View.SortDescriptions(0).PropertyName = "Wert2" Then
          cvs.View.SortDescriptions.Clear()
          cvs.View.SortDescriptions.Add(New SortDescription("Wert1", ListSortDirection.Ascending))
        Else
          cvs.View.SortDescriptions.Clear()
          cvs.View.SortDescriptions.Add(New SortDescription("Wert2", ListSortDirection.Ascending))
        End If
        OnPropertyChanged("View")
      End Sub
    
    #Region " PropertyChanged"
    
      Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
      Private Sub OnPropertyChanged(ByVal prop As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
      End Sub
    
    #End Region
    
    End Class
    
    Public Class Window32Item
      Public Property Datum As Date
      Public Property Wert1 As Double
      Public Property Wert2 As Double
      Public Property Wert3 As Double
      Public Property Wert4 As Double
      Public Property Wert5 As Double
      Public Property Wert6 As Double
      Public Property Wert7 As Double
      Public Property Wert8 As Double
      Public Property Wert9 As Double
      Public Property Wert10 As Double
    End Class
    

    --
    Viele Gruesse
    Peter

    Mittwoch, 12. Oktober 2011 18:36
  • Hallo Peter, ich konnte auch Dank Deiner kleinen Demo das Problem soweit tracken, dass es anscheinend an den Templates liegt, die ich für die Spaltenzellen verwende. U.a. macht alleine ein Template eines Textblocks, der decimal-Werte als Währung und negative Zahlen zusätzlich in Rot darstellt, eine spürbare Veränderung aus. Gibt es eine Möglichkeit, das Darstellen von Währung und in Rot ohne ein Zell-Template zu erzeugen ? Im Cell-Template wird dafür natürlich dann ein Converter aufgerufen. Von den 11 Spalten sind nur 3, die keine eigene Template-Zuweisung haben. P.S.: Habe den Code in C# geschrieben und auch schon durch die Analyse-Funktion gescheucht. Viele Grüße Christoph
    Mittwoch, 12. Oktober 2011 19:11
  • Ohne zu wissen, was Du konkret machst, kann man da nur raten. Hier mal eine Demo, wie ich das mache und alles läuft sehr schnell:
    <Window x:Class="Window32"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window32" Height="300" Width="600"
            xmlns:local="clr-namespace:WpfApplication1" >
      <Window.Resources>
        <local:Window32VM x:Key="vm"/>
      </Window.Resources>
      <Grid DataContext="{Binding Source={StaticResource vm}}">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Content="Sort" Command="{Binding cmdSort}" Name="Button1" />
        <ListView Grid.Row="1" ItemsSource="{Binding View}">
          <ListView.View>
            <GridView >
              <GridViewColumn Width="Auto" Header="Datum" DisplayMemberBinding="{Binding Datum}"/>
              <GridViewColumn Width="Auto" Header="Wert 1" >
                <GridViewColumn.CellTemplate>
                  <DataTemplate>
                    <Label Content="{Binding Wert1}" Background="{Binding Wert1Farbe}" />
                  </DataTemplate>
                </GridViewColumn.CellTemplate>
              </GridViewColumn>
              <GridViewColumn Width="Auto" Header="Wert 2" DisplayMemberBinding="{Binding Wert2}"/>
              <GridViewColumn Width="Auto" Header="Wert 3" DisplayMemberBinding="{Binding Wert3}"/>
              <GridViewColumn Width="Auto" Header="Wert 4" DisplayMemberBinding="{Binding Wert4}"/>
              <GridViewColumn Width="Auto" Header="Wert 5" DisplayMemberBinding="{Binding Wert5}"/>
              <GridViewColumn Width="Auto" Header="Wert 6" DisplayMemberBinding="{Binding Wert6}"/>
              <GridViewColumn Width="Auto" Header="Wert 7" DisplayMemberBinding="{Binding Wert7}"/>
              <GridViewColumn Width="Auto" Header="Wert 8" DisplayMemberBinding="{Binding Wert8}"/>
              <GridViewColumn Width="Auto" Header="Wert 9" DisplayMemberBinding="{Binding Wert9}"/>
              <GridViewColumn Width="Auto" Header="Wert 10" DisplayMemberBinding="{Binding Wert10}"/>
            </GridView>
          </ListView.View>
        </ListView>
      </Grid>
    </Window>
    

    Dazu der ViewModel:

    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class Window32VM
      Implements INotifyPropertyChanged
    
      Dim rnd As New Random
    
      Public Sub New()
        For i = 1 To 4000
          liste.Add(New Window32Item With {.Datum = Now.AddDays(i * 10), _
                                           .Wert1 = rnd.NextDouble, _
                                           .Wert2 = rnd.NextDouble, _
                                           .Wert3 = rnd.NextDouble, _
                                           .Wert4 = rnd.NextDouble, _
                                           .Wert5 = rnd.NextDouble, _
                                           .Wert6 = rnd.NextDouble, _
                                           .Wert7 = rnd.NextDouble, _
                                           .Wert8 = rnd.NextDouble, _
                                           .Wert9 = rnd.NextDouble, _
                                           .Wert10 = rnd.NextDouble})
        Next
      End Sub
    
      Private liste As New ObservableCollection(Of Window32Item)
      Private cvs As New CollectionViewSource With {.Source = liste}
    
      Public ReadOnly Property View As ICollectionView
        Get
          Return cvs.View
        End Get
      End Property
    
      Public ReadOnly Property cmdSort As ICommand
        Get
          Return New RelayCommand(New Action(Of Object)(AddressOf cmdExec))
        End Get
      End Property
    
      Private Sub cmdExec(ByVal state As Object)
        If cvs.View.SortDescriptions.Count = 0 OrElse cvs.View.SortDescriptions(0).PropertyName = "Wert2" Then
          cvs.View.SortDescriptions.Clear()
          cvs.View.SortDescriptions.Add(New SortDescription("Wert1", ListSortDirection.Ascending))
        Else
          cvs.View.SortDescriptions.Clear()
          cvs.View.SortDescriptions.Add(New SortDescription("Wert2", ListSortDirection.Ascending))
        End If
        OnPropertyChanged("View")
      End Sub
    
    #Region " PropertyChanged"
    
      Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
      Private Sub OnPropertyChanged(ByVal prop As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
      End Sub
    
    #End Region
    
    End Class
    
    Public Class Window32Item
    
      Public Shared Farbe1 As New SolidColorBrush(Colors.LightPink)
      Public Shared Farbe2 As New SolidColorBrush(Colors.White)
    
      Public Property Datum As Date
      Public Property Wert1 As Double
      Public ReadOnly Property Wert1Farbe As SolidColorBrush
        Get
          If Wert1 > 0.5 Then
            Return Farbe1
          Else
            Return Farbe2
          End If
        End Get
      End Property
      Public Property Wert2 As Double
      Public Property Wert3 As Double
      Public Property Wert4 As Double
      Public Property Wert5 As Double
      Public Property Wert6 As Double
      Public Property Wert7 As Double
      Public Property Wert8 As Double
      Public Property Wert9 As Double
      Public Property Wert10 As Double
    End Class
    
    

    --
    Viele Gruesse
    Peter

    Mittwoch, 12. Oktober 2011 19:56
  • Hallo Peter, ich fürchte, ich habe an der falschen Stelle gesucht. Mit einem Standard-ListView (GridView) ist auch bei mir trotz der Converter alles sehr schnell. Allerdings ist mein GridView (und der Header) deutlich verändert (Skalierbarer Content des GridView ohne die Scrollbars auch zu skalieren, DropDrown-Button in den Spalten-Headern mit Filterdialog, etc.). Damit wird es langsam. Jetzt muss ich mich einmal herantasten, wo die Performance verschwindet ... Viele Grüße Christoph
    Mittwoch, 12. Oktober 2011 20:28
  • Hallo,

    eine mögliche Ursache für das Laufzeitverhalten könnte das setzen von Pixelweise Scrollen für deine ListView sein. Das entsprechende Property ist:

     <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
    

    Setzt man CanContentScroll auf false, so wird die Liste pixelweise gescrollt und es findet keine Virtualisierung mehr statt. Nur bei itemweisen Scrolling gibt es eine Virtualisierung!

     

    Gruss Andreas

    Donnerstag, 13. Oktober 2011 08:16
  • Hallo Andreas,

    danke für den Tipp, aber daran liegt es nicht. Ohne Gruppen virtualisiert der ListView. Aber ich habe zwischen der Rückkehr der Funktion und der visuellen Darstellung der Daten fast 2 Sekunden, die vergehen. Damit muss die Zeit irgendwie in der Erstellung der Visuals verloren gehen.

    Mit einem Standard-GridView (ohne meine Erweiterungen) ist dies jedoch nicht der Fall. Also habe ich irgendwo in den Templates einen Resourcen oder Performance-Fresser.

    Viele Grüße

    Christoph

    Donnerstag, 13. Oktober 2011 17:17
  • Ich habe den Performance-Fresser entdeckt: es ist der GroupStyle. Wenn ich keinen GroupStyle angebe, dann ist der ListView auch schnell. Erst mit dem GroupStyle wird er langsam. Anbei mein GroupStyle:

           <GroupStyle>
                <GroupStyle.ContainerStyle>
                    <Style TargetType="{x:Type GroupItem}">
                        <Setter Property="Margin" Value="0,0,0,5"/>
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type GroupItem}">
                                    <Grid>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto" />
                                            <RowDefinition Height="Auto" />
                                            <RowDefinition Height="Auto" />
                                        </Grid.RowDefinitions>
                                        <DockPanel Grid.Row="0">
                                            <ToggleButton Name="Expander" IsChecked="True" Margin="4,1,1,1" VerticalAlignment="Center">
                                                <ToggleButton.Style>
                                                    <Style TargetType="ToggleButton">
                                                        <Setter Property="OverridesDefaultStyle" Value="True" />
                                                        <Setter Property="Template">
                                                            <Setter.Value>
                                                                <ControlTemplate TargetType="ToggleButton">
                                                                    <Path Name="Arrow" Data="M0,0 L0,4 L4,2z" Fill="Black" Stretch="Uniform" Width="10" Height="10" />
                                                                    <ControlTemplate.Triggers>
                                                                        <Trigger Property="IsChecked" Value="True">
                                                                            <Setter Property="Data" TargetName="Arrow" Value="M0,0 L4,0 L2,4z" />
                                                                        </Trigger>
                                                                        <Trigger Property="IsMouseOver" Value="True">
                                                                            <Setter Property="Fill" TargetName="Arrow" Value="{StaticResource btnMouseOverBackBrush}" />
                                                                        </Trigger>
                                                                        <Trigger Property="IsPressed" Value="True">
                                                                            <Setter Property="Fill" TargetName="Arrow" Value="{StaticResource btnPressedBackBrush}" />
                                                                        </Trigger>
                                                                    </ControlTemplate.Triggers>
                                                                </ControlTemplate>
                                                            </Setter.Value>
                                                        </Setter>
                                                    </Style>
                                                </ToggleButton.Style>
                                            </ToggleButton>
                                            <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,15,0" />
                                            <TextBlock Text="(" />
                                            <TextBlock Text="{Binding Path=ItemCount}"/>
                                            <TextBlock Text=")" />
                                            <TextBlock FontStyle="Italic"
                                                       Foreground="{Binding Converter={StaticResource SubTotalColConv}}"
                                                       Text="{Binding Converter={StaticResource SubTotalConv},StringFormat=C,ConverterCulture=de-de}" Margin="5,0,15,0" />
                                        </DockPanel>
                                        <Path Grid.Row="1" Height="1" Data="M0,0 L1,0" Stretch="Fill" Stroke="Silver" StrokeDashArray="3 3" Margin="14,1,1,1" />
                                        <ItemsPresenter Grid.Row="2" Visibility="{Binding IsChecked,ElementName=Expander,Converter={StaticResource BoolVisible}}" />
                                    </Grid>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </GroupStyle.ContainerStyle>
            </GroupStyle>
    
    Gibt es irgendetwas, was mir nicht aufgefallen ist und soviel Performance benötigt ?
    Viele Grüße, Christoph
    Samstag, 15. Oktober 2011 18:29
  • Hast Du mal die Zeit gemessen, dein Dein Converter “SubTotalConv” verbraucht?
     
    --
    Viele Gruesse
    Peter
    Samstag, 15. Oktober 2011 18:38
  • Beide Converter werden *NICHT* aufgerufen. Erst wenn das Gruppieren eingeschaltet wird. Bis dahin erfolgt kein Call.

    Viele Grüße

    Christoph

    Samstag, 15. Oktober 2011 18:46
  • Hallo,

     

    ich würde an deiner Stelle mal versuchen mir den VisualTree über Snoop oder WpfInspector anzuschauen. Dann solltest du feststellen können, ob die Virtualisierung deiner ListView funktioniert. Ich vermute mal, dass die Virtualisierung bei aktivierter Gruppierung deaktiviert ist.

     

    Ciao Andreas

    Montag, 17. Oktober 2011 06:28
  • Ja, bei aktivierter Gruppierung schon. Allerdings ist der ListView so langsam, obwohl die Gruppierung nicht eingeschaltet ist. Nur die Gruppenstil-Definition ist angegeben. Das macht die Sache so merkwürdig.

    Chrsitoph

    Montag, 17. Oktober 2011 17:34