Performanceprobleme bei vielen Elementen

  • WPF

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von Niko Ortner.

    Performanceprobleme bei vielen Elementen

    Ich habe mich jetzt erstmals richtig an WPF herangewagt.
    Ich habe den XAML-Code auf das wesentliche gekürzt:
    Spoiler anzeigen

    XML-Quellcode

    1. <Window
    2. xmlns:t="clr-namespace:Tags3"
    3. SizeChanged="OnMainWindowSizeChanged"
    4. >
    5. <Window.Resources>
    6. <!-- 5 Styles -->
    7. </Window.Resources>
    8. <Window.DataContext>
    9. <t:MainWindowViewModel/>
    10. </Window.DataContext>
    11. <Grid>
    12. <TextBlock Visibility="{Binding Path=ResizingViewVisible}">Größenänderung...</TextBlock>
    13. <Grid Visibility="{Binding Path=LoadingTranslationsViewVisible}">
    14. <TextBlock>Lade Übersetzungen...</TextBlock>
    15. <ProgressBar Maximum="{Binding Path=LoadTranslationsMaximum}" Value="{Binding Path=LoadTranslationsProgress}"/>
    16. </Grid>
    17. <DockPanel Visibility="{Binding Path=TranslationsViewVisible}">
    18. <Menu DockPanel.Dock="Top">
    19. <!-- Ein paar MenuItems -->
    20. </Menu>
    21. <ScrollViewer>
    22. <ItemsControl ItemsSource="{Binding Path=Texts}">
    23. <ItemsControl.ItemsPanel>
    24. <ItemsPanelTemplate>
    25. <StackPanel />
    26. </ItemsPanelTemplate>
    27. </ItemsControl.ItemsPanel>
    28. <ItemsControl.ItemTemplate>
    29. <DataTemplate>
    30. <GroupBox Header="{Binding Path=LineNumberString}">
    31. <Grid>
    32. <Grid.ColumnDefinitions>
    33. <ColumnDefinition Width="80"/>
    34. <ColumnDefinition Width="*"/>
    35. </Grid.ColumnDefinitions>
    36. <Grid.RowDefinitions>
    37. <RowDefinition Height="{Binding Path=TaggedOriginalRowHeight}"/>
    38. <RowDefinition Height="{Binding Path=SplittedOriginalRowHeight}"/>
    39. <RowDefinition Height="{Binding Path=PlainOriginalRowHeight}"/>
    40. <RowDefinition Height="Auto"/>
    41. <RowDefinition Height="{Binding Path=SplittedTranslationRowHeight}"/>
    42. <RowDefinition Height="{Binding Path=TaggedTranslationRowHeight}"/>
    43. </Grid.RowDefinitions>
    44. <TextBlock Grid.Column="0" Grid.Row="0">Mit Tags:</TextBlock>
    45. <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Path=TaggedOriginal}"/>
    46. <TextBlock Grid.Column="0" Grid.Row="1">Aufteilung:</TextBlock>
    47. <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Path=SplittedOriginal}"/>
    48. <TextBlock Grid.Column="0" Grid.Row="2">Ohne Tags:</TextBlock>
    49. <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Path=PlainOriginal}"/>
    50. <TextBlock Grid.Column="0" Grid.Row="3">Übersetzung:</TextBlock>
    51. <TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Path=PlainTranslation, UpdateSourceTrigger=PropertyChanged}"/>
    52. <TextBlock Grid.Column="0" Grid.Row="4">Aufteilung:</TextBlock>
    53. <TextBox Grid.Column="1" Grid.Row="4" Text="{Binding Path=SplittedTranslation}"/>
    54. <TextBlock Grid.Column="0" Grid.Row="5">Mit Tags:</TextBlock>
    55. <TextBox Grid.Column="1" Grid.Row="5" Text="{Binding Path=TaggedTranslation}"/>
    56. </Grid>
    57. </GroupBox>
    58. </DataTemplate>
    59. </ItemsControl.ItemTemplate>
    60. </ItemsControl>
    61. </ScrollViewer>
    62. </DockPanel>
    63. </Grid>
    64. </Window>

    Das sieht dann so aus:

    Die Daten kommen in dieser Form ins Programm:

    Quellcode

    1. Comment: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,,
    2. Dialogue: 10,0:00:22.69,0:00:23.63,Default,Mar,0,0,0,,{\an8}Nun,
    3. Dialogue: 10,0:00:23.63,0:00:28.25,Default,Mar,0,0,20,,{\an8}beginnen wir mit der Besprechung des Angriffs, den wir bald starten werden.
    4. Dialogue: 10,0:00:44.27,0:00:49.52,Default,Dad,0,0,0,,{\i1}Du sollst hier nicht deine Zeit verschwenden.{\i0}

    Für jede dieser Zeilen wird im ItemsControl eine GrupBox mit 6 TextBoxen erstellt.
    Die Höhen der RowDefinitions sind an Properties gebunden, damit man sie ausblenden kann (0 / Auto).

    Das funktioniert mit 40 Zeilen noch ganz gut, aber sobald man über 60 hinaus geht, fängt es an, stark zu ruckeln, wenn man die Größe des Fensters ändert:


    Leider gibt es nur das SizeChanged-Event, und nicht wie bei Forms auch ein ResizeBegin- und ResizeEnd-Event. Ich habe versucht, beim ersten Auslösen des SizeChanged-Events die ganzen Elemente auszublenden (die Visibility des DockPanels auf Collapsed zu setzen) und ein paar Millisekunden nach dem letzten Auslösen die Elemente wieder anzuzeigen. Aber das Problem dabei ist, dass dieses Event erst ausgelöst wird, wenn es bereits zu spät ist. Zuerst wird einmal gelayoutet und dann werden die Elemente ausgeblendet. Das bringt natürlich nichts.

    Dazu also meine Fragen: Habe ich WPF überschätzt? Oder mache ich irgendwas falsch?
    Wie kann ich das Problem mit dem Flackern beheben?
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Du verwendest ein normales StackPanel, welches immer alle Children rendert, unabhängig davon, ob sie sichtbar sind oder nicht.

    Ein VirtualizingStackPanel macht zunächst diese Überprüfung:
    msdn.microsoft.com/de-de/libra…stackpanel(v=vs.110).aspx
    miteshsureja.blogspot.de/2011/…g-stack-panel-in-wpf.html
    stackoverflow.com/questions/13…for-increased-performance
    | Keine Fragen per PN oder Skype.
    So, ich hab das mal ausprobiert, aber leider hilft das Verwenden von einem VirtualizingStackPanel auch nicht weiter.
    Ich habe den XAML-Code so umgeschrieben:

    XML-Quellcode

    1. <ScrollViewer>
    2. <ItemsControl>
    3. <ItemsControl.ItemsPanel>
    4. <ItemsPanelTemplate>
    5. <VirtualizingStackPanel IsVirtualizing="True" VirtualizationMode="Recycling" />
    6. </ItemsPanelTemplate>
    7. </ItemsControl.ItemsPanel>
    8. <!-- ItemsControl.ItemTemplate... -->
    9. </ItemsControl>
    10. </ScrollViewer>

    Es gibt keinen Unterschied zwischen StackPanel und VirtualizingStackPanel. Bei beiden hängt es eine Weile, bis das Fenster richtig gelayoutet ist.

    DataVirtualization hab ich auch mal ausprobiert, bei mir läuft es aber immer darauf hinaus, dass die ganze Liste abgefragt wird, und nicht nur der Bereich, der gerade angezeigt wird.

    Trotzdem finde ich das Thema sehr interessant und weil ich was dazugelernt habe, hast Du ein Hilfreich bekommen ;)
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Ich bin definitiv kein WPF-Pro, aber ich denke, es liegt daran, dass alles einzeln geupdated wird auf der GUI.
    Verwende doch mal eine BindingList, dann kannst du dort Events disablen, um die GUI vom Empfangen jeglicher Events abzuhalten.

    Das geht btw mit:

    C-Quellcode

    1. BindingList.RaiseListChangedEvents = false;


    Danach setzt du es einfach auf trueund rufst die Methode ResetBindings auf.
    Kannst du ja mal versuchen.
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Ich hab jetzt erst mal anderweitig am Projekt weiter gearbeitet und dieses Problem hinten an gestellt.
    Ein Testprojekt ist im Anhang.

    Die BindingList werde ich gleich probieren. Muss mich da erst zurechtfinden.

    Edit: Verwende ich die so richtig?

    VB.NET-Quellcode

    1. Dim _Items As BindingList(Of Item)
    2. Public ReadOnly Property Items As BindingList(Of Item)
    3. Get
    4. Return _Items
    5. End Get
    6. End Property
    7. Public Sub New()
    8. Dim Temp As New List(Of Item)
    9. For i = 1 To If(DesignerProperties.GetIsInDesignMode(New DependencyObject), 30, 627)
    10. Temp.Add(New Item("Item " & i.ToString))
    11. Next
    12. _Items = New BindingList(Of Item)(Temp)
    13. _Items.RaiseListChangedEvents = True
    14. End Sub

    Denn das Problem besteht nach wie vor (habs im angehängten Testprojekt getestet).
    Und Laut dieser Seite sollte man BindingList nicht in WPF verwenden, sondern stattdessen ObservableCollection verwenden.
    Dateien
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Niko Ortner“ ()

    Das Problem ist das Layout. Du hast das ItemsControl in einen ScrollViewer gepackt, da macht es sich wg. Width, Heigth.Auto so groß, dass alle Items reinpassen (das ist bei 600 Items sehr groß).
    Ohne ScrollViewer ist aber auch doof, weil ItemsControl scheint gar keine eigenen Scrollbars zu haben :(

    Lösung: Listbox nehmen statt ItemsControl, weil Listbox hat die Scrollbar eingebaut.
    Allerdings muss man da den ItemContainer manipulieren, damit der ebenso schön wie im ItemsControl die ganze Breite umfasst:

    XML-Quellcode

    1. <Window
    2. x:Class="MainWindow"
    3. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    4. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    5. xmlns:c="clr-namespace:Virtualisierung"
    6. Title="MainWindow" Height="350" Width="350">
    7. <Window.DataContext>
    8. <c:MainWindowViewModel/>
    9. </Window.DataContext>
    10. <Grid>
    11. <ListBox ItemsSource="{Binding Path=Items}">
    12. <ItemsControl.ItemsPanel>
    13. <ItemsPanelTemplate>
    14. <VirtualizingStackPanel IsVirtualizing="True" VirtualizationMode="Recycling"/>
    15. </ItemsPanelTemplate>
    16. </ItemsControl.ItemsPanel>
    17. <ItemsControl.ItemContainerStyle>
    18. <Style TargetType="{x:Type ListBoxItem}">
    19. <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    20. </Style>
    21. </ItemsControl.ItemContainerStyle>
    22. <ItemsControl.ItemTemplate>
    23. <DataTemplate>
    24. <GroupBox Header="{Binding Path=Value}" >
    25. <TextBox Text="{Binding Path=Stuff}"/>
    26. </GroupBox>
    27. </DataTemplate>
    28. </ItemsControl.ItemTemplate>
    29. </ListBox>
    30. </Grid>
    31. </Window>
    Nun nimmt die Listbox nur den verfügbaren Raum ein, und daher können die ItemContainer auch virtualisieren.
    Erstaunlicherweise gehts sogar ohne Virtualisierung - scheint garnix zu bringen - probiers mal mit paar tausend Items 8|

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Wow, danke danke danke!
    Ich musste noch bei der ListBox HorizontalContentAlignment auf Stretch stellen (das Setzen von HorizontalAlignment im Style war dann nicht mehr nötig).
    (Hier gefunden.)

    Wie man die Markierung der ListBox entfernt habe ich noch nicht herausgefunden. Im ItemContainerStyle Focusable auf False zu setzen funktioniert schon mal nicht. Ich bin noch am Probieren und meld mich dann... oder einer von euch hat eine Idee ;)
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Niko Ortner“ ()

    Danke, hab das mit ein bisschen Experimentieren hinbekommen.
    Hab diesen XAML-Code verwendet:

    XML-Quellcode

    1. <ListBox.ItemContainerStyle>
    2. <Style TargetType="ListBoxItem">
    3. <Setter Property="Focusable" Value="False"/>
    4. <Setter Property="Template">
    5. <Setter.Value>
    6. <ControlTemplate TargetType="{x:Type ListBoxItem}">
    7. <Grid>
    8. <Border Background="{TemplateBinding Background}"/>
    9. <Border Background="#BEFFFFFF">
    10. <Grid>
    11. <Grid.RowDefinitions>
    12. <RowDefinition/>
    13. </Grid.RowDefinitions>
    14. <Border Grid.Row="0" Background="#57FFFFFF"/>
    15. </Grid>
    16. </Border>
    17. <ContentPresenter/>
    18. </Grid>
    19. <ControlTemplate.Triggers>
    20. <MultiTrigger>
    21. <MultiTrigger.Conditions>
    22. <Condition Property="IsMouseOver" Value="True"/>
    23. <Condition Property="IsSelected" Value="False"/>
    24. </MultiTrigger.Conditions>
    25. <Setter Property="Background" Value="Transparent"/>
    26. </MultiTrigger>
    27. </ControlTemplate.Triggers>
    28. </ControlTemplate>
    29. </Setter.Value>
    30. </Setter>
    31. </Style>
    32. </ListBox.ItemContainerStyle>
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Niko Ortner“ ()

    jo, und ich hab auch nochmal geforscht - es ist so: ItemsControl laggt, Listbox (welche von ItemsControl erbt) nicht. 8| ?( ;(

    Dafür habich eine Vereinfachung deines Styles:

    XML-Quellcode

    1. <ListBox.ItemContainerStyle>
    2. <Style TargetType="ListBoxItem">
    3. <Setter Property="Focusable" Value="False"/>
    4. <Setter Property="Template">
    5. <Setter.Value>
    6. <ControlTemplate TargetType="{x:Type ListBoxItem}">
    7. <ContentPresenter/>
    8. </ControlTemplate>
    9. </Setter.Value>
    10. </Setter>
    11. </Style>
    12. </ListBox.ItemContainerStyle>

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Danke dafür. Kannst Du vielleicht auch erklären, warum das so funktioniert? Ich verstehe zwar die einzelnen Elemente, aber ich verstehe nicht, wie das Ganze zusammen spielt und wie dadurch heraus kommt, dass die Markierung weg ist.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    das ist ganz einfach:
    Ein normales ListboxItem - gugge ObjectBrowser - erbt von ContentControl und hat noch zusätzliches Brimborium.
    Aber da hauen wir ein ControlTemplate drüber, also ersetzen das komplette Visual des ListboxItems durch etwas primitiveres: ContentPresenter. Der hat keine SelectionColor, sondern der zeigt grade mal ein DataTemplate an.